15 组合型回溯

162 阅读3分钟

灵神【基础算法精讲】视频的个人笔记。

视频例题

77.组合

image.png

image.png

从根节点往下

  • 第一行是在集合中选一个数的情况
  • 第二行是在集合中选两个数的情况
  • 第三行是在集合中选三个数的情况

这就跟组合问题很像了。

设path长为m,那么还需要选d=k-m个数。 设当前需要从[1,i]这i个数中选数,如果i<d,最后必然无法选出k个数。 不需要继续递归,这是一种剪枝技巧

写法1:选和不选

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    int _k;

    void dfs(int i) {
        int d = _k - path.size(); //还要选a个数
        if(d <= 0) {
            ans.emplace_back(path);
            return;
        }

        //选
        path.emplace_back(i);
        dfs(i - 1);
        path.pop_back(); //恢复

        //不选
        if(i > d) //剩余的数 比 还要选的数 少, 不能不选
            dfs(i - 1);
    }

    vector<vector<int>> combine(int n, int k) {
        _k = k;
        dfs(n);
        return ans;
    }
};

写法2:枚举下一个数选哪一个

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    int _k;

    void dfs(int i) {
      int d = _k - path.size(); //还要选a个数
        if(d <= 0) {
            ans.emplace_back(path);
            return;
        }

        for (int j = i; j > 0; --j) {
            path.emplace_back(j);
            dfs(j - 1);
            path.pop_back(); //恢复

        }
    }

    vector<vector<int>> combine(int n, int k) {
        _k = k;
        dfs(n);
        return ans;
    }
};

216.组合总和 III

image.png image.png

写法1:选和不选

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    int _k, _n;

    void dfs(int i, int sum) {
        if(sum == _n && path.size() == _k) { //符合条件
            ans.emplace_back(path);
            return;
        }

        if(path.size() > _k || i >= 10 || sum > _n) return; //base case

        //选
        path.emplace_back(i);
        dfs(i + 1, sum + i);
        path.pop_back(); //恢复

        //不选
        dfs(i + 1, sum);
    }

    vector<vector<int>> combinationSum3(int k, int n) {
        _k = k; _n = n;
        dfs(1, 0);
        return ans;
    }
};

写法2:枚举下一个数选哪一个

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    int _k, _n;

    void dfs(int i, int sum) {
      if(sum == _n && path.size() == _k) { //符合条件
            ans.emplace_back(path);
            return;
        }

        if(path.size() > _k || sum > _n) return; //base case

        for (int j = i; j < 10; ++j) {
            path.emplace_back(j);
            dfs(j + 1, sum + j);
            path.pop_back(); //恢复
        }
    }

    vector<vector<int>> combinationSum3(int k, int n) {
        _k = k; _n = n;
        dfs(1, 0);
        return ans;
    }
};

22.括号生成

image.png

该题选左括号或者选右括号,两种选择,可以转换成选和不选问题。

写法1:选和不选

class Solution {
public:
    vector<string> ans;
    string path;
    int _n;
    
    void dfs(int i, int leftCnt){
        if(i >= 2 * _n) {
            ans.emplace_back(path);
            return;
        }
    
        //选左括号
        if(leftCnt < _n) { //左括号数小于n
            path[i] = '(';
            dfs(i  + 1, leftCnt + 1);
        }
    
        //选右括号
        if(i - leftCnt < leftCnt) { //右括号数小于左括号数
            path[i] = ')';
            dfs(i  + 1, leftCnt);
        }
    }

    vector<string> generateParenthesis(int n) {
        string s(2 * n, 0);
        path = s;
        _n = n;
        dfs(0, 0);
        return ans;
    }
};

力扣参考灵神题解

选哪一个

class Solution {
public:
    vector<string> ans;
    vector<int> path2;
    int _n;

	// balance = 左括号个数 - 右括号个数
    void dfs(int i, int balance) {
        if(path2.size() == _n) {
            string s(_n * 2, ')');
            for (int j: path2) s[j] = '(';
            ans.emplace_back(s);
            return;
        }

         // 可以填 0 到 balance 个右括号
        for (int j = 0; j <= balance; ++j) { // 填 j 个右括号
            path2.emplace_back(i + j); // 填 1 个左括号
            dfs(i + j + 1, balance - j  + 1);
            path2.pop_back();
        }
    }

    vector<string> generateParenthesis(int n) {
        _n = n;
        dfs(0, 0);
        return ans;
    }
};

课后作业

301.删除无效的括号

image.png

宫水三叶 参考题解

  • score判断左右括号是否合法
  • len记录路径最大长度
  • _max最大括号数,左右括号中的最小值

枚举子串,每个字符选或不选,字母就直接选。

class Solution {
public:
    set<string> ans;
    int n; //字符串长度
    int _max; //最大括号数
    int len; //最大路径子串的长度
    string s;

    /**
     * @param i 位置
     * @param score 得分: 左括号+1, 右括号-1
     * @param path 路径
     */
    void dfs(int i, int score, string path) {
        if(score < 0 || score > _max) return; //得分小于0 或 左括号数大于最大括号数, 不符合要求
        
        if(i == n) {
            if(score == 0 && path.size() >= len) { //得分平衡 且 path大于等于最大长度
                if(path.size() > len) { //大于等于最大长度, 重置
                    len = path.size();
                    ans.clear(); //清空
                }
                ans.insert(path);
            }
            return;
        }
        
        char c = s[i];
        if(c != '(' && c != ')') //字母直接选
            dfs(i + 1, score, path + c);
        else {
            dfs(i + 1, score + (c == '(' ? 1 : -1), path + c); //不删
            dfs(i + 1, score, path); //删除
        }
    }

    vector<string> removeInvalidParentheses(string _s) {
        n = _s.size();
        s = _s;

        //最大括号数 是 左右括号的最小值
        int l = 0, r = 0;
        for(char c: s) {
            if(c == '(') l++;
            else if(c == ')') r++;
        }
        _max = min(l, r);

        dfs(0, 0, "");
        return vector<string>(ans.begin(), ans.end());
    }
};

通过预处理,得到最后的「应该删除的左括号数量」和「应该删掉的右括号数量」,来直接得到最终的 len,多增加一层剪枝。

class Solution {
public:
    set<string> ans;
    int n; //字符串长度
    int _max; //最大括号数
    int len; //最大路径子串的长度
    string s;

    void dfs2(int i, int score, string path, int l, int r) {
        if(l < 0 || r < 0 || score < 0 || score > _max) return; //删多了 或 得分小于0 或 左括号数大于最大括号数, 不符合要求
        if(l == 0 && r == 0 && path.size() == len) ans.insert(path);
        if(i == n) return;

        char c = s[i];
            
        if(c == '(') {
            dfs2(i + 1, score + 1, path + c, l, r); //不删
            dfs2(i + 1, score, path, l - 1, r); //删除
        } else if(c == ')') {
            dfs2(i + 1, score - 1, path + c, l, r); //不删
            dfs2(i + 1, score, path, l, r - 1); //删除
        } else {//字母直接选
            dfs2(i + 1, score, path + c,  l, r);
        }
    }

    vector<string> removeInvalidParentheses(string _s) {
        n = _s.size();
        s = _s;

        //最大括号数 是 左右括号的最小值
        int l = 0, r = 0;
        int removeL = 0, removeR = 0;
        for(char c: s) {
            if(c == '(') {
                l++;
                removeL++;
            }
            else if(c == ')') {
                r++;
                if(removeL != 0) removeL--;
                else removeR++;
            }
        }
        len = n - removeL - removeR;
        _max = min(l, r);

        dfs2(0, 0, "", removeL, removeR);
        return vector<string>(ans.begin(), ans.end());
    }
};