目录
LeetCode39.组合总和
给你一个 无重复元素 的整数数组
candidates
和一个目标整数target
,找出candidates
中可以使数字和为目标数target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。对于给定的输入,保证和为
target
的不同组合数少于150
个。
思路:之前也做了一个求组合之和,无重复元素,限定目标之和以及元素个数,采用的是回溯。
同样,这道题同样可以采用回溯方法,这道题没有限制个数,同时需要注意后面元素的起始位置可以从自身开始,因为题目中说同一个数字可以无限制重复被选取。
其实相对来说还是比较简单的。
vector<vector<int>> result;//记录最终结果
vector<int> path; //记录每次结果
int sum = 0;//记录每次求和
void backtracking(vector<int>& candidates, int target, int startIndex){
if(sum > target) return;//sum的值大于了target,直接返回
if(sum == target){
result.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size(); i ++){
path.push_back(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, startIndex);
path.pop_back();//开始回溯
sum -= candidates[i];
startIndex ++; //准备从下一个元素开始遍历
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
backtracking(candidates, target, 0);
return result;
}
当然,可以对上面的代码进行优化。
先对要处理的数组排序,然后当sum+candidates[i]>target是,就别进入循环了,因为就算进入递归了也会立即返回。所以优化在于当sum加上本次循环的元素值已经大于target时,就继续下一次循环,这次循环就不进行了。
vector<vector<int>> result;//记录最终结果
vector<int> path; //记录每次结果
int sum = 0;//记录每次求和
void backtracking(vector<int>& candidates, int target, int startIndex){
if(sum == target){
result.push_back(path);
return;
}
//当sum+candidates[i]<=target时可以继续循环,否则进入下一个循环
for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i ++){
path.push_back(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, startIndex);
path.pop_back();//开始回溯
sum -= candidates[i];
startIndex ++; //准备从下一个元素开始遍历
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
sort(candidates.begin(),candidates.end());//需要排序
backtracking(candidates, target, 0);
return result;
}
时间复杂度:O(n*2^n)
空间复杂度:O(target)
LeetCode40.组合总和II
给定一个候选人编号的集合
candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。注意:解集不能包含重复的组合。
思路:前面做过好几道这样的题目了。大体思路一致,但是仍然存在难点。
首先是每个元素只能使用一次,且不能有重复的解。
这里难主要是难在不能有重复的解,但是每个解里面能够存在重复的元素!
如何能够在同一组解里面允许相同元素进入,但是在不同解里面碰到重复元素时能够continue,就是解决本题的关键。
这里提供两种方法。
第一种是设置一个used数组,记录各元素的使用情况,当两值相等时,如果used[i]==true,说明它们是在同一组解里面,只要不超过target,就能加入试试;而两值相等,used[i]==false时,说明前面和它相等的元素的解已经找完了,递归跳出来了,开始尝试递归它了,此时就不能进入循环,而是应该continue,否则递归下去必然就会出现重复解。
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used){
if(sum == target){
result.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i ++){
//当candidates[i]==candidates[i-1]&&used[i-1]==false时,说明是在另一个可能是答案的数组中进行(同树层),
//这时就不允许循环了,因为前面一个相等的元素已经将该元素可能的情况遍历完全了,再遍历的话必然重复
//当candidates[i]==candidates[i-1]&&used[i-1]==true时,说明是在同一个可能是答案的数组中(同树枝),
//这时是允许继续循环的,因为相当于使用前一个相等元素尝试和后面的元素组合起来,尝试找到解
if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) continue;
path.push_back(candidates[i]);
sum += candidates[i];
used[i] = true;
backtracking(candidates, target, sum, i + 1, used);
path.pop_back();//开始回溯
sum -= candidates[i];
used[i] = false;
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);//记录元素使用情况
result.clear();
path.clear();
sort(candidates.begin(), candidates.end());//去重需要进行排序
backtracking(candidates, target, 0, 0, used);
return result;
}
时间复杂度:O(n*2^n)
空间复杂度:O(n)
第二种方法是不设置used数组,而是使用startIndex与下标索引i进行比较,当i>startIndex且前后两值相等时,continue,原因上面也说过了,就是避免出现重复解,同时也保证能够在不超过target的情况下能够将重复元素加入同一组解中尝试构造一组解。
当然,这样避免重复元素,需要首先对待处理数组进行排序,两种方法都是这样的。
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex){
if(sum == target){
result.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i ++){
//这里可以使用startIndex来和i进行比较,判断是否能进入循环,此时不用再设置used数组
if(i > startIndex && candidates[i] == candidates[i - 1]) continue;
path.push_back(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, sum, i + 1);
path.pop_back();//开始回溯
sum -= candidates[i];
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
result.clear();
path.clear();
sort(candidates.begin(), candidates.end());//去重需要进行排序
backtracking(candidates, target, 0, 0);
return result;
}
LeetCode131.分割回文串
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是回文串。返回
s
所有可能的分割方案。
思路:分割回文串,需要解决的主要有两个问题
如何确定分割以及如何判断是否为回文串
从第一个元素分割开始,当是回文串的时候,就加入path,当进入递归时,下标要加1,分割位置也从找到第一个回文串的地方开始设置,后面只要找到了回文串,就将它加入path,并且继续递归,那什么时候停止呢?就在索引到达最后位置时,前面的也就已经划分完成,加入result,开始返回。所以如何确定分割也就完成了。
那么如何判断是否为回文串呢?
这里有两种方法。
第一种是传入字符串以及其实位置,使用双指针,一个从头遍历,一个从尾遍历,判断元素值是否相同。整体代码如下所示。
vector<vector<string>> result;//存放最终结果
vector<string> path;//存放单次结果
void backtracking(string& s, int startIndex){
if(startIndex == s.size()){//当startIndex的大小等于s的大小时,说明已经切割完成
result.push_back(path);
return;
}
for(int i = startIndex; i < s.size(); i ++){
if(isPalindrome(s, startIndex, i)){//判断是否为回文
string str = s.substr(startIndex, i - startIndex + 1);//提取子串
path.push_back(str);
}else{
continue;
}
backtracking(s, i + 1);//这里是i+1,避免分割位置重合
path.pop_back();//回溯
}
}
bool isPalindrome(string& s, int begin, int end){
//使用双指针前后比较元素,若存在不同的元素,则直接返回false
for(int i = begin, j = end; i <= j; i ++, j --){
if(s[i] != s[j]){
return false;
}
}
return true;
}
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
backtracking(s, 0);
return result;
}
时间复杂度:O(n*2^n)
空间复杂度:O(n^2)
第二种是使用动态规划,提前将字符串中的回文串情况统计出来,然后在判断时直接使用下标查询即可。整体代码如下所示。
vector<vector<string>> result;//存放最终结果
vector<string> path;//存放单次结果
vector<vector<bool>> palindrome;//用于存放字符串的回文情况
void backtracking(string& s, int startIndex){
if(startIndex == s.size()){//当startIndex的大小等于s的大小时,说明已经切割完成
result.push_back(path);
return;
}
for(int i = startIndex; i < s.size(); i ++){
if(palindrome[startIndex][i]){//判断是否为回文
string str = s.substr(startIndex, i - startIndex + 1);//提取子串
path.push_back(str);
}else{
continue;
}
backtracking(s, i + 1);//这里是i+1,避免分割位置重合
path.pop_back();//回溯
}
}
void computePalindrome(string& s, vector<vector<bool>>& palindrome){
//palindrome[i][j]表示s[i]到s[j]是否为回文串
palindrome.resize(s.size(), vector<bool>(s.size(), false));
//将palindrome的大小更新为s的大小,使之成为一个s大小的行、列的二维数组,初始值为false
for(int i = s.size() - 1; i >= 0; i --){
//这里需要倒着求是否为回文数,这样能够保证求i行的情况时,能够用到i+1行的内容
for(int j = i; j < s.size(); j ++){
if(i == j){palindrome[i][j] = true;}//当为单个字符的情况
else if(j - i == 1){palindrome[i][j] = (s[i] == s[j]);}//当为两个字符的情况
else palindrome[i][j] = (s[i] == s[j] && palindrome[i + 1][j - 1]);//当为多个字符的情况
}
}
}
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
palindrome.clear();
computePalindrome(s, palindrome);
backtracking(s, 0);
return result;
}
感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。
如果有什么问题欢迎评论区讨论!