https://leetcode-cn.com/problems/combination-sum/
真的没想到这么短的一个程序写了还挺久,而且程序效率还不高。 有很大一部分原因是对 Go 的切片理解不充分,再加上早晨起来大脑比较懵。
对 Go 的切片可以参考这篇文章
程序的主要思路是对各种情况进行递归探索,实际上复杂度已经到了 O(n!),感觉不是很妙。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| func getAllComs(candidates []int, target int, nowPos int, nowSlice *[]int, nowSum int, ret *[][]int) { s:=nowSum+candidates[nowPos] switch { case s==target: *nowSlice=append(*nowSlice,candidates[nowPos]) *ret=append(*ret,*nowSlice) return case s>target: return case s<target: *nowSlice=append(*nowSlice,candidates[nowPos]) for i:=nowPos;i<len(candidates);i++ { ns:=make([]int,len(*nowSlice)) copy(ns,*nowSlice) getAllComs(candidates,target,i,&ns,s,ret) } } } func combinationSum(candidates []int, target int) [][]int { var ret [][]int for i:=0;i<len(candidates);i++ { nowSlice:=make([]int,0) getAllComs(candidates,target,i,&nowSlice,0,&ret) } return ret }
|
看了一下别人的方法,大同小异,但是别人的代码就很快,而且很简洁,在这里学习一下。
他的思路是先排序,然后每次循环当前字符后面的那部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func getAllComs(candidates []int, nowTarget int, nowSlice []int, ret *[][]int) { for i,v:=range candidates { switch { case v==nowTarget: nowSlice=append(nowSlice,candidates[i]) temp:=make([]int,len(nowSlice)) copy(temp,nowSlice) *ret=append(*ret,temp) case v<nowTarget: getAllComs(candidates[i:],nowTarget-v,append(nowSlice,candidates[i]),ret) default: return } } } func combinationSum(candidates []int, target int) [][]int { var ret [][]int sort.Ints(candidates) getAllComs(candidates,target,[]int{},&ret) return ret }
|
下面是一个简单的C++回溯版本,在这个版本中,不必关心每一个元素的顺序,只需要不走回头路即可。但是因为递归调用层次会很多,所以需要时间和空间都比较大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public: void getAll(vector<int>& candidates, int target, vector<vector<int>>& result, vector<int> record, int pos) { if(target < 0) return; if(target == 0) { vector<int> record_copy(record); result.push_back(record_copy); return; } for(int i=pos; i<candidates.size(); ++i) { int c = candidates[i]; record.push_back(c); getAll(candidates, target-c, result, record, i); record.pop_back(); } }
vector<vector<int>> combinationSum(vector<int>& candidates, int target) { vector<vector<int>> result; vector<int> record; getAll(candidates, target, result, record, 0); return result; } };
|
下面是一个优化的版本,先通过sort函数对candidates数组进行排序,然后再行回溯,这时,因为有了大小的假设,就可以在循环中对一些情况进行判断,如果出现问题及时推出循环,减少了很多循环次数,并且不必再次递归调用函数,效率会提升很多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Solution { public: void getAll(vector<int>& candidates, int target, vector<vector<int>>& result, vector<int> record, int pos) { for(int i=pos; i<candidates.size(); ++i) { int c = candidates[i]; int t = target-c; if(t < 0) return; else if(t == 0) { vector<int> record_copy(record); record_copy.push_back(c); result.push_back(record_copy); return; } else { record.push_back(c); getAll(candidates, t, result, record, i); record.pop_back(); }
} }
vector<vector<int>> combinationSum(vector<int>& candidates, int target) { vector<vector<int>> result; vector<int> record; sort(candidates.begin(), candidates.end()); getAll(candidates, target, result, record, 0); return result; } };
|
最后一种解法很有意思,它没有采用回溯的思想,而是采用了dp的思路,方法参考了https://leetcode-cn.com/problems/combination-sum/solution/chao-qiang-gifzhu-ni-shi-yong-dong-tai-gui-hua-qiu/。
首先,使用一个map记录1到target中每一个数的解作为历史记录。然后,通过不断循环(1到target)来得到中间每个target的结果。
在外层循环中还有一个内层循环,内存循环主要寻找candidates里可以插入到当前target对应数组的数据。如果某个candidate和i相等,则说明它可以作为结果数据,这些数据就是起始值。如果i大于某个candidate,则可以寻找i-c,如果找到,就说明i的结果可以用之前的数据构建,就将之前的数据插入record[i]中。
最终,将record[i]中的元素转移到一个vector中返回即可。
需要注意的是去重,这里用set<vector<int>>
以及使用sort(r.begin(), r.end())
都是为了去重。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public: vector<vector<int>> combinationSum(vector<int>& candidates, int target) { unordered_map<int, set<vector<int>>> record; for(int i=1; i<=target; ++i) { for(int c:candidates) { if(i == c) { record[c].insert(vector<int>{c}); } else if(i > c) { for(auto r:record[i-c]) { r.push_back(c); sort(r.begin(), r.end()); if(record[i].count(r)==0) { record[i].insert(r); } } } } } vector<vector<int>> result; for(auto r:record[target]) result.push_back(r); return result; } };
|