LeetCode216.組合總和lll

Tomorrowland_D發表於2024-08-14

4.組合總和lll(LeetCode216)

題目敘述:

找出所有相加之和為 nk 個數的組合,且滿足下列條件:

  • 只使用數字1到9
  • 每個數字 最多使用一次

返回 所有可能的有效組合的列表 。該列表不能包含相同的組合兩次,組合可以以任何順序返回。

示例 1:

輸入: k = 3, n = 7
輸出: [[1,2,4]]
解釋:
1 + 2 + 4 = 7
沒有其他符合的組合了。

示例 2:

輸入: k = 3, n = 9
輸出: [[1,2,6], [1,3,5], [2,3,4]]
解釋:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
沒有其他符合的組合了。

示例 3:

輸入: k = 4, n = 1
輸出: []
解釋: 不存在有效的組合。
在[1,9]範圍內使用4個不同的數字,我們可以得到的最小和是1+2+3+4 = 10,因為10 > 1,沒有有效的組合。

提示:

  • 2 <= k <= 9
  • 1 <= n <= 60

思路:

1.回溯函式的引數以及返回值

  • 沒有返回值

  • 我們需要startindex變數來標誌著我們遍歷的位置

  • 並且需要一個一維陣列path來存放當前的結果,一個二維陣列result來儲存最終的結果集。

  • targetSum(int)目標和,也就是題目中的n。

  • k(int)就是題目中要求k個數的集合。

  • sum(int)為已經收集的元素的總和,也就是path裡元素的總和。

  • startIndex(int)為下一層for迴圈搜尋的起始位置。

程式碼如下:

vector<vector<int>> result;
vector<int> path;
void backtracking(int targetSum, int k, int sum, int startIndex)

2.回溯函式的結束條件

  • 當我們陣列中的元素個數等於k時,就收集到了k個元素,也就是我們滿足題目的條件,此時就應該返回了
  • 並且,如果此時的targetSum=sum了,就是滿足我們題目要求的一個答案,我們就應該收集這個答案了。

程式碼如下:

if(path.size()==k){
    if(sum==targetSum) result.push_back(path);
    return;
}

3.單層遞迴的邏輯

  • 此處我們是選擇1-9的九個數字,因此單層遞迴的for迴圈應該是≤9
for(int i=startindex;i<=9;i++)
  • 處理過程就是 path收集每次選取的元素,相當於樹型結構裡的邊,sum來統計path裡元素的總和

  • 並且,我們還需要有回溯的過程,只要加了的話,我們要回到上一層,重新選擇的話,此時我們就應該減回來

程式碼如下:

for(int i=startindex;i<=9;i++){
    path.push_back(i);
    sum+=i;
    //此時這裡是i+1,因為元素不能夠重複取
    backtracking(targetSum,k,sum,i+1);
    //這裡是回溯的邏輯,在之前加過的元素,如果我們要回溯到上一層再操作,就要減回來
    sum-=i;
    path.pop_back();
}
  • 其實,我們也不需要對sum做加減的操作,直接對傳入的引數進行操作,這樣相當於是將回溯的邏輯隱藏在了遞迴函式中,不推薦這種做法。
for(int i=startindex;i<=9;i++){
    path.push_back(i);
    //此時這裡是i+1,因為元素不能夠重複取
    backtracking(targetSum,k,sum+i,i+1);
    path.pop_back();
}

程式碼:

  • 我們根據上面三步的分析,不難得出程式碼:
class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(int targetSum, int k, int startindex, int sum) {
        if (path.size() == k) {
            if (sum == targetSum)
                result.push_back(path);
            return;
        }
        for (int i = startindex; i <= 9; i++) {
            sum += i;
            path.push_back(i);
            backtracking(targetSum, k, i + 1, sum);
            //回溯的過程
            path.pop_back();
            sum -= i;
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        //從1開始選
        backtracking(n, k, 1, 0);
        return result;
    }
};

剪枝:

  • 在這段程式碼中,我們可以有兩個剪枝的過程。
  1. 第一就是如果我們發現當前的sum已經大於targetSum了,那麼我們就沒有必要就再往下進行遍歷了,肯定沒有符合條件的k個數相加為n。
    void backtracking(int targetSum, int k, int startindex, int sum) {
        //發現sum已經大於targetSum了,直接結束本層遞迴
        if(sum>targetSum) return;
        if (path.size() == k) {
            if (sum == targetSum)
                result.push_back(path);
            return;
        }
        for (int i = startindex; i <= 9; i++) {
            sum += i;
            path.push_back(i);
            backtracking(targetSum, k, i + 1, sum);
            //回溯的過程
            path.pop_back();
            sum -= i;
        }
    }
  1. 第二就是單層遞迴的條件,我們沒有必要從startindex遍歷到9,只需要遍歷到9-(k-path.size())+1即可
  • 為什麼只需要遍歷到9-(k-path.size())+1就可以了呢?這題的邏輯其實和我們之前講解的LeetCode77組合總和有異曲同工之妙,我們陣列中的元素個數為path.size()個,我們還需要選擇k-path.size()個元素
  • 又因為我們是從i開始,樹的最底層,也就是遞迴的深度,是遍歷到9結束,那麼這個區間內的元素個數必須≥k-path.size()
  • 這個區間的元素個數為:9-i+1,那麼9-i+1>=k-path.size(),可以推出i<=9-(k-path.size())+1,就是說i至多從這個位置開始遍歷

剪枝過程圖如下:

216.組合總和III1

  • 本文借閱了程式碼隨想錄的原圖,想要更加深入瞭解的可以去觀看原作者所發的文章!

    [程式碼隨想錄 (programmercarl.com)]()

相關文章