递归/回溯:Combination Sum II数组之和
問題如下:
已知一組數(其中有重復元素),求這組數可以組成的所有子集中,子 集中的各個元素和為整數target的子集,結果中無重復的子集。
例如: nums[] = [10, 1, 2, 7, 6, 1, 5], target = 8
結果為: [[1, 7], [1, 2, 5], [2, 6], [1, 1, 6]]
同樣之前有類似的相關的問題遞歸/回溯:Subsets II求子集(有重復元素),最終將子集中和為8的集合輸出,同樣不能有重復子集
一個直接的辦法就是在輸出最終子集的時候,使用之前類似的問題解決過程,將初始篩選的集合做一個計算,檢查是否滿足target 為8的要求,滿足則返回。
類似如下:
std::vector<std::vector<int> > get_subsets(std::vector<int> &num ,int target) {std::vector<int> item;std::vector<std::vector<int>> result;std::set<std::vector<int> > uniq_result;sort(num.begin(), num.end());result.push_back(item);generate(0, num, item, result, uniq_result);/*對遞歸回溯獲取到的結果進行計算,將滿足要求的和為target的子集篩選出來返回*/int sum; std::vector<std::vector<int>> target_result;for (int i = 0; i< result.size(); ++i){sum = 0;for (int j = 0; j < result[i].size(); ++j) {sum += result[i][j];}if (sum == target) {target_result.push_back(result[i]);}}return target_result;
}
但是一個很嚴重的問題,遞歸/回溯本身是2^n的復雜度,如果仍然按照以上的方法對所有元素篩選一遍之后再返回顯然時間復雜度極高,并且在回溯過程中沒有應用到要求的target條件
提出如下方法,在回溯的過程中進行計算,當計算過程中有超過target的集合或者元素,即可終止繼續遞歸,直接回溯,防止沒有意義的遞歸下去。
類似[10,1,2,3,5],其中包含10的子集顯然沒有必要加入到最終的集合,因為10已經大于target了
實現算法如下:
oid generate(int i, std::vector<int> &num,std::vector<int> &item,std::vector<std::vector<int> > &result,std::set<std::vector<int> > &uniq_result,int sum, int target) {/*當結果大于target,直接返回,沒有必要繼續遞歸*/if (sum > target || i >= num.size()) {return;}sum += num[i];item.push_back(num[i]);/*滿足結果為target,且不是重復子集,則加入到最終的結果中*/if (sum == target && uniq_result.find(item) == uniq_result.end()) {result.push_back(item);uniq_result.insert(item);}generate(i + 1, num, item, result, uniq_result, sum, target);item.pop_back();sum -= num[i];generate(i + 1, num, item, result, uniq_result, sum, target);
}std::vector<std::vector<int> > get_combiname_sets(std::vector<int> &num, int target) {std::vector<int> item;std::vector<std::vector<int>> result; //存儲最終的子集std::set<std::vector<int> > uniq_result; //集合,篩選重復子集sort(num.begin(),num.end());generate(0, num, item, result, uniq_result, 0, target);return result;
}
測試代碼如下:
nums[] = [10, 1, 2, 7, 6, 1, 5], target = 8
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>/*
已知一組數(其中有重復元素),求這組數可以組成的所有子集中,子 集中的各個元素和為整數target的子集,結果中無重復的子集。
例如: nums[] = [10, 1, 2, 7, 6, 1, 5], target = 8
結果為: [[1, 7], [1, 2, 5], [2, 6], [1, 1, 6]]
*/using namespace std;void generate(int i, std::vector<int> &num,std::vector<int> &item,std::vector<std::vector<int> > &result,std::set<std::vector<int> > &uniq_result,int sum, int target) {if (sum > target || i >= num.size()) {return;}sum += num[i];item.push_back(num[i]);if (sum == target && uniq_result.find(item) == uniq_result.end()) {result.push_back(item);uniq_result.insert(item);}generate(i + 1, num, item, result, uniq_result, sum, target);item.pop_back();sum -= num[i];generate(i + 1, num, item, result, uniq_result, sum, target);
}std::vector<std::vector<int> > get_combiname_sets(std::vector<int> &num, int target) {std::vector<int> item;std::vector<std::vector<int>> result;std::set<std::vector<int> > uniq_result;sort(num.begin(),num.end());generate(0, num, item, result, uniq_result, 0, target);return result;
}int main(int argc, char const *argv[])
{std::vector<int> num;std::vector<int> item;std::vector<std::vector<int>> result;int tmp;int N;std::cin >> N;for (int i = 0;i < N; ++i) {std::cin >> tmp;num.push_back(tmp);}int target;cin >> target;result= get_combiname_sets(num,target);for (int i = 0;i < result.size(); ++i) {for (int j = 0;j < result[i].size(); ++j) {std::cout << "[" << result[i][j] << "] ";}std::cout << std::endl;}return 0;
}
輸出如下:
#輸入
7
10 1 2 7 6 1 5
8#結果
[1] [1] [6]
[1] [2] [5]
[1] [7]
[2] [6]
總結:
遞歸回溯的結合,本身是時間復雜度較高的一種算法實現組合,但是如果能夠合理運用給出的篩選條件,能夠極大得縮短時間復雜度,使得代碼更加簡介高效。
總結
以上是生活随笔為你收集整理的递归/回溯:Combination Sum II数组之和的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对于"娶媳妇先看丈母娘"这话如何理解?[
- 下一篇: 6级伤残应赔负多少钱?