1 題目描述

找到所有可以使三個數字的和為零的不重複組合。

2 解法

看到不重複,腦子第一個冒出來是set。應該要把所有的組合都放進去,然後再轉成list。

而這題是two pointer卻要找3個數字的和為0,而two pointer使用的前提最好是==排序==過的數組。我們可以透過固定一個數字,然後使用two pointer的方式找到另外兩個數字。

2.1 Hash Set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
==== Step 1 ====
# nums 排序過
# i 是固定的數值
# l, r 分別是 left pointer 和 right pointer
[-4,-1,-1,0,1,2]
i l r
# 因為還太小,我們透過移動l來使總合變大
nums[i] + nums[l] + nums[r] = -3

==== Step 2 ====
[-4,-1,-1,0,1,2]
i l r
# 因為還太小,我們透過移動l來使總合變大
nums[i] + nums[l] + nums[r] = -3

==== Step 3 ====
[-4,-1,-1,0,1,2]
i l r
# 因為還太小,我們透過移動l來使總合變大
nums[i] + nums[l] + nums[r] = -2

==== Step 4 ====
[-4,-1,-1,0,1,2]
i l r
# 因為還太小,我們透過移動l來使總合變大
nums[i] + nums[l] + nums[r] = -1

==== Step 5 ====
# 觸底了...都還沒找到,表示當前的i無法找到解
[-4,-1,-1,0,1,2]
i r
l

nums[i] + nums[l] + nums[r] = -1

基本上從上面的程式碼就可以寫出以下程式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
ans = set() # 存放所有的組合 避免重複
nums.sort() # 排序
target = 0
# 固定i
for i in range(len(nums)):
l, r = 0, len(nums)-1
while l < r:
tot = nums[i] + nums[l] + nums[r]
if tot == target:
ans.add((nums[i], nums[l], nums[r]))
l += 1
r -= 1
elif tot < target:
l += 1
else:
r -= 1
return list(ans) # 轉成list

2.2 List

如果我們今天不想使用set,我們就要避免重複的組合,通常重複的組合怎麼出現的?如果我們今天的數組是[-4,-1,-1,-1,1,2]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
==== Step 1 ====
# 今天換下一個i
[-4,-1,-1,-1,1,2]
i l r
nums[i] + nums[l] + nums[r] = 0 # [-1, -1, 2] 找到

==== N Step ====
[-4,-1,-1,-1,1,2]
i l r
# 當我們發現l與上一個l是一樣的時候,我們就要跳過
nums[i] + nums[l] + nums[r] = 0 # [-1, -1, 2] 找到

==== N+1 Step ====
[-4,-1,-1,-1,1,2]
i l r
# l 應該從與上一個不同時開始
nums[i] + nums[l] + nums[r] = 2

基本上從上面的程式碼就可以寫出以下程式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
for idx in range(len(nums)):
# i 也是相同的意思,如果與上一個相同就跳過避免重複
if idx > 0 and nums[idx] == nums[idx-1]:
continue

left, right = idx + 1, len(nums) - 1
while left < right:
total = nums[idx] + nums[left] + nums[right]
if total > 0:
right -= 1
elif total < 0:
left += 1
else:
res.append([nums[idx], nums[left], nums[right]])
left += 1
# 使用list不重複的關鍵在,如果left下一個與上一個相同要繼續往下走否則會重複
while nums[left] == nums[left-1] and left < right:
left += 1
return res

3 總結

這題我當初卡的點有兩個

  • 怎麼把set換成list,沒想到list(set)就可以了
  • 怎麼避免重複的組合,我沒想到要檢查i跟l是否與上一個相同,如果相同要一直往下走。