Leetcode刷题笔记Day12:回溯Ⅱ

32 阅读4分钟

组合总和

  • 力扣题目链接
  • 组合问题需要startIndex定位,即使有重复选取也一样!
  • 直接给出剪枝版答案:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, 
                  int target, int sum, int index) {
    if(sum>target) return;
    if(sum==target) {result.push_back(path); return;}
    // 如果 sum+candidates[i]>target 就终止遍历
    for(int i=index; i<candidates.size() && 
        sum+candidates[i]<=target; i++){
        sum+=candidates[i];
        path.push_back(candidates[i]);
        // 关键点:不用i+1了,表示可以重复读取当前的数
        backtracking(candidates, target, sum, i);
        sum-=candidates[i];
        path.pop_back();
    }
}
vector<vector<int>> combinationSum(
    vector<int>& candidates, int target) {
    // 需要排序
    sort(candidates.begin(), candidates.end());
    backtracking(candidates, target, 0, 0);
    return result;
}

组合总和II

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, 
                  int sum, int index){
    if(target==sum) {result.push_back(path); return;}
    for(int i=index; i<candidates.size() 
        && sum+candidates[i]<=target; i++){
        sum+=candidates[i];
        path.push_back(candidates[i]);
        backtracking(candidates, target, sum, i+1);
        sum-=candidates[i];
        path.pop_back();
    }
}
vector<vector<int>> combinationSum2(
    vector<int>& candidates, int target) {
    sort(candidates.begin(), candidates.end());
    backtracking(candidates, target, 0, 0);
    return result;
}
  • 正确写法(利用used数组):

40.组合总和II1

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, 
                  int sum, int index, vector<bool>& used) {
    if(target==sum) {result.push_back(path); return;}
    for(int i=index; i<candidates.size() 
        && sum+candidates[i]<=target; i++) {
        // used[i-1]==true,说明同一树枝candidates[i-1]使用过
        // used[i-1]==false,说明同一树层candidates[i-1]使用过
        // 要对同一树层使用过的元素进行跳过
        if(i>0 && candidates[i]==candidates[i-1] 
           && used[i-1]==false) continue;
        sum+=candidates[i];
        path.push_back(candidates[i]);
        used[i]=true;
        backtracking(candidates, target, sum, i+1, used);
        used[i]=false;
        sum-=candidates[i];
        path.pop_back();
    }
}
vector<vector<int>> combinationSum2(
    vector<int>& candidates, int target) {
    sort(candidates.begin(), candidates.end());
    vector<bool> used(candidates.size(), false);
    backtracking(candidates, target, 0, 0, used);
    return result;
}
  • 只利用startIndex也可以通过:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, 
                  int sum, int index) {
    if(target==sum) {result.push_back(path); return;}
    for(int i=index; i<candidates.size() 
        && sum+candidates[i]<=target; i++){
        // 要对同一树层使用过的元素进行跳过
        if(i>index && candidates[i]==candidates[i-1]) 
            continue;
        sum+=candidates[i];
        path.push_back(candidates[i]);
        backtracking(candidates, target, sum, i+1);
        sum-=candidates[i];
        path.pop_back();
    }
}
vector<vector<int>> combinationSum2(
    vector<int>& candidates, int target) {
    sort(candidates.begin(), candidates.end());
    backtracking(candidates, target, 0, 0);
    return result;
}

分割回文串

  • 力扣题目链接
  • 我们遇到的第一个分割问题,但切割问题也是组合问题!
  • 回溯+判断回文(注意substr()函数的用法)
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
bool isPalindrome(const string& s, int start, int end){
    for(int i=start, j=end; i<j; i++, j--)  // 双指针法
        if(s[i]!=s[j]) return false;
    return true;
}
void backtracking(const string& s, int index){
    if(index>=s.size()) {result.push_back(path); return;}
    for(int i=index; i<s.size(); i++) {
        if(isPalindrome(s, index, i)) {     //是回文子串
            // 获取[startIndex,i]在s中的子串
            string str=s.substr(index, i-index+1);
            path.push_back(str);
        } else continue; //不需要回溯
        backtracking(s, i+1);
        path.pop_back();
    }
}
vector<vector<string>> partition(string s) {
    backtracking(s, 0);
    return result;
}
  • 动态规划提前算好(提高版):
vector<vector<string>> result;
vector<string> path;                // 放已经回文的子串
vector<vector<bool>> isPalindrome;  // 放事先计算好的回文子串结果
void computePalindrome(const string& s) {
    // isPalindrome[i][j]代表s[i:j](双边包括)是否是回文字串
    isPalindrome.resize(s.size(), vector<bool>(s.size(), false));
    for (int i=s.size()-1; i>=0; i--)
        // 需要倒序计算, 保证在i行时, i+1行已经计算好了
        for(int j=i; j<s.size(); j++) {
            if(j==i) isPalindrome[i][j]=true;
            else if(j-i==1) isPalindrome[i][j]=(s[i]==s[j]);
            // 动态规划核心
            else isPalindrome[i][j]=
                (s[i]==s[j] && isPalindrome[i+1][j-1]);
        }
}
void backtracking(const string& s, int index){
    if(index>=s.size()) {result.push_back(path); return;}
    for(int i=index; i<s.size(); i++) {
        if(isPalindrome[index][i]){ //是回文子串
            // 获取[startIndex,i]在s中的子串
            string str=s.substr(index, i-index+1);
            path.push_back(str);
        } else continue; //不需要回溯
        backtracking(s, i+1);
        path.pop_back();
    }
}
vector<vector<string>> partition(string s) {
    computePalindrome(s);
    backtracking(s, 0);
    return result;
}
  • 总的来说,是一道比较困难的模板题,需要认真吸收!

复原IP地址

  • 同上题一样,判断合法性单独写,回溯步骤基本相同,只是这题确定了点数
  • 注意掌握insert()和erase()函数的用法!
vector<string> result;
bool isValid(const string& s, int start, int end){
    if(start>end || end-start>2) return false;
    // 0开头的数字不合法
    if(s[start]=='0' && start!=end) return false;
    int num=0;
    for(int i=start; i<=end; i++){
        if(s[i]>'9' || s[i]<'0') return false;
        num = num*10 + (s[i]-'0');
        if(num>255) return false;
    }
    return true;
}
void backtracking(string& s, int start, int pointNum){
    if(pointNum==3 && isValid(s, start, s.size()-1)) {
        result.push_back(s); return;
    }
    for(int i=start; i<s.size() && isValid(s, start, i); i++){
        s.insert(s.begin()+i+1, '.'); // 在i后插入
        pointNum++;
        backtracking(s, i+2, pointNum);
        pointNum--;
        s.erase(s.begin()+i+1);
    }
}
vector<string> restoreIpAddresses(string s) {
    // 又来一次剪枝优化
    if(s.size()<4 || s.size()>12) return result;
    backtracking(s, 0, 0);
    return result;
}

子集

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int start){
    result.push_back(path);
    if(start>=nums.size()) return;
    for(int i=start; i<nums.size(); i++){
        path.push_back(nums[i]);
        backtracking(nums, i+1);
        path.pop_back();
    }
}
vector<vector<int>> subsets(vector<int>& nums) {
    backtracking(nums, 0);
    return result;
}

参考资料

[1] 代码随想录

[2] Leetcode题解