題目:
給你一個整數數組?nums
?,判斷是否存在三元組?[nums[i], nums[j], nums[k]]
?滿足?i != j
、i != k
?且?j != k
?,同時還滿足?nums[i] + nums[j] + nums[k] == 0
?。請你返回所有和為?0
?且不重復的三元組。
注意:答案中不可以包含重復的三元組。
示例 1:
輸入:nums = [-1,0,1,2,-1,-4] 輸出:[[-1,-1,2],[-1,0,1]] 解釋: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元組是 [-1,0,1] 和 [-1,-1,2] 。 注意,輸出的順序和三元組的順序并不重要。
示例 2:
輸入:nums = [0,1,1] 輸出:[] 解釋:唯一可能的三元組和不為 0 。
示例 3:
輸入:nums = [0,0,0] 輸出:[[0,0,0]] 解釋:唯一可能的三元組和為 0 。
思路:
拿這個nums數組來舉例,首先將數組排序,然后有一層for循環,i從下標0的地方開始,同時定一個下標left 定義在i+1的位置上,定義下標right 在數組結尾的位置上。
依然還是在數組中找到 abc 使得a + b +c =0,我們這里相當于 a = nums[i],b = nums[left],c = nums[right]。
接下來如何移動left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就說明 此時三數之和大了,因為數組是排序后了,所以right下標就應該向左移動,這樣才能讓三數之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 說明 此時 三數之和小了,left 就向右移動,才能讓三數之和大一些,直到left與right相遇為止。
時間復雜度:O(n^2)。
代碼:
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> result;sort(nums.begin(), nums.end());// 找出a + b + c = 0// a = nums[i], b = nums[left], c = nums[right]for (int i = 0; i < nums.size(); i++) {// 排序之后如果第一個元素已經大于零,那么無論如何組合都不可能湊成三元組,直接返回結果就可以了if (nums[i] > 0) {return result;}// 錯誤去重a方法,將會漏掉-1,-1,2 這種情況/*if (nums[i] == nums[i + 1]) {continue;}*/// 正確去重a方法if (i > 0 && nums[i] == nums[i - 1]) {continue;}int left = i + 1;int right = nums.size() - 1;while (right > left) {// 去重復邏輯如果放在這里,0,0,0 的情況,可能直接導致 right<=left 了,從而漏掉了 0,0,0 這種三元組/*while (right > left && nums[right] == nums[right - 1]) right--;while (right > left && nums[left] == nums[left + 1]) left++;*/if (nums[i] + nums[left] + nums[right] > 0) right--;else if (nums[i] + nums[left] + nums[right] < 0) left++;else {result.push_back(vector<int>{nums[i], nums[left], nums[right]});// 去重邏輯應該放在找到一個三元組之后,對b 和 c去重while (right > left && nums[right] == nums[right - 1]) right--;while (right > left && nums[left] == nums[left + 1]) left++;// 找到答案時,雙指針同時收縮right--;left++;}}}return result;}
};
過程:
示例輸入:nums = [-1,0,1,2,-1,-4]
排序后的數組為:[-4, -1, -1, 0, 1, 2]
詳細執行過程如下:
-
i=0(nums[i]=-4)
-
left=1,right=5
-
計算和:-4 + (-1) + 2 = -3 < 0 → left++
-
left=2,和:-4 + (-1) + 2 = -3 < 0 → left++
-
... 直到 left >= right,未找到解。
-
-
i=1(nums[i]=-1)
-
非重復元素,left=2,right=5
-
和:-1 + (-1) + 2 = 0 → 記錄 [-1,-1,2]
-
跳過右側重復(無),right=4,left=3
-
-
新和:-1 + 0 + 1 = 0 → 記錄 [-1,0,1]
-
跳過重復后,循環結束。
-
-
-
i=2(nums[i]=-1)
-
與前一個元素重復,跳過。
-
-
i=3(nums[i]=0)
-
后續元素均正數,無解。
-
最終輸出:[[-1,-1,2], [-1,0,1]]
部分代碼分析:
?`if (i > 0 && nums[i] == nums[i-1])` 是去重核心邏輯**,目的是跳過重復的「第一個數」,確保不會生成重復的三元組。
📌 代碼邏輯解析
1. 數組先排序:排序后為 `[-4, -1, -1, 0, 1, 2]`。
2. 固定第一個數:遍歷每個元素 `nums[i]` 作為三元組的第一個數。
3. 雙指針找后兩個數:用 `left` 和 `right` 在 `i` 右側的區間內搜索和為 `nums[i]` 的兩個數。
但關鍵問題是:如果 `nums[i]` 重復出現,會導致重復的三元組。例如:
?當 `i=1` 時,`nums[i] = -1`,會找到三元組 `[-1,-1,2]` 和 `[-1,0,1]`。
-當 `i=2` 時,`nums[i] = -1`(與前一個數相同),此時若不跳過,會再次生成相同的三元組。
🌰 以你的示例逐步分析
1. 當 `i=1`(`nums[i]=-1`)
檢查條件 `i > 0 && nums[i] == nums[i-1]`:
`i=1 > 0` 且 `nums[1] = -1 == nums[0] = -4`? → **不成立**,繼續執行。
- 雙指針找到兩個解:`[-1,-1,2]` 和 `[-1,0,1]`。
2. 當 `i=2`(`nums[i]=-1`)
檢查條件 `i > 0 && nums[i] == nums[i-1]`:
`i=2 > 0` 且 `nums[2] = -1 == nums[1] = -1` → 成立,跳過本次循環。
為什么跳過??
? ?因為此時固定的是第二個 `-1`,而第一個 `-1`(即 `i=1`)已經處理過所有可能的三元組。如果繼續處理,會重復生成以 `-1` 開頭的相同三元組。
? 為什么要加 `i > 0`?
當 `i=0` 時,`nums[i-1]` 會越界,所以必須限定 `i > 0`。
只有從第二個元素開始(`i≥1`),才有必要檢查是否與前一個元素重復。
?🚫 如果不加這個條件會怎樣?
假設去掉 `i > 0`,則當 `i=0` 時,`nums[i]` 會與 `nums[i-1]`(即 `nums[-1]`,非法訪問)比較,導致未定義行為。
? 總結條件的作用
跳過重復的「第一個數」:確保每個不同的值只作為第一個數處理一次。
依賴數組已排序:只有排序后,重復元素才會相鄰,才能通過 `nums[i] == nums[i-1]` 判斷重復。
?🌟 完整去重邏輯
1. 第一個數的去重:通過 `if (i>0 && nums[i]==nums[i-1])` 跳過。
2. 第二個數的去重:在找到解后,`while (left < right && nums[left] == nums[left+1]) left++`。
3. 第三個數的去重:在找到解后,`while (left < right && nums[right] == nums[right-1]) right--`。
這三步共同確保三元組在三個維度上不重復。