演算法 三數之和(3sum)。

2021-10-09 03:36:57 字數 3238 閱讀 1017

給你乙個包含 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 如果是陣列是有序...