---
# System prepended metadata

title: 15. 3Sum 筆記
tags: [15. 3Sum]

---

###### tags: `15. 3Sum`
# 15. 3Sum 筆記

## 題目
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.

Notice that the solution set **must not contain duplicate triplets.**

- 3 <= nums.length <= 3000
- -105 <= nums[i] <= 105

## 思路
題目two sum衍伸過來的, 可以先拿來練習一下

### Two Sum
Leetcode必刷題, 每個人的標配 :D
固定一個值, 找出有沒有剩下的數符合

```swift=
for i in 0..<total-1 {
    for j in i+1..<total {
        if list[i] + list[j] == target {
            return [list[i], list[j]]
        }
    }
}
```

### 3Sum
回到3Sum, 這題是要找出任意三個數相加會為0的所有組合, 會出現多組答案的情況, 需要把Two sum的思路改一下
剛開始也是固定一個數, 找出剩下兩個數相加為remain, 就會是符合的組

但因為剩下的值相加不會限縮, 又題目沒有規定說要按順序吐出
所以可以先sort完再處理

拿[-1,0,1,2,-1,-4]作為範例：
```swift
let list = nums.sort()
/// [-4,-1,-1,0,1,2]
```

再來是固定頭, 因為最後一定會至少有兩個數在後面, 所以頭只需要到count - 3
```swift=
for i in 0..<list.count-2 {
    let remain = -list[i]
    
    // find tuples which sum up with reamin = 0
    ...
}
```

找出剩下的數組可以用Two Sum的雙迴圈來處理, 但有更好的方法
這時候剛開始sort的用處就出現了

當remain 為-1 如果剩餘的兩個相加 > 1我們就可以停止找數組, 直接換到下一個頭, 在往右加都會是更大

這邊可以用binary search的方式來找, 相加太多就限縮, 太少就往右增加
```swift=
var left = i + 1, right = list.count - 1
while left < right {
    let sum = remain + nums[left] + nums[right]
    if sum == 0 {
        result.append([nums[i], nums[left], nums[right]])
        left += 1 // 這組可以, 往右推進找下一組答案

        /// 避免找到重複的
        while nums[left] == nums[left - 1], left < right {
            left += 1
        }
    } else if sum > 0 {
        right -= 1
    } else {
        left += 1
    }
}
```

最後再把上下結合, 就可以解出答案
```swift=
let nums = nums.sorted()
let total = nums.count - 1
var result = [[Int]]()

for i in 0 ..< nums.count - 2 {
    /// 如果頭都是同一個, 就不用再找避免重複
    if i > 0, nums[i] == nums[i - 1] {
        continue
    }

    var left = i + 1, right = total
    while left < right {
        let sum = nums[i] + nums[left] + nums[right]
        if sum == 0 {
            result.append([nums[i], nums[left], nums[right]])
            left += 1

            while nums[left] == nums[left - 1], left < right {
                left += 1
            }
        } else if sum > 0 {
            right -= 1
        } else {
            left += 1
        }
    }
}

return result
```

### 複雜度
- 時間複雜度：O(n^2)
    最外面的那層需要跑n-2次
    裡面的雙指標基本上都會跑完, 但數量會被頭的位置限縮
    平均為n/2 (全跑的話)
    =>  (n-2) * n/2 = O(n^2)
- 空間: O(n)

# 延伸問題
- 如果像是[4Sum](!https://leetcode.com/problems/4sum)問題, 該怎麼處理呢？
- 5Sum? 6Sum? ...