2 Sum 這題是 Leetcode 的第一題,相信大部分小夥伴都聽過的吧。web
做爲一道標着 Easy 難度的題,它真的這麼簡單嗎?面試
我在以前的刷題視頻裏說過,你們刷題必定要吃透一類題,爲何有的人題目作着愈來愈少,有的人總以爲刷不完的題,就是由於沒有分類吃透。算法
單純的追求作題數量是沒有意義的,Leetcode 的題目只會愈來愈多,就像高三時的模考試卷同樣作不完,但分類總結,學會解決問題的方式方法,才能遇到新題也不手足無措。數組
這道題題意就是,給一個數組和一個目標值,讓你在這個數組裏找到兩個數,使得它倆之和等於這個目標值的。數據結構
好比題目中給的例子,目標值是 9,而後數組裏 2 + 7 = 9
,因而返回 2 和 7 的下標。編輯器
在我多年前還不知道時空複雜度的時候,我想這還不簡單嘛,就每一個組合挨個試一遍唄,也就是兩層循環。flex
後來我才知道,這樣時間複雜度是很高的,是 O(n^2)
;但另外一方面,這種方法的空間複雜度最低,是 O(1)
。優化
因此,面試時必定要先問面試官,是但願優化時間仍是優化空間。url
通常來講咱們追求優化時間,但你不能默認面試官也是這麼想的,有時候他就是想考你有沒有這個意識呢。spa
若是一個方法可以兼具優化時間和空間那就更好了,好比斐波那契數列這個問題中從遞歸到 DP 的優化,就是時間和空間的雙重優化,不清楚的同窗後臺回覆「遞歸」快去補課~
咱們來看下這個代碼:
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[]{-1, -1};
}
}
喏,這速度不太行誒。
那在我學了 HashMap
這個數據結構以後呢,我又有了新的想法。
HashMap
或者 HashSet
的最大優點就是可以用 O(1)
的時間獲取到目標值,那麼是否是能夠優化方法一的第二個循環呢?
有了這個思路,假設當前在看 x
,那就是須要把 x
以前或者以後的數放在 HashSet
裏,而後看下 target - x
在不在這個 hashSet
裏,若是在的話,那就匹配成功~
誒這裏有個問題,這題要求返回這倆數的下標,但是 HashSet
裏的數是無序的...
那就用升級版——HashMap
嘛~~還不瞭解 HashMap
的原理的同窗快去公衆號後臺回覆「HashMap」看文章啦。
HashMap
裏記錄下數值和它的 index
這樣匹配成功以後就能夠順便獲得 index
了。
這裏咱們不須要提早記錄全部的值,只須要邊過數組邊記錄就行了,爲了防止重複,咱們只在這個當前的數出現以前的數組部分裏找另外一個數。
總結一下,
HashMap
裏記錄的是下標
i
以前的全部出現過的數;
nums[i]
,咱們先檢查
target - nums[i]
是否在這個
map
裏;
i
的信息加進
map
裏。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
res[0] = map.get(target - nums[i]);
res[1] = i;
return res;
}
map.put(nums[i], i);
}
return res;
}
}
喏,速度提高至 beat 99.96%
這是最基本的 2 Sum
問題,這個題能夠有太多的變種了:
若是這個數組裏有不止一組結果,要求返回全部組合,該怎麼作?
若是這個數組裏有重複元素,又該怎麼作?
若是這個數組是一個排好序了的數組,那如何利用這個條件呢?- Leetcode 167
若是不是數組而是給一個 BST
,該怎麼在一棵樹上找這倆數呢?- Leetcode 653
...
這裏講一下排序數組這道題,以後會在 BST
的文章裏會講 653 這題。
咱們知道排序算法中最快的也須要 O(nlogn)
,因此若是是一個 2 Sum
問題,那不必專門排序,由於排序會成爲運算的瓶頸。
但若是題目給的就是個排好序了的數組,那確定要好好收着了呀!
由於當數組是排好序的時候,咱們能夠進一步優化空間,達到 O(n)
的時間和 O(1)
的空間。
該怎麼利用排好序這個性質呢?
那就是說,在 x
右邊的數,都比 x
要大;在 x
左邊的數,都比 x
要小。
若是 x + y > target
,那麼就要 y
往左走,往小的方向走;
若是 x + y < target
,那麼就要 x
往右走,往大的方向走。
這也就是典型的 Two pointer
算法,兩個指針相向而行的狀況,我以後也會出文章詳細來說噠。
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[]{left + 1, right + 1}; //Your returned answers are not zero-based.
} else if (sum < target) {
left ++;
} else {
right --;
}
}
return new int[]{-1, -1};
}
}
3 Sum
的問題其實就是一個 2 Sum
的升級版,由於 1 + 2 = 3 嘛。。
那就是外面一層循環,固定一個值,在剩下的數組裏作 2 Sum
問題。
反正 3 Sum
怎麼着都得 O(n^2)
,就能夠先排序,反正不在意排序的這點時間了,這樣就能夠用 Two pointer
來作了。
還須要注意的是,這道題返回的是數值,而非 index
,因此它不須要重複的數值——The solution set must not contain duplicate triplets.
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i + 2 < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
// skip same result
continue;
}
int j = i + 1;
int k = nums.length - 1;
int target = -nums[i];
while (j < k) {
if (nums[j] + nums[k] == target) {
res.add(Arrays.asList(nums[i], nums[j], nums[k]));
j++;
k--;
while (j < k && nums[j] == nums[j - 1]) {
j++; // skip same result
}
while (j < k && nums[k] == nums[k + 1]) {
k--; // skip same result
}
} else if (nums[j] + nums[k] > target) {
k--;
} else {
j++;
}
}
}
return res;
}
}
最後就是 4 Sum
問題啦。
這一題若是隻是 O(n^3)
的解法沒什麼難的,由於就是在 3 Sum
的基礎上再加一層循環嘛。
可是若是在面試中只作出 O(n^3)
恐怕就過不了了哦😯
這 4 個數,能夠想成兩兩的 2 Sum
,先把第一個 2 Sum
的結果存下來,而後在後續的數組中作第二個 2 Sum
,這樣就能夠把時間下降到 O(n^2)
了。
這裏要注意的是,爲了避免重複,也就是下圖的 nums[x] + nums[y] + nums[z] + nums[k]
,其實和 nums[z] + nums[k] + nums[x] + nums[y]
並無區別,因此咱們要限制第二組的兩個數要在第一組的兩個數以後哦。
看下代碼吧:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Set<List<Integer>> set = new HashSet<>();
Map<Integer, List<List<Integer>>> map = new HashMap<>();
Arrays.sort(nums);
// 先處理第一對,把它們的sum存下來
for(int i = 0; i < nums.length - 3; i++) {
for(int j = i + 1; j < nums.length - 2; j++) {
int currSum = nums[i] + nums[j];
List<List<Integer>> pairs = map.getOrDefault(currSum, new ArrayList<>());
pairs.add(Arrays.asList(i, j));
map.put(currSum, pairs);
}
}
// 在其後作two sum
for(int i = 2; i < nums.length - 1; i++) {
for(int j = i + 1; j < nums.length; j++) {
int currSum = nums[i] + nums[j];
List<List<Integer>> prevPairs = map.get(target - currSum);
if(prevPairs == null) {
continue;
}
for(List<Integer> pair : prevPairs) {
if(pair.get(1) < i) {
set.add(Arrays.asList(nums[pair.get(0)], nums[pair.get(1)], nums[i], nums[j]));
}
}
}
}
return new ArrayList<>(set);
}
}
好啦,以上就是 2 Sum
相關的全部問題啦,若是有收穫的話,記得關注我哦~