給你乙個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有滿足條件且不重複的三元組。
注意:答案中不可以包含重複的三元組。
給定陣列 nums = [-1, 0, 1, 2, -1, -4],本題與兩數之和類似,是非常經典的面試題,但是做法不盡相同。滿足要求的三元組集合為:
[[-1, 0, 1],
[-1, -1, 2]
]
題目中要求找到所有「不重複」且和為 0 的三元組,這個「不重複」的要求使得我們無法簡單地使用三重迴圈列舉所有的三元組。這是因為在最壞的情況下,陣列中的元素全部為 0,即
[0, 0, 0, 0, 0, ..., 0, 0, 0]任意乙個三元組的和都為 0。如果我們直接使用三重迴圈列舉三元組,會得到 o(n^3) 個滿足題目要求的三元組(其中 n 是陣列的長度)時間複雜度至少為 o(n^3)。在這之後,我們還需要使用雜湊表進行去重操作,得到不包含重複三元組的最終答案,又消耗了大量的空間。這個做法的時間複雜度和空間複雜度都很高,因此我們要換一種思路來考慮這個問題。
「不重複」的本質是什麼?我們保持三重迴圈的大框架不變,只需要保證:
也就是說,我們列舉的三元組 (a, b, c) 滿足 a≤b≤c,保證了只有 (a, b, c) 這個順序會被列舉到,而 (b, a, c)、(c, b, a) 等等這些不會,這樣就減少了重複。要實現這一點,我們可以將陣列中的元素從小到大進行排序,隨後使用普通的三重迴圈就可以滿足上面的要求。
同時,對於每一重迴圈而言,相鄰兩次列舉的元素不能相同,否則也會造成重複。舉個例子,如果排完序的陣列為
[0, 1, 2, 2, 2, 3]我們使用三重迴圈列舉到的第乙個三元組為 (0, 1, 2),如果第三重迴圈繼續列舉下乙個元素,那麼仍然是三元組 (0, 1, 2),產生了重複。因此我們需要將第三重迴圈「跳到」下乙個不相同的元素,即陣列中的最後乙個元素 3,列舉三元組 (0, 1, 3)。^ ^ ^
下面給出了改進的方法的偽**實現:
nums.sort()
for first = 0 .. n-1
// 只有和上一次列舉的元素不相同,我們才會進行列舉
if first == 0 or nums[first] != nums[first-1] then
for second = first+1 .. n-1
if second == first+1 or nums[second] != nums[second-1] then
for third = second+1 .. n-1
if third == second+1 or nums[third] != nums[third-1] then
// 判斷是否有 a+b+c==0
check(first, second, third)
這種方法的時間複雜度仍然為 o(n^3),畢竟我們還是沒有跳出三重迴圈的大框架。然而它是很容易繼續優化的,可以發現,如果我們固定了前兩重迴圈列舉到的元素 a 和 b,那麼只有唯一的 c 滿足 a+b+c=0。當第二重迴圈往後列舉乙個元素 b′時,由於 b′>b,那麼滿足 a+b′ +c′=0 的 c′一定有 c' < c,即 c′在陣列中一定出現在 c 的左側。也就是說,我們可以從小到大列舉 b,同時從大到小列舉 c,即第二重迴圈和第三重迴圈實際上是並列的關係。
有了這樣的發現,我們就可以保持第二重迴圈不變,而將第三重迴圈變成乙個從陣列最右端開始向左移動的指標,從而得到下面的偽**:
nums.sort()
for first = 0 .. n-1
if first == 0 or nums[first] != nums[first-1] then
// 第三重迴圈對應的指標
third = n-1
for second = first+1 .. n-1
if second == first+1 or nums[second] != nums[second-1] then
// 向左移動指標,直到 a+b+c 不大於 0
while nums[first]+nums[second]+nums[third] > 0
third = third-1
// 判斷是否有 a+b+c==0
check(first, second, third)
這個方法就是我們常說的「雙指標」,當我們需要列舉陣列中的兩個元素時,如果我們發現隨著第乙個元素的遞增,第二個元素是遞減的,那麼就可以使用雙指標的方法,將列舉的時間複雜度從 o(n^2) 減少至 o(n)。為什麼是 o(n) 呢?這是因為在列舉的過程每一步中,「左指標」會向右移動乙個位置(也就是題目中的 bb),而「右指標」會向左移動若干個位置,這個與陣列的元素有關,但我們知道它一共會移動的位置數為 o(n),均攤下來,每次也向左移動乙個位置,因此時間複雜度為 o(n)。
注意到我們的偽**中還有第一重迴圈,時間複雜度為 o(n),因此列舉的總時間複雜度為 o(n^2)。由於排序的時間複雜度為 o(n log n),在漸進意義下小於前者,因此演算法的總時間複雜度為 o(n^2)。
上述的偽**中還有一些細節需要補充,例如我們需要保持左指標一直在右指標的左側(即滿足 b≤c),具體可以參考下面的**,均給出了詳細的注釋。
class solution
// c 對應的指標初始指向陣列的最右端
int third = n - 1;
int target = -nums[first];
// 列舉 b
for (int second = first + 1; second < n; ++second)
// 需要保證 b 的指標在 c 的指標的左側
while (second < third && nums[second] + nums[third] > target)
// 如果指標重合,隨著 b 後續的增加
// 就不會有滿足 a+b+c=0 並且 blist = new arraylist();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);}}
}return ans;
}}
15 三數之和 3Sum
給定乙個包含 n 個整數的陣列nums,判斷nums中是否存在三個元素 a,b,c 使得 a b c 0 找出所有滿足條件且不重複的三元組。注意 答案中不可以包含重複的三元組。例如,給定陣列 nums 1,0,1,2,1,4 滿足要求的三元組集合為 1,0,1 1,1,2 線性複雜度 class s...
leetcode 15 三數之和 3Sum
給定乙個包含 n 個整數的陣列nums,判斷nums中是否存在三個元素 a,b,c 使得 a b c 0 找出所有滿足條件且不重複的三元組。注意 答案中不可以包含重複的三元組。例如,給定陣列 nums 1,0,1,2,1,4 滿足要求的三元組集合為 1,0,1 1,1,2 原題鏈結 這題可以使用雜湊...
15 3Sum 求三數之和
給你乙個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a,b,c 使得 a b c 0 請你找出所有滿足條件且不重複的三元組。注意 答案中不可以包含重複的三元組。示例 給定陣列 nums 1,0,1,2,1,4 滿足要求的三元組集合為 1,0,1 1,1,2 如果是陣列是有序...