LeetCode hot100@回溯

More content力扣题记之回溯

回溯是递归的副产物,只要有递归就会有回溯

回溯算法模板:回溯递归是往深处找,内部for遍历是横向找

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

其中,回溯有以下两种方式:

①明确修改值,再撤销修改

path.push_back(i);
sum -= i;
depth++;
backTracking(sum, depth);
sum += i;
depth--;
path.pop_back()

②以传递参数的形式修改,递归函数内使用的是新的变量值,外部变量值未被修改,相当于回溯了

path.push_back(i);
backTracking(sum - i, depth + 1); //注意depth+1一定不要写成depth++,不然值就改变了
path.pop_back()

回溯算法可解决的问题类型:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

46. 全排列

用一个数组来记录nums下标的状态,即记录下标对应的元素是否被用过(比如flags[2] = 1代表nums[2]被使用过了)

ps:为什么不直接记录元素是否被用过?因为元素中有负数,下标都是非负数,所以用下标来记录下标,反正nums数组的下标和元素对应关系不会变

class Solution {
private:
vector<vector<int>> res;
vector<int> path;
vector<int> flags;
public:
void backTracking(vector<int>& nums) {
if(path.size() == nums.size()) {
res.push_back(path);
return ;
}

for(int i = 0; i < nums.size(); i++) {
if(flags[i]) continue;
path.push_back(nums[i]);
flags[i] = 1;
backTracking(nums);
path.pop_back();
flags[i] = 0;
}
}
vector<vector<int>> permute(vector<int>& nums) {
flags.resize(nums.size(), 0);
backTracking(nums);

return res;
}
};

78. 子集

控制一下长度即可

class Solution {
private:
vector<vector<int>> res;
vector<int> path;

public:
void backTracking(vector<int>& nums, int startIndex, int len) {
if(path.size() == len) {
res.push_back(path);
return ;
}

for(int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]);
backTracking(nums, i + 1, len);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
res.push_back({});
res.push_back(nums);

for(int i = 1; i < nums.size(); i++) {
backTracking(nums, 0, i);
}

return res;
}
};

17. 电话号码的字母组合

定义全局变量,精简递归函数参数

class Solution {
private:
    vector<string> res;
    string path;
    int n;
    unordered_map<char, string> mp;
public:
    void backtracking(string digits, int depth) {
        if(path.size() == n) {
            res.push_back(path);
            return ;
        }

        for(const char &i : mp[digits[depth]]) {
            path += i;
            backtracking(digits, depth + 1);
            path.erase(path.size() - 1, 1);
        }
    }
    vector<string> letterCombinations(string digits) {
        if(!digits.size()) return res;
        
        mp['2'] = "abc";
        mp['3'] = "def";
        mp['4'] = "ghi";
        mp['5'] = "jkl";
        mp['6'] = "mno";
        mp['7'] = "pqrs";
        mp['8'] = "tuv";
        mp['9'] = "wxyz";

        n = digits.size();
        backtracking(digits, 0);

        return res;
    }
};

39. 组合总和

target递减,由两个参数变为一个参数

class Solution {
private:
    vector<vector<int>> res;
    vector<int> path;
public:
    void backtracking(vector<int>& candidates, int sub_target, int startIndex) {
        if(sub_target < 0) return ; //和超出目标了,作废
        if(!sub_target) { //和正好等于目标值,记录
            res.push_back(path);
            return ;
        }

        for(int i = startIndex; i < candidates.size(); i++) {
            path.push_back(candidates[i]);
            backtracking(candidates, sub_target - candidates[i], i); //允许重复选取当前元素,但不能回到之前的元素
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0);

        return res;
    }
};

22. 括号生成

一个字符一个字符地递归着往后搜,每个字符只有两种可能性

先搜出来再检测合法性

class Solution {
private:
    vector<string> res;
    string path = "("; //第一个字符必是左括号
public:
    void backTracking(int n) {
        if(path.size() == n * 2) {
            if(checkValid(path)) res.push_back(path);
            return ;
        }

        path += '(';
        backTracking(n);
        path.erase(path.size() - 1, 1);

        path += ')';
        backTracking(n);
        path.erase(path.size() - 1, 1);
    }

    bool checkValid(string s) { 
        stack<char> sk;
        for(const char &c : s) {
            if(c == ')' && !sk.empty() && sk.top() == '(') sk.pop();
            else sk.push(c);
        }
        if(sk.empty()) return true;
        return false;
    }

    vector<string> generateParenthesis(int n) {
        backTracking(n);
        return res;
    }
};

79. 单词搜索

一个格子一个格子地递归回溯着搜,遍历四个方向

不用存储答案,遇到直接返回,因此递归函数返回值为bool

class Solution {
private:
    int m, n; //m行n列
public:
    bool backTracking(vector<vector<char>>& board, vector<vector<bool>> &used, string word, int row, int col, int index) {
        if(index == word.size()) return true;

        //遍历四个方向
        if(row != 0 && board[row - 1][col] == word[index] && !used[row - 1][col]) { //上
            used[row - 1][col] = true;
            if(backTracking(board, used, word, row - 1, col, index + 1)) return true; //一旦搜到答案,立刻返回 
            used[row - 1][col] = false;
        }
        if(row != m - 1 && board[row + 1][col] == word[index] && !used[row + 1][col]) { //下
            used[row + 1][col] = true;
            if(backTracking(board, used, word, row + 1, col, index + 1)) return true; //一旦搜到答案,立刻返回 
            used[row + 1][col] = false;
        }
        if(col != 0 && board[row][col - 1] == word[index] && !used[row][col - 1]) { //左
            used[row][col - 1] = true;
            if(backTracking(board, used, word, row, col - 1, index + 1)) return true; //一旦搜到答案,立刻返回 
            used[row][col - 1] = false;
        }
        if(col != n - 1 && board[row][col + 1] == word[index] && !used[row][col + 1]) { //右
            used[row][col + 1] = true;
            if(backTracking(board, used, word, row, col + 1, index + 1)) return true; //一旦搜到答案,立刻返回 
            used[row][col + 1] = false;
        }

        return false; //四个方向都搜寻无果,返回false                    
    }
    bool exist(vector<vector<char>>& board, string word) {
        m = board.size();
        n = board[0].size();

        vector<vector<bool>> used(m, vector<bool>(n, false)); //初始化标记数组

        //搜到首字母进入递归
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(board[i][j] == word[0]) {
                    used[i][j] = true;
                    if(backTracking(board, used, word, i, j, 1)) return true;
                    used[i][j] = false; //虽然这个首字母没走到最后,但可能别人还需要它
                }
            }
        }

        return false;

    }
};

131. 分割回文串

93. 复原 IP 地址思路一样(遍历回溯切割 + 检测非法字段) 但更简单一些

class Solution {
private:
    vector<vector<string>> res;
    vector<string> path;
public:
    void backTracking(string s, int startIndex) {
        if(s.size() == startIndex) {
            res.push_back(path);
            return ;
        }
  
        for(int i = 1; startIndex + i <= s.size(); i++) { //遍历要切的位数    
            if(!checkPalindrome(s, startIndex, i)) continue;
            path.push_back(s.substr(startIndex, i));
            backTracking(s, startIndex + i);
            path.pop_back();
        }
    }

    bool checkPalindrome(string s, int beginIndex, int len) {
        int l = beginIndex;
        int r = beginIndex + len - 1;
        while(l <= r) {
            if(s[l++] != s[r--]) return false;
        }
        return true;
    }

    vector<vector<string>> partition(string s) {
        backTracking(s, 0);
        // if(checkPalindrome(s, 0, 3)) cout << "yes";
        // else cout << "no";
        return res;
    }
};

51. N 皇后

思路没问题,先按行搜出所有方案,再对列、撇、捺进行合法性检查,但没想出撇和捺的检查公式,缺点是搜出所有方案在筛选,效率低

在搜的过程中就检查合法性,非法的直接跳过

class Solution {
private:
    vector<vector<string>> res;
public:
    void backTracking(vector<string> &tep, int n, int row) {
        if(row == n) {
            res.push_back(tep); 
            return ;
        }

        for(int col = 0; col < n; col++) { //只遍历当前行的每列,遍历行递归实现
            if(checkValid(row, col, tep, n)) { 
                tep[row][col] = 'Q';
                backTracking(tep, n, row + 1); //row相当于深度
                tep[row][col] = '.'; //回溯,撤销Q
            }
        }
    }

    bool checkValid(int row, int col, vector<string> &tep, int n) {
        for(int i = 0; i < n; i++) { //检查列
            if(tep[i][col] == 'Q') return false;
        }
      
        for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { //检查捺
            if(tep[i][j] == 'Q') {
                return false;
            }
        }

        for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { //检查撇
            if(tep[i][j] == 'Q') {
                return false;
            }
        }

        return true;
    }
    
    vector<vector<string>> solveNQueens(int n) {
        vector<string> tep(n, string(n, '.')); //初始化棋盘,等效于下面数行
        // vector<string> tep;
        // for(int i = 0; i < n; i++) {
        //     string s;
        //     for(int j = 0; j < n; j++) {
        //         s += '.';
        //     }
        //     tep.push_back(s);
        // }
        backTracking(tep, n, 0);
        
        return res;
    }
};


不准投币喔 👆

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇