引言
前段時間看到一篇刷 LeetCode 的文章,感觸很深,我本身自己上大學的時候,沒怎麼研究過演算法這一方面,導致自己直到現在演算法都不咋地。
一直有心想填補下自己的這個短板,實際上又一直給自己找理由各種推脫。
結果今天晚上吃多了,下定決心要刷一波 LeetCode 的題,刷題的過程順便寫點文章分享下(其實有很好的督促作用)。
LeetCode 的背景啥的我就不多介紹了,我計劃只刷簡單難度和中等難度的題,據說刷了這兩個難度基本上就夠了,至於困難這個難度,先等我把前兩個部分刷完再說。
大致聊一下刷題的套路,先看題目, 5 分鐘左右沒思路的直接看答案,這玩意沒思路是真沒辦法,沒有基礎儲備想做題還是有點困難的。
看完答案自己動手寫一下程式碼,這一點很重要,現在面試很多白板程式碼,一定要自己寫,寫會有效加深記憶力。
這個系列的文章計劃日更,原本以為不是一件很難的事情,結果當我看到了這個:
1700+ 多道題,即使刨除掉困難的部分, 2/3 也有 1000+ 道題,我真的是給自己開了一個非常棒的頭,這一下把未來兩三年的規劃都制定好了,我真是嗶了狗了。
不過不管怎麼樣吧,決心都下好了,那麼做還是要做的,對我這個計劃感興趣的同學可以每天在留言區和我一起打卡,預計每篇文章閱讀時長在 3 分鐘左右,寫程式碼加除錯程式碼總時長不會超過半小時。使用的程式碼為 Java ,如果使用 Python 寫的話有點太取巧了。
做事情,重要的是要堅持。
需要程式碼朋友可以訪問我的 GitHub 和 Gitee 獲取,每天的程式碼我會同步提交至這兩個程式碼倉庫:
GitHub:https://github.com/meteor1993/LeetCode
Gitee:https://gitee.com/inwsy/LeetCode
題目:兩數之和
給定一個整數陣列 nums 和一個目標值 target,請你在該陣列中找出和為目標值的那兩個整數,並返回他們的陣列下標。
你可以假設每種輸入只會對應一個答案。但是,陣列中同一個元素不能使用兩遍。
示例:
給定 nums = [2, 7, 11, 15], target = 9
因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
思路
首先整理下思路,雖然題目的目標是要求一個加法,使用程式直接寫加法有點不是那麼好寫,稍微轉變一下,使用目標值 target 減去陣列 nums 中的一個值,如果得到的結果也在這個陣列中,那麼我們就解題完成。
暴力破解
我最先想到的也是最簡單的方案,就是暴力破解,直接兩個迴圈套一下,暴力去算,得到結果:
public int[] twoSum_1(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
int temp = target - nums[i];
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] == temp) {
return new int[]{i, j};
}
}
}
throw new IllegalArgumentException("No two sum solution");
}
這種方案的缺點是時間複雜度有點高,優點是簡單,應該是每個人都能想到的方案。
因為總共有 n 個元素,而對於其中的每個元素來講,我們都需要遍歷整個陣列來尋找是否存在它所需要的對應的元素,這將耗費 O(n) 的時間。
時間複雜度: \(O(n^2)\) 。
空間複雜度: O(1) 。
兩次雜湊表
對暴力破解方案的優化思路是,整個陣列,至少需要迭代一次,關鍵是在這次迭代中,我們要尋找另一種方案,能比套一層迴圈更快更高效的找到檢查在整個陣列中,是否含有我們需要的值。
我們可以藉助雜湊表來進行尋找,它支援以 「近似」 恆定的時間進行快速查詢。
public int[] twoSum_2(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement) && map.get(complement) != i) {
return new int[] {i, map.get(complement)};
}
}
throw new IllegalArgumentException("No two sum solution");
}
我們把包含有 n 個元素的列表遍歷兩次。由於雜湊表將查詢時間縮短到 O(1) ,所以時間複雜度為 O(n)。
時間複雜度: O(n) 。
空間複雜度: O(n) 。
一次雜湊表:
上面我們是先把陣列放到雜湊表中,然後再進行遍歷,實際上我們可以一邊放一邊進行遍歷操作,直到某一個時刻,打成我們的目標,這時我們可以直接返回資料,剩下沒有放到雜湊表中的資料也不用再往裡面放了。
public int[] twoSum_3(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
雖然這種方式看著比前面的兩次雜湊表更加的高效,實際上時間複雜度和空間複雜度是一致的,同樣是:
時間複雜度: O(n) 。
空間複雜度: O(n) 。