友情提示:篇幅較長,找題目的話,右邊有目錄,幸虧我會MarkDown語法。
改爲了系列模式,由於相似的題很多,本質上都是換殼,因此在同一篇文章裏面把這類問題彙總一下。先說2 Sum。這道題很是出名,由於這是leetcode的第一道題。不少人說一些公司招聘的時候,這道題專門出給他們想招進來的人,由於這不是放水,簡直就是洪水。要作的就是已知一個數組,和一個target值。返回兩個目標的index,這兩個目標求和就是target值。編程
這題不難,只須要熟悉hashtable便可。在hashtable裏面,key是差,value是index。好比例子中的[2,7,11,15]
,target是9。那麼在2的時候就存入7 0
,下一位找到7的時候,以前有個差值是7,那麼就返回7對應的index,0,以及當前這個7的index,就是1。數組
public class Solution { public int[] twoSum(int[] nums, int target) { //建立一下數組,要存兩個index的數組。 int[] result = new int[2]; //這裏用hashtable也行,看心情。 Map<Integer, Integer> map = new HashMap<Integer, Integer>(); //掃一遍數組,一邊掃一邊存 for(int i = 0; i < nums.length; i++){ int cur = nums[i]; //這裏搞出來個差值,其實差值是在沒找到以後添加到map裏面的。 int toFind = target - cur; //若是發現以前須要這個差值,那就找index。 if(map.containsKey(cur)){ result[0] = map.get(cur); result[1] = i; return result; } //若是沒有,就put到map裏面 else{ map.put(toFind, i); } } return result; } }
就是O(n),由於只掃了一遍數組。less
雖然這題很是簡單,可是14年秋天第一次看到這題的時候感受仍是難到爆炸,無從下手。由於一絲編程基礎都沒有,如今兩年過去了,用腳趾都能敲出來。其實行業之間努力其次,路徑很是重要,在這裏感謝一下點撥個人老鄉和兄弟們。並且從新寫了幾回,連變量命名都是同樣的。測試
這題用sorted
當作題目,比如路邊的一些職業勾引男性行人,很是直接的就意味着二分搜索。一次查一半,因此剛開始只用到了二分搜索。可是有個問題,二分搜索的步子太大,可能把目標值跳過,那麼還要借鑑雙指針的全盤掃描的特色。線程
public class Two_Sum_II { public int[] twoSum(int[] numbers, int target) { int[] result = new int[2]; //這裏用二分搜索,我經常使用start和end來命名兩頭,middle是中間。 int start = 0; int end = numbers.length-1; //這個while循環條件很巧妙,二分搜索建議固定一個模板,這個就挺好固定的。 while (start + 1 < end) { //看,我剛說的是實話,並且這裏middle的計算方法是防止越界。 int middle = start + (end-start)/2; if (numbers[start] + numbers[end] < target) { //這裏須要判斷,究竟是跳一半仍是走一步,就再加個判斷。 if (numbers[middle] + numbers[end] < target) { start = middle; } else { start++; } } else if(numbers[start] + numbers[end] > target) { if (numbers[middle] + numbers[start] > target) { end = middle; } else { end--; } } else { break; } } //題目中保證了有結果,還不是zero-based,那麼就把result兩項都續一秒。 result[0] = start+1; result[1] = end+1; return result; } }
固然就是最壞狀況O(n)了,也是標準的雙指針複雜度。不過二分搜索方法是它最優狀況是O(nlgn)。翻譯
不得不說,這個題目把二分搜索和雙指針拼在一塊兒很是有創意。只用二分搜索讓我多交了一個submit,好題一個。指針
Design and implement a TwoSum class. It should support the following operations: add and find. add - Add the number to an internal data structure. find - Find if there exists any pair of numbers which sum is equal to the value. For example, add(1); add(3); add(5); find(4) -> true find(7) -> false
鎖住的題,罕見的一道design題目和Google不要緊,tag只有Linkedin,怪不得被收購了。code
這是一道入門級別的design題目,固然第一次遇到design這個詞我就像腦血栓般渾身發抖。不過好在它脫胎於Two Sum,本質上仍是不難的,咱們要作的就是套上design的外殼,解決掉它。值得注意的是,一個數字只能用1次,因此仍是要記錄一下數字出現的次數的。排序
public class TwoSumIII { //用一個List當容器裝數字,用Map來記錄數字出現的次數 List<Integer> list = new ArrayList<>(); Map<Integer, Integer> map = new HashMap<Integer, Integer>(); // Add the number to an internal data structure. public void add(int number) { list.add(number); //很是常規的往map裏記錄出現個數的寫法 if (map.containsKey(number)) { map.put(number, map.get(number)+1); } else { map.put(number,1); } } // Find if there exists any pair of numbers which sum is equal to the value. public boolean find(int value) { for (int i = 0; i < list.size(); i++) { int cur = list.get(i); int toFind = value - cur; //這裏仍是Two Sum的求法,取一個,找另外一個。值得注意的是須要看求和的兩個數是否是相等。 if (cur != toFind) { //根據leetcode測試,在map裏面找比在list找目標數字更快一些。 if (map.containsKey(toFind)) { return true; } } else { if (map.get(cur) > 1) { return true; } } } return false; } }
這種題應該不太須要分析複雜度吧,能實現就行。每次都是遍歷一遍List,因此就是O(n)。three
寫的時候發現其實遍歷一下Map也行,能夠省去一個list。但我恰恰不省,由於List不要錢,能加就加,並且看着也方便,一個map用於不一樣的用途,可能會引發線程衝突。出來混,多一事不如少一事。
這題用腳後跟看都是2Sum的follow up。就是在一個數組裏面挑3個數字,這三個數字的和爲0就行。須要注意的是triplet這個單詞的拼寫和發音,還有不能有重複的triplet,不能重複這一點仍是有點兒小麻煩的
既然是follow up,解決思路也就是follow up。follow up是什麼意思呢,咱們翻譯一下,follow就是跟隨,up就是上面。就是跟隨上面的意思,咱們往上看,上面只有2Sum一題,那咱們就跟隨一下。A+B
是2Sum,A+B+C
是3Sum,那麼稍加修改A+(B+C)就成了這兩道題鏈接的橋樑。因此這題的基本思路就是套了個殼子而已。
值得一提的是,此題可能有重複數字,並且要求不能有重複結果,因此使用雙指針法。前面這句的不是很理所固然,在這裏就當經驗記錄一下了,強行解釋就是指針能夠跳太重複的數字,並且求和也很容易。
public class ThreeSum { public List<List<Integer>> threeSum(int[] nums) { //首先把輸出寫出來 List<List<Integer>> result = new ArrayList<>(); //雙指針出馬以前數組一般須要排序 Arrays.sort(nums); for (int i = 0; i < nums.length; i++) { int cur = nums[i]; //這個本意是重複數字的話能夠跳過。由於以前的數字已經打頭過了,重複的就不必打頭了。 if (i > 0 && cur == nums[i-1]) { continue; } //雙指針出馬,這裏注意了我通常命名成left和right。 int left = i+1; int right = nums.length-1; //這裏注意了開始2Sum過程。 while (left < right) { int two_sum = nums[left] + nums[right]; if (two_sum < -1*cur) { //說明加和小了,那把左指針往右移動 left++; } else if (two_sum > -1 * cur) { //大了那就往左移動 right--; } else { List<Integer> list = new ArrayList<>(); list.add(cur); list.add(nums[left]); list.add(nums[right]); //把此次記錄的結果加到result裏面 result.add(list); //下回測試corner case的時候就一羣0,此次4個0就吃虧了,忘了這個while循環,因此要跳太重複數字。 while(left+1 < right && nums[left] == nums[left+1]){ left++; } while (right-1 > left && nums[right] == nums[right-1]) { right--; } //跳過以後,繼續挪動一下。 left++; right--; } } } return result; } }
這個排序的複雜度是O(nlgn),循環的複雜度就是O(n^2),因此就是循環的複雜度n方。
其實這種數組題,不管多麼的熟練,都要在紙上先勾畫一下思路,尤爲是這道題裏面重複數字的處理,其實也能夠用個set來去重,但那樣畢竟顏值不行,不符合我自拍的一向水準。跳過相等數字,最後再挪動一下,code裏面很差分析,在紙上畫畫一目瞭然。
Given an array of n integers nums and a target, find the number of index triplets i, j, k with 0 <= i < j < k < n that satisfy the condition nums[i] + nums[j] + nums[k] < target. For example, given nums = [-2, 0, 1, 3], and target = 2. Return 2. Because there are two triplets which sums are less than 2: [-2, 0, 1] [-2, 0, 3] Follow up: Could you solve it in O(n2) runtime?
這題竟然是鎖住的,company tag只有一個Google,因此把題目內容貼上來。
這題說老實話讓我很困惑,爲啥這題都能當一道題出了。其實就是3Sum稍微變一下,而後返回個個數而不是一羣triplet。並且要求是O(n2),那類比3Sum的雙指針方法能夠知足。
public class Three_Sum_Smaller {
public int threeSumSmaller(int[] nums, int target) { //雙指針是必定要排序的,不然後面根據大小挪動指針就沒有意義了。 Arrays.sort(nums); int result = 0; for (int i = 0; i < nums.length-2; i++) { int left = i+1; int right = nums.length-1; int cur = nums[i]; while (left < right) { int two_sum = nums[left] + nums[right]; //這裏須要注意若是知足條件,接下來不須要移動指針,直接把中間全部的可能性都加起來就能夠 if (cur + two_sum < target) { result += right - left; left++; } else { //只有和大了,纔要讓右邊指針左移,讓總體小一些。 right--; } } } return result; }
}
直接O(n2)了,就是兩重循環,多說一句,雙指針就是把O(n2)降成O(n)的,外面再套一層循環,就是O(n2)了。
這題會作了,Google是否是能過一輪了啊。就注意小於的狀況直接求result就行。
仍是一個數組,還有個目標數。返回距離目標最近的一個和,這個和是3個數的和。
和上面同樣,雙指針,看大小。
public class ThreeSumClosest { public int threeSumClosest(int[] nums, int target) { if(nums == null || nums.length < 3){ return 0; } int res = 0; int diff = Integer.MAX_VALUE; Arrays.sort(nums); for(int cur = 0; cur < nums.length-2; cur++){ int left = cur+1; int right = nums.length-1; int tempTar = target-nums[cur]; while(left < right){ int sum = nums[left] + nums[right]; int value = Math.abs(sum-tempTar); //找到更小的就更新。 if(value <= diff){ diff = value; res = nums[cur] + nums[left] + nums[right]; } if(sum < tempTar){ left++; } else if(sum > tempTar){ right--; } else{ return target; } } } return res; } }
仍是O(n2).
因此能夠看出,不少題目思路一致,換湯不換藥。都是雙指針掃數組,很是容易。
此次是4個,就是找四個數,它們的和是目標數。
此次就是3 Sum套了個殼而已,方法都是同樣的。
public class FourSum { public List<List<Integer>> fourSum(int[] nums, int target) { List<List<Integer>> res = new ArrayList<>(); //象徵性的說,若是沒有4個數,那還玩個球啊 if(nums.length < 4) return res; //雙指針排序,都看膩了吧 Arrays.sort(nums); for(int i = 0; i < nums.length-3; i++){ //去掉重複的數字,就是打頭不能相同 if(i > 0 && nums[i] == nums[i-1]) continue; for(int j = i+1; j < nums.length-2; j++){ //再去掉一遍 if(j > i+1 && nums[j] == nums[j-1]) continue; //實力打臉,之後仍是要left和right,low和high太low了。 int low = j+1, high = nums.length-1; while(low < high){ int sum = nums[i] + nums[j] + nums[low] + nums[high]; if(sum == target){ //這裏新建一個list也行。 res.add(Arrays.asList(nums[i],nums[j], nums[low], nums[high])); while(low+1 < high && nums[low+1] == nums[low]){ low++; } while(high-1 > low && nums[high-1] == nums[high]){ high--; } low++; high--; } else if(sum < target){ low++; } else{ high--; } } } } return res; } }
O(n3),由於是三重循環。
這個系列結束了,沒想到從2 Sum能夠延伸這麼長。不過核心思想都差很少,一些細節處,好比去掉重複數字這種手法須要多加熟練。這個9月加油找了。