Combination Sum III

題目描述

找出所有用 K 個 1-9 的數字(不重複使用)使其和為 N 的組合。

輸入:k = 3, n = 7
輸出:[[1,2,4]]
解釋:1+2+4=7,唯一的組合

輸入:k = 3, n = 9
輸出:[[1,2,6],[1,3,5],[2,3,4]]

解題思路

1

定義搜尋空間

候選數字固定為 1-9,每個最多選一次。搜尋空間是 C(9,k) 種組合

2

回溯框架

backtrack(remain, start, path):remain 是剩餘目標和,start 是本層起始數字,path 是當前已選數字

3

剪枝條件

path 長度已達 k → 檢查 remain 是否為 0;i > remain → break(後面數字更大不可能湊出)

4

收集結果

path.length == k 且 remain == 0 → 複製 path 加入結果集。兩條件缺一不可

5

避免重複

遞迴時傳 start = i+1,保證後選數字嚴格大於先選,避免 [1,2,4] 和 [2,1,4] 重複

程式碼

C#

csharp
public class Solution {
    public IList<IList<int>> CombinationSum3(int k, int n) {
        var result = new List<IList<int>>();
        Backtrack(k, n, 1, new List<int>(), result);
        return result;
    }
    
    private void Backtrack(int k, int remain, int start,
                           List<int> path, List<IList<int>> result) {
        if (path.Count == k) {
            if (remain == 0) result.Add(new List<int>(path));
            return; // 不管是否等於 0 都要 return,已經選了 k 個
        }
        for (int i = start; i <= 9; i++) {
            if (i > remain) break; // 剪枝:當前數字已超過剩餘和
            path.Add(i);
            Backtrack(k, remain - i, i + 1, path, result);
            path.RemoveAt(path.Count - 1); // 回溯
        }
    }
}

TypeScript

typescript
function combinationSum3(k: number, n: number): number[][] {
    const result: number[][] = [];
    
    function backtrack(remain: number, start: number, path: number[]) {
        if (path.length === k) {
            if (remain === 0) result.push([...path]);
            return;
        }
        for (let i = start; i <= 9; i++) {
            if (i > remain) break; // 剪枝
            path.push(i);
            backtrack(remain - i, i + 1, path);
            path.pop(); // 回溯
        }
    }
    
    backtrack(n, 1, []);
    return result;
}

Python

python
def combinationSum3(k: int, n: int) -> list[list[int]]:
    result = []
    
    def backtrack(remain: int, start: int, path: list[int]):
        if len(path) == k:
            if remain == 0:
                result.append(path[:])  # 複製一份加入結果
            return
        for i in range(start, 10):
            if i > remain:  # 剪枝:後面的數字更大,不可能湊出
                break
            path.append(i)
            backtrack(remain - i, i + 1, path)
            path.pop()  # 回溯
    
    backtrack(n, 1, [])
    return result

複雜度分析

| | Time | Space | |--|------|-------| | 本解法 | O(C(9,k)) | O(k) |

C(9,k) 是從 9 個數字中選 k 個的組合數,最多 C(9,4) = 126。

圖解

開始 k=3, n=7
i=1
選 1, 剩餘 6
i=2
選 2, 剩餘 4
i=4 → 答案
選 4, 剩餘 0 ✓
選 3, 剩餘 3
剪枝
下一個 4>3 剪枝

圖表展示了回溯搜尋樹的實際走訪過程:選 1→2→4 時 remain=0 找到答案 [1,2,4]。 選 1→3 後剩餘 3,下一個數字 4 > 3,直接 break 剪枝,避免無效搜尋。

進階觀點

💡面試追問

  • 和 Combination Sum 系列的差別? 39 題允許重複使用同一數字(遞迴傳 i 不是 i+1);40 題有重複元素需跳過同層相同值;本題數字固定 1-9 且不重複,是最單純的版本。
  • 剪枝用 break 而不是 continue 的原因? 因為迴圈是 i 從 start 到 9 遞增的。如果 i > remain,那 i+1, i+2... 更大,加上去只會讓 remain 變更負,全都不可能成功,所以直接 break 結束整層。
  • 能再加什麼剪枝? 檢查「剩餘 k - path.length 個數字能湊出的最大和」是否 ≥ remain。最大和 = 9 + 8 + ... 取最後幾個,如果連最大都不夠就提前 return。

邊界情況

理解測驗

🤔 為什麼遞迴時下一層從 i+1 開始而不是從 1 開始?

🤔 剪枝 if (i > remain) break 的效果是什麼?

你可能也想看

17. Letter Combinations of a Phone Number1137. N-th Tribonacci Number

按 ← → 鍵切換課程