資料庫索引調優技巧
阿新 • • 發佈:2020-12-29
技術標籤:LeetCode雙指標leetcode演算法三數之和
15. 三數之和
1、參考資料
https://leetcode-cn.com/problems/3sum/
https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
2、題目要求
題目要求:
給你一個包含 n
個整數的陣列 nums
,判斷 nums
中是否存在三個元素 a
,b
,c
,使得 a + b + c = 0
,請你找出所有滿足條件且不重複的三元組。
注意:答案中不可以包含重複的三元組。
示例:
給定陣列 nums = [-1, 0, 1, 2, -1, -4], 滿足要求的三元組集合為: [ [-1, 0, 1], [-1, -1, 2] ]
3、程式碼思路
暴力匹配
- 暴力匹配可以,三層
for
迴圈帶給我們的是 O(n3) 的複雜度,我們還需要將三個數按照從小到大的順序排列,並使用HashMap
進行去重操作,得到不包含重複三元組的最終答案 - 因為
(a, b, c)
、(b, a, c)
、(b, c, a)
屬於重複的三元組,匹配之後我們需要進行排序、去重操作
排序 + 雙指標
- 我們首先要理解不重複的本質是什麼?我們先將陣列排序,然後在陣列中找出三個數
a
、b
、c
,滿足其和:a + b + c = 0
,其中a <= b <= c
,這樣保證了只有(a, b, c)
這樣的順序,不會出現(b, a, c)
或者(b, c, a)
- 但是這樣做我們始終沒有跳出三層
for
迴圈的思想,實際上第二重 + 第三重迴圈我們可以使用一層while
迴圈 + 雙指標來替代 left = i + 1
為左指標(i
最外層for
迴圈的計數器),right = nums.length - 1
為右指標。當left
增加,right
不變時,三數之和增大;當right
減小,left
不變時,三數之和減小;我們利用這個特點,可以將原來兩層for
迴圈變為一層while
迴圈,即我們可以通過雙指標的方式將內層迴圈的複雜度從 O(n2) 降為 O(n)- 為了保證得到的三元組為不重複的三元組,對於數
a
for
迴圈中都判斷數a
是否與上次的值相同,如果相同,則跳過本次迴圈,對於left
和right
,我們每次在外層for
開始時,將left
重置為i + 1
,將right
重置為nums.length - 1
,在匹配到三數之和等於0
時,將left
右移至與上次元素值不同的位置,將right
左移至與上次元素值不同的位置(保證三元組不重複)
總結:
- 題目要求不可以包含重複的三元組,我們可以使用模式識別,利用排序避免重複答案
- 將陣列排序後,進入第一層
for
迴圈之後其實就是twoSum
問題,與twoSum
不同的是,twoSum
只需要找到一組解,而這裡要找到所有解 ,從而將問題變為:在一個排序陣列中找到所有的兩數之和 - 在一個排序陣列中找到所有的兩數之和:兩個指標分別指向陣列的首位,如果
sum > target
,那麼首指標後移,使得sum
增大;如果sum < target
,那麼尾指標前移,使得sum
減小
4、程式碼實現
程式碼
/**
* @ClassName ThreeSumDemo
* @Description TODO
* @Author Heygo
* @Date 2020/9/2 17:11
* @Version 1.0
*/
public class ThreeSumDemo {
public static void main(String[] args) {
List<List<Integer>> res = threeSum(new int[]{-1, 0, 1, 2, -1, -4});
System.out.println(res);
}
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
// Guard Safe
if (nums == null || nums.length <= 2) {
return result;
}
// 先對陣列進行排序,後面才能方便地進行判斷是否重複
Arrays.sort(nums);
// 遍歷陣列,當 i = nums.length - 3時,left = nums.length - 2,right = nums.length - 1,為臨界條件
for (int i = 0; i < nums.length - 2; i++) {
// 如果當前元素大於 0,證明已經將陣列中三數之和等於 3 的元素都找出來了,直接跳出迴圈
if (nums[i] > 0) {
break;
}
// 上一次的元素和當前相同,跳過此次迴圈
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int target = -nums[i]; // 目標值
int left = i + 1; // left 指標
int right = nums.length - 1; // right 指標
// 當 left < right 時,證明還有元素待驗證
while (left < right) {
if (nums[left] + nums[right] == target) {
// 如果三數之和等於 0,則將這三個數加入集合中
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
left++; // 左指標右移
right--; // 右指標左移
// 左指標需要移動至下一個與上次元素值不相同的位置
while (left < right && nums[left] == nums[left - 1]) {
left++;
}
// 右指標需要移動至下一個與上次元素值不相同的位置
while (left < right && nums[right] == nums[right + 1]) {
right--;
}
} else if (nums[left] + nums[right] < target) {
// 否則三數之和小於目標值,則 left++,嘗試增大三數之和
left++;
} else {
// 否則三數之和大於目標值,則 right--,嘗試減小三數之和
right--;
}
}
}
return result;
}
}