4.組合總和lll(LeetCode216)
題目敘述:
找出所有相加之和為 n
的 k
個數的組合,且滿足下列條件:
- 只使用數字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;
}
};
剪枝:
- 在這段程式碼中,我們可以有兩個剪枝的過程。
- 第一就是如果我們發現當前的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;
}
}
- 第二就是單層遞迴的條件,我們沒有必要從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至多從這個位置開始遍歷
剪枝過程圖如下:
-
本文借閱了程式碼隨想錄的原圖,想要更加深入瞭解的可以去觀看原作者所發的文章!
[程式碼隨想錄 (programmercarl.com)]()