原文作者:aircraft
原文鏈接:https://www.cnblogs.com/DOMLX/p/10940636.html
唉!最近忙着面試找實習,然后都是面試的很多是leetcode的算法題,所以自己就刷了一遍,並且做些筆記,以后再來復習好了,悲催的大學生。。。。。
一套面試題的目錄在此,還在繼續完善中。。。。。。
c/c++ 2019面試題目錄
一、從(排序!)數組中刪除重復項
給定一個排序數組,你需要在原地刪除重復出現的元素,使得每個元素只出現一次,返回移除后數組的新長度。
不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。
示例 1:
給定數組 nums = [1,1,2], 函數應該返回新的長度 2, 並且原數組 nums 的前兩個元素被修改為 1, 2。 你不需要考慮數組中超出新長度后面的元素。
示例 2:
給定 nums = [0,0,1,1,1,2,2,3,3,4], 函數應該返回新的長度 5, 並且原數組 nums 的前五個元素被修改為 0, 1, 2, 3, 4。 你不需要考慮數組中超出新長度后面的元素。
說明:
為什么返回數值是整數,但輸出的答案是數組呢?
請注意,輸入數組是以“引用”方式傳遞的,這意味着在函數里修改輸入數組對於調用者是可見的。
你可以想象內部操作如下:
// nums 是以“引用”方式傳遞的。也就是說,不對實參做任何拷貝
int len = removeDuplicates(nums); // 在函數里修改輸入數組對於調用者是可見的。 // 根據你的函數返回的長度, 它會打印出數組中該長度范圍內的所有元素。
for (int i = 0; i < len; i++) { print(nums[i]); }
我第一次自寫的代碼(因為審題不認真,寫成了無序數組的刪除QAQ---):
用時300ms.
class Solution { public: int removeDuplicates(vector<int>& nums) { vector<int> my_nums; bool same = 0; bool first = 1; for (auto num1 : nums) { if (first) { my_nums.emplace_back(num1); first = 0; continue; } for (auto num2 : my_nums) { if (num1 == num2) { same = 1; break; } } if (!same) my_nums.emplace_back(num1); same = 0; } for (int i = 0; i < my_nums.size(); i++) { nums[i] = my_nums[i]; } return my_nums.size(); } };
大佬們寫的示例代碼():
用時30ms左右:
class Solution { public: int removeDuplicates(vector<int>& nums) { int length = nums.size(); if(length == 0) return 0; int flag = 0; for(int i = 1; i < length; i++){ while(nums[i] == nums[flag]){ if(i < length-1) i++; else
break; } flag++; nums[flag] = nums[i]; } return (flag + 1); } };
用時20左右ms
class Solution { public: int removeDuplicates(vector<int>& nums) { int n = nums.size(); if(n == 0 || n == 1) return n; int index=0; int i = 1; while(i<n) { if(nums[index] != nums[i]) { index++; nums[index] = nums[i]; } i++; } return index+1; } };
自我反思:因為題目看錯導致跟大佬們的代碼對比反思不多所以就總結兩點
- 剛開始刷LeetCode沒有認真對待,沒有審題清楚。
- 即使是寫成無序數組的刪除還是沒有考慮到傳入參數為0和1的情況。
二、買賣股票的最佳時機 II
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨后,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之后再將它們賣出。
因為這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。
我的代碼:
12ms左右
class Solution { public: int maxProfit(vector<int>& prices) { int sell; int sell_value = 0; int buy_value = 0; int profit = 0; int len = prices.size(); if (len < 2) return 0; int i = 0; int index; while (i < len) { index = i + 1; if (index < len) { if (prices[i] < prices[index]) { buy_value = prices[i]; sell_value = prices[index]; sell = index; while (++index < len) { if (prices[sell] > prices[index]) { profit += sell_value - buy_value; i = index - 1; buy_value = sell_value = 0; break; } else { sell = index; sell_value = prices[index]; } } } } if (index == len)break; i++; } profit += sell_value - buy_value; return profit; } };
先講講我一開始看到這道題目是怎么分析的,從題意上看:
- 數組從大到小例如[5,4,3,2,1]的就直接沒有利潤,利潤置為0;
- 數組從小到大的直接返回最后的減去最前的就是利潤;
- 數組無序的,找到第一個滿足[i]<[index=i+1]的,i就是我們的買入點,index就先設置為賣的點;
- 然后從index往后遍歷,只要是找到小於index這個數組值的數的坐標就可以作為下一個買入點,到這里先計算第一個買入點和賣出點的利潤,然后將下一個買入點坐標賦值給i,這樣就變成了3步驟的重復,后面如果是從小到大或者大到小又變成計算1,2步驟
- 利潤profit從每個步驟中都加等起來,得到最終利潤
然后看看大佬們的代碼:
1ms左右
class Solution { public: int maxProfit(vector<int>& prices) { if (prices.size()<2) return 0; int profit=0; for (int i=0;i<prices.size()-1;i++) { if (prices[i]<prices[i+1]) profit+=(prices[i+1]-prices[i]); } return profit; } };
行吧,看到這個代碼的時候我就知道我是個傻子了,算法題說白了是什么?不就是數學題嗎,數學題考什么?考邏輯思維唄!!!(我根本沒有抓住這題的本質QAQ)
大佬們都是從數學上直接看這題的本質,不管我這個股票怎么買賣,只要有買入點和賣出點,那么最終的利潤都可以靠后一個個大的值減去鄰近的前一個小的值不斷的累加起來得到。
比如[1,7,8,9],買入1在9賣出利潤8,也可以等於 9-8加8-7加7-1得到利潤值,不管是[1,2,3,4,5]還是[5,4,3,2,1]或者無序的[7,1,5,3,6,4]都可以這樣累加差值得到利潤。
自我反思:emmmmm.....沒什么好反思的,我就是個菜雞和傻子。。。。。。
三、旋轉數組
給定一個數組,將數組中的元素向右移動 k 個位置,其中 k 是非負數。
示例 1:
輸入: [1,2,3,4,5,6,7]
和 k = 3
輸出: [5,6,7,1,2,3,4]
解釋:
向右旋轉 1 步: [7,1,2,3,4,5,6]
向右旋轉 2 步: [6,7,1,2,3,4,5]
向右旋轉 3 步:
[5,6,7,1,2,3,4]
示例 2:
輸入: [-1,-100,3,99]
和 k = 2
輸出: [3,99,-1,-100]
解釋:
說明:
- 盡可能想出更多的解決方案,至少有三種不同的方法可以解決這個問題。
- 要求使用空間復雜度為 O(1) 的原地算法。
我的代碼:
30ms左右
class Solution { public: void rotate(vector<int>& nums, int k) { vector<int> temp; int len = nums.size(); int pos; for(int i =0; i < len; i++){ pos = (2*len + i - k) % len; temp.emplace_back(nums[pos]); } pos = -1; while(++pos<len)nums[pos] = temp[pos]; } };
大佬的代碼:
好吧這次沒有大佬的代碼,因為那個坐標圖上12ms大佬的代碼實在點不出來啊,太小了,我的鼠標移動了半天都點不出來,MMP這leetcode前端程序員該不會是個傻Z吧 天哪嚕!!!!
自我反思:題目要求用O(1)的原地算法,而我的是O(n),恩,我是個菜雞,天哪好想看看12ms那個O(1)怎么寫。。。。哪位大哥好心幫我點出來評論在我博客下面唄。。。。難受QAQ
四、存在重復
給定一個整數數組,判斷是否存在重復元素。
如果任何值在數組中出現至少兩次,函數返回 true。如果數組中每個元素都不相同,則返回 false。
示例 1:
輸入: [1,2,3,1]
輸出: true
示例 2:
輸入: [1,2,3,4]
輸出: false
示例 3:
輸入: [1,1,1,3,3,4,3,2,4,2]
輸出: true
我的代碼:
第一次看到這個題目的時候,第一時間想到的就是常規的兩個for循環遍歷。。。。。然后就超時了,果然沒有這么簡單。只好用map鍵值對的方式來完成這個題目了
44ms左右:
class Solution { public: bool containsDuplicate(vector<int>& nums) { unordered_map<int, int> map; if (nums.size() < 2) return false; for (auto nm : nums) { map[nm]++; } for (auto mp : map) { if (mp.second > 1)return true; } return false; } };
36ms左右:
class Solution { public: bool containsDuplicate(vector<int>& nums) { unordered_map<int,string> map; for(auto nm:nums){ if(map.find(nm) != map.end()) return true; map.insert(pair<int,string>(nm,"1")); } return false; } };
上面兩種其實只是運用場景不同,
第一種:全部存入后,再去看哪一個存入次數大於1次。適用於重復數在中間或者很后面。
第二種:每次存入時都判斷是否已經有這個數字存入了,也就是每次都查找一次。適用於重復數在比較前的場景。
綜合的話還是覺得第一種更好。
大佬們的代碼:
20ms左右
const static auto io_speed_up = []() { std::ios::sync_with_stdio(false); cin.tie(0); return 0; }(); class Solution { public: bool containsDuplicate(vector<int>& nums) { if(nums.size()==1)return false; else { sort(nums.begin(),nums.end()); int size=nums.size(); for(int i=0;i<size-1;i++){ if(nums[i]==nums[i+1])return true; } return false; } } };
看到這個代碼的時候我都懵了,天哪你們還可以用標准庫自帶的sort排序。。。。。我服了!!!
然后還加了一個std::ios::sync_with_stdio(false);關閉緩沖
以及cin.tie(0);在默認的情況下cin綁定的是cout,每次執行 << 操作符的時候都要調用flush,這樣會增加IO負擔。可以通過tie(0)(0表示NULL)來解除cin與cout的綁定,進一步加快執行效率。這個有刷ACM的話就比較常見。
自我反思:果然我還是太單純了,不夠狡猾!!!
五、只出現一次的數字
給定一個非空整數數組,除了某個元素只出現一次以外,其余每個元素均出現兩次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性時間復雜度。 你可以不使用額外空間來實現嗎?
示例 1:
輸入: [2,2,1]
輸出: 1
示例 2:
輸入: [4,1,2,1,2]
輸出: 4
我的代碼:
48ms左右
class Solution { public: int singleNumber(vector<int>& nums) { int len = nums.size(); if(len == 1) return nums[0]; if(len == 0) return -8989; sort(nums.begin(),nums.end()); for(int i = 0; i < len ; i++){ if(i == 0){ if(nums[i] != nums[i + 1])return nums[i]; } else if(i == len - 1){ if(nums[i] != nums[i - 1])return nums[i]; } else if(nums[i] != nums[i - 1] && nums[i] != nums[i+1]) return nums[i]; } return -8989; } };
這次我看到這次就想到上一題,先排序好在進行常規遍歷對比,沒什么好說的,大部分人沒有經常刷算法題的都是我這個思維。
看看大佬們的代碼:
20ms左右
class Solution { public: int singleNumber(vector<int>& nums) { //求vector的最大值和最小值 int l = *min_element(nums.begin(), nums.end()), h = *max_element(nums.begin(), nums.end()); int mid, count; while(l <= h){ count = 0; mid = l + (h - l) / 2; for(int i = 0; i < nums.size(); i ++){ if(nums[i] <= mid) count++; } if(count % 2 == 0) l = mid + 1; else h = mid - 1; } return l; } };
這位大佬數學應用的不錯啊,直接就用二分計數的方法加上類似於遞歸的形式,不斷的確定出那個單數在哪一個值的區間,然后將其返回出來
8ms左右
class Solution { public: int singleNumber(vector<int>& nums) { //一個數異或一遍即可 int res = 0; for (int i = 0; i < nums.size(); i++){ res ^= nums[i]; } return res; } };
這位大佬就讓我感覺到了,他這編程基礎肯定無比的扎實,直接就用異或這個辦法一次遍歷得出答案。
異或就是只有在兩個比較的位不同時其結果是1,否則結果為0。兩個數相同的時候就是0了,而這組數里只有一個數字是單獨出現的,全部異或完一遍剩下的那個數就是結果
自我反思:對於編程這種事,數學跟編程知識基礎都是不可或缺的,像小小的位運算符有時候都會幫助我們剩下很多多余的操作。
六、兩個數組的交集 II
給定兩個數組,編寫一個函數來計算它們的交集。
示例 1:
輸入: nums1 = [1,2,2,1], nums2 = [2,2]
輸出: [2,2]
示例 2:
輸入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
輸出: [4,9]
說明:
- 輸出結果中每個元素出現的次數,應與元素在兩個數組中出現的次數一致。
- 我們可以不考慮輸出結果的順序。
進階:
- 如果給定的數組已經排好序呢?你將如何優化你的算法?
- 如果 nums1 的大小比 nums2 小很多,哪種方法更優?
- 如果 nums2 的元素存儲在磁盤上,磁盤內存是有限的,並且你不能一次加載所有的元素到內存中,你該怎么辦?
我的代碼:
16ms左右:
class Solution { public: vector<int> intersect(vector<int>& nums1, vector<int>& nums2) { vector<int> copy_nums1; vector<int> copy_nums2; vector<int> res; int len1 = nums1.size(); int len2 = nums2.size(); if(len1 == 0 || len2 == 0) return res; copy_nums1.assign(nums1.begin(),nums1.end()); copy_nums2.assign(nums2.begin(),nums2.end()); for(int i = 0; i < len1; i++){ for(int j = 0; j < len2; j++){ if(copy_nums1[i] == copy_nums2[j]){ res.emplace_back(copy_nums1[i]); copy_nums2.erase(copy_nums2.begin()+j); len2--; break; } } } return res; } };
剛開始看到這個題目的時候我只想出了兩個辦法,第一個就是上面這個---復制兩個數組之后可以判斷兩個數組,小的那個放在外循環,當然這里沒有加這個判斷,因為那是進階的內容了。
一個外循環數組第二個數組里面查找,找到之后存入res,為了避免二次利用那個數,直接將第二個數組數(反正也是拷貝的數組,可以直接在上面進行操作)刪除,最后返回的就是兩個數組的交集了
大佬們的代碼:
8ms左右:
class Solution { public: vector<int> intersect(vector<int>& nums1, vector<int>& nums2) { unordered_map<int,int>m; vector<int>res; for(auto a:nums1)++m[a]; for(auto a:nums2) { if(m[a]-->0)res.push_back(a); } return res; } };
這就是我第二個想法,用map來存儲,不過呢,他是用unordered_map底層是hash,查找方面速度會有一些優勢。
大佬們代碼:
1ms左右:
class Solution { public: vector<int> intersect(vector<int>& nums1, vector<int>& nums2) { vector<int>res; sort(nums1.begin(),nums1.end()); sort(nums2.begin(),nums2.end()); int i=0,j=0; while(i<nums1.size()&&j<nums2.size()) { if(nums1[i]==nums2[j]) { res.push_back(nums1[i]); ++i;++j; } else if(nums1[i]<nums2[j]) { i++; } else j++; } return res; } };
這個就是進階里面將數組排序之后在去尋找兩個數組的交集,這里兩個數組排序完之后沒有什么好探討的,就是使用類似雙指針的形式去不斷遍歷兩個數組找到交集存儲
自我反思:進階里面的三沒有怎么去思考,不過我想無非就是分段讀取吧。
七、加一
給定一個由整數組成的非空數組所表示的非負整數,在該數的基礎上加一。
最高位數字存放在數組的首位, 數組中每個元素只存儲一個數字。
你可以假設除了整數 0 之外,這個整數不會以零開頭。
示例 1:
輸入: [1,2,3]
輸出: [1,2,4]
解釋:
示例 2:
輸入: [4,3,2,1]
輸出: [4,3,2,2]
解釋:
我的代碼:
8ms左右:
class Solution { public: vector<int> plusOne(vector<int>& digits) { const int size = digits.size(); for (int i = size - 1; i >= 0; i--){ digits[i]++; digits[i] %= 10; if (digits[i] != 0) return digits; } reverse(digits.begin(), digits.end()); digits.emplace_back(1); reverse(digits.begin(), digits.end()); return digits; } };
這題無非就是兩種情況一種是末尾加一不會大於10的,就直接末尾加一返回數組就行了。
還有一種就是999,或者9999這種全是9的情況,加一的話要不斷的進位,那么問題就轉化成了如何在這個數組前面加一個1上去。
所以我這里用逆序補1后在逆序回來。
大佬們的代碼:
1ms左右:
class Solution { public: vector<int> plusOne(vector<int>& digits) { const int size = digits.size(); for (int i = size - 1; i >= 0; i--) { digits[i]++; digits[i] %= 10; if (digits[i] != 0) return digits; } digits.resize(size + 1); digits[0] = 1; return digits; } };
大佬們比我分析的好,我沒有想到這題數組值全是0的情況恰好可以直接使用resize來改變內存,resize改變后的數組默認全部初始化為0,恰好這題就是,只要把第一個0改成1就行了。
自我反思:對STL容器的各種方法和使用場景還沒有很好的掌握。
八、移動零
給定一個數組 nums
,編寫一個函數將所有 0
移動到數組的末尾,同時保持非零元素的相對順序。
示例:
輸入: [0,1,0,3,12]
輸出: [1,3,12,0,0]
說明:
- 必須在原數組上操作,不能拷貝額外的數組。
- 盡量減少操作次數。
我的代碼:
56ms左右
class Solution { public: void moveZeroes(vector<int>& nums) { int len = nums.size(); for(int i = 0; i < len; i++){ if(nums[i] == 0){ nums.erase(nums.begin() + i); nums.emplace_back(0); len--; i--; } } } };
我的思路就是遇到0就將其刪除然后補充在最后,這樣的邏輯雖然簡單,但是依靠vector函數來操作的話內部其實要做許許多多的事,導致時間很慢。
大佬們的代碼:
16ms左右:
class Solution { public: void moveZeroes(vector<int>& nums) { int j = 0;//快慢指針 for(int i = 0;i < nums.size();i++){ while(nums[j] != 0 && j < nums.size()-1){ j++; } if(nums[i] != 0 && i > j){ nums[j] = nums[i]; nums[i] = 0; } } } };
大佬的思路就是快慢指針,一個指針也就是for循環正常遍歷,而另外一個指針就不斷的先找到0的位置停止下來,等到for循環到了0后面的位置並且是有效值,就將他們交換。
說白了就是將0后最近的非0數與其交換,不過這樣的換法,從全局上看就是不斷的把每個0往末尾移動,移動的次數非常多。
8ms左右:
class Solution { public: void moveZeroes(vector<int>& nums) { int len = nums.size(); int i = -1,j = 0; while(j < len) { if(nums[j] != 0) { i += 1; nums[i] = nums[j]; } j += 1; } i += 1; while(i < len) { nums[i] = 0; i++; } } };
這個代碼就跟16ms那個代碼不一樣的思路,不是將0不斷的往后搬了。而是將有效的數字都提到前面來,然后用i記住有效數字的最后一位在哪,之后空間的全部一次性置為0。
九、兩數之和
給定一個整數數組 nums
和一個目標值 target
,請你在該數組中找出和為目標值的那 兩個 整數,並返回他們的數組下標。
你可以假設每種輸入只會對應一個答案。但是,你不能重復利用這個數組中同樣的元素。
示例:
給定 nums = [2, 7, 11, 15], target = 9
因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
我的代碼:
兩種方法,一種常規遍歷,一種hash存儲
44ms左右
class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { int len = nums.size(); vector<int> res; for(int i = 0; i < len; i++){ for(int j = i + 1; j < len; j++){ if(nums[i] + nums[j] == target){ res.emplace_back(i); res.emplace_back(j); return res; } } } return res; } };
上面這種就是常規的遍歷,沒什么好說的。
4ms左右
vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int, int> m; vector<int> temp; int len = nums.size(); for (int i = 0; i < len; ++i) { m[nums[i]] = i; } for (int i = 0; i < len; ++i) { if (m.count(target - nums[i]) && m[target - nums[i]] != i) { temp.emplace_back(i); temp.emplace_back(m[target - nums[i]]); return tem; } } return tem; }
這里還是用unordered_map容器來存儲查找,因為要返回的是下標。所以先將所有的值作為鍵,下標作為值存入容器。然后就是遍歷,因為兩數之和為target,那么已經知道一個值為Nums[i],那另外一個值就是target - nums[i]。
m[target - nums[i]] != i 加上這句就是為了防止自己找到自己 比如 【3,2,7】target = 6 ,這里的話不加判斷就會返回自己3的下標兩次【0,0】
然后這里使用unodered_map而不是map,因為本題不需要存入有序序列,並且本題有頻繁的查找工作,unodered_map底層為hash查找比紅黑樹更快。
十、有效的數獨
判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。
- 數字
1-9
在每一行只能出現一次。 - 數字
1-9
在每一列只能出現一次。 - 數字
1-9
在每一個以粗實線分隔的3x3
宮內只能出現一次。
上圖是一個部分填充的有效的數獨。
數獨部分空格內已填入了數字,空白格用 '.'
表示。
示例 1:
輸入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
輸出: true
示例 2:
輸入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
輸出: false
解釋: 除了第一行的第一個數字從 5 改為 8 以外,空格內其他數字均與 示例1 相同。
但由於位於左上角的 3x3 宮內有兩個 8 存在, 因此這個數獨是無效的。
說明:
- 一個有效的數獨(部分已被填充)不一定是可解的。
- 只需要根據以上規則,驗證已經填入的數字是否有效即可。
- 給定數獨序列只包含數字
1-9
和字符'.'
。 - 給定數獨永遠是
9x9
形式的。
我的代碼:
32ms左右
class Solution { public: bool containsDuplicate(vector<char>& nums) { if (nums.size() < 2) return false; unordered_map<char, int> map; for (auto nm : nums) { map[nm]++; } for (auto mp : map) { if (mp.second > 1)return true; } return false; } bool isValidSudoku(vector<vector<char>>& board) { const int bufsize = 2; vector<vector<char> > raw_col = { { '0' },{ '0' } }; vector<vector<char> > three_temp = { { '0' },{ '0' },{ '0' } }; for (int i = 0; i < board.size(); i++) { for (int j = 0; j < board[0].size(); j++) { if (board[i][j] != '.') { raw_col[0].emplace_back(board[i][j]); if (j <3) three_temp[0].emplace_back(board[i][j]); else if (j < 6) three_temp[1].emplace_back(board[i][j]); else three_temp[2].emplace_back(board[i][j]); } } if (containsDuplicate(raw_col[0])) return false; raw_col[0].clear(); if ((i + 1) % 3 == 0) { for (int k = 0; k < three_temp.size(); k++){ if (containsDuplicate(three_temp[k])) return false; three_temp[k].clear(); } } } for (int j = 0; j < board[0].size(); j++) { for (int i = 0; i < board.size(); i++) { if (board[i][j] != '.') raw_col[1].emplace_back(board[i][j]); } if (containsDuplicate(raw_col[1])) return false; raw_col[1].clear(); } return true; } };
我的思路很簡單,就是分別計算每一行每一列是否有重復,然后對於,判斷每個九宮格是否有重復我則是采用一個二維數組來存儲,看圖可以划分為9個九宮格,我每次存儲橫向的三個九宮格然后進行判斷里面是否出現重復。
然后跳到下三個個橫向九宮格的時候,將數組清空在次存入判斷
大佬們的代碼:
12ms左右
class Solution { public: bool isValidSudoku(vector<vector<char>>& board) { int col[10][10], line[10][10], sq[10][10][10]; memset(col,0,sizeof(col)); memset(line,0,sizeof(line)); memset(sq,0,sizeof(sq)); for(int i=0;i<9;i++){//i -> line for(int j=0;j<9;j++){ // j->col if(board[i][j]=='.') continue; int val=board[i][j]-'0'; // if(val<=0 || val>9) return false; if(col[j][val] || line[i][val] || sq[i/3][j/3][val]) return false; col[j][val]=line[i][val]=sq[i/3][j/3][val]=1; } } return true; } };
大佬則是采用創建三個標記空間的形式,把所有的數據用點亮標記的形式來判斷,比如if(col[j][val])就是判斷j列里val是否已經點亮為1,如果已經為1那么說明這列里面已經存在一個val值,那么就重復了。行也是這樣類推。
然后就是判斷九個九宮格內是否重復,也是將九宮格划分好,比如sq[i/3][j/3][val]=1;這就是第i行的第j個九宮格中的val元素點亮置為1.。i除以3是因為i值為0-3之間就是第一行的某個九宮格,3-6則是第二行的某個九宮格。。。。j的意思也這樣理解
4ms左右
class Solution { public: bool isValidSudoku(vector<vector<char>> &board) { char k; int num; bitset<9> hang; vector<bitset<9>> lie(9, hang), box(9, hang); for (int i = 0; i < 9; ++i) //行 { for (int j = 0; j < 9; ++j) //列 { k = board[i][j]; if (k != '.') { num = k - '0' - 1; if (hang[num] && lie[j][num] && box[i / 3 * 3 + j / 3][num]) return 0;else hang[num] = lie[j][num] = box[i / 3 * 3 + j / 3][num] = 1; } } hang.reset(); } return 1; } };
這個大佬應該是有刷過ACM之類的,跟上一個12ms的思路其實差不多,就是對內存各方面做了優化,用bitset來存儲數據,bitset就像一個bool類型的數組一樣,但是有空間優化——bitset中的一個元素一般只占1 bit,相當於一個char元素所占空間的八分之一。
十一、旋轉圖像
給定一個 n × n 的二維矩陣表示一個圖像。
將圖像順時針旋轉 90 度。
說明:
你必須在原地旋轉圖像,這意味着你需要直接修改輸入的二維矩陣。請不要使用另一個矩陣來旋轉圖像。
示例 1:
給定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋轉輸入矩陣,使其變為:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
給定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋轉輸入矩陣,使其變為:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
8ms左右:
class Solution { public: void rotate(vector<vector<int>>& matrix) { int length = matrix.size(); // 調換對角元素 for (int i = 0; i < length; i++) { for (int j = 0; j < length - i; j++) { int tmp = matrix[i][j]; matrix[i][j] = matrix[length - j - 1][length - i - 1]; matrix[length - j - 1][length - i - 1] = tmp; } } // 調換列元素 for (int i = 0; i < length; i++) { for (int j = 0; j < length / 2; j++) { int tmp = matrix[j][i]; matrix[j][i] = matrix[length - j - 1][i];//這里用異或來調換兩個元素也行,自己喜歡 matrix[length - j - 1][i] = tmp; } } } };
本來是想直接用圖像旋轉的原理公式,然是因為題目要求了只能在原數組中做旋轉操作,那么就只能看看在元素調換的方面有沒有什么規律。
調換所有(/)左下到右上對角線的對角元素
之前:
[ [1,2,3], [4,5,6], [7,8,9] ],
之后:
[ [9,6,3], [8,5,2], [7,4,1] ],
逆序所有列元素
之前:
[ [9,6,3], [8,5,2], [7,4,1] ],
之后:
[ [7,4,1], [8,5,2], [9,6,3] ],
同理如果要調換所有(\)左上到右下對角線的對角元素,那么就要逆序所有行元素
也可以看看我的其他面試題總結:
c++面試題中經常被面試官面試的小問題總結(一)(本篇偏向基礎知識)
后面還會繼續出LeetCode字符串篇以及一些其他教程,想繼續看的話關注在下吧hhhhhh
若有興趣交流分享技術,可關注本人公眾號,里面會不定期的分享各種編程教程,和共享源碼,諸如研究分享關於c/c++,python,前端,后端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識