leetcode算法之回溯

今天来盘一盘 **回溯 ** 这类题目

使用python刷题分类整理的笔记,请参考: https://github.com/lxztju/leetcode-algorithm/tree/v1

回溯

回溯法其实就是暴力法。经常使用的暴力法就是多层循环, 但是面对树形结构的话很难采用循环的方式进行遍历。这时就要采用递归回溯的方法实现暴力法。

这类题目看似比较难,但是其实时很简单规范的套路性的代码, 下边几道题会有一个固定的模板套路, 请耐心体会。

  • 17 电话号码的字母组合(medium)
  • 93 复原IP地址(medium)
  • 131 分割回文串(medium)
  • 46 全排列(medium)
  • 47 全排列 II(medium)
  • 77 组合(medium)
  • 39 组合总和(medium)
  • 40 组合总和 II
  • 216 组合总和 III
  • 78 子集(medium)
  • 90 子集II(medium)
  • 79 单词搜索(medicum)
  • 200 岛屿数量(medium)
  • 130 被围绕的区域(medium)
  • 417 太平洋大西洋水流问题(medium)
  • 51 N皇后(hard)
  • 52 N皇后 II(hard)
  • 37 解数独(hard)

17 电话号码的字母组合(medium)

  • 一个树形结构
  • 例如输入的数字为“23
  • 那么这棵树的第一层有三个节点是 abc, 每个节点的下边有三个节点def。遍历这棵树得到所有的路径。
class Solution {
public:
    vector letterCombinations(string digits) {
        // 构建字典映射
        unordered_map character2Num({
            {'2', "abc"},
            {'3', "def"},
            {'4', "ghi"},
            {'5', "jkl"},
            {'6', "mno"},
            {'7', "pqrs"},
            {'8', "tuv"},
            {'9', "wxyz"}
        });
        // 定义返回值
        vector res;
        string path;
        backtrace(digits, res, 0, path, character2Num);
        return res;
    }

    void backtrace(string digits, vector& res, int index, string path, unordered_map& character2Num){
        // 回溯递归函数
        if (digits.empty()) return ;
        // 如果遍历完成所有的数字,就保存得到的字母组合。
        if (index == digits.size()) {
            res.push_back(path);
            return ;
        }
        // 得到某个数字映射得到的字符串
        auto characters = character2Num[digits[index]];
        // 遍历这个字符串,相当于树形结构中的一层
        for (int i = 0; i < characters.size(); i++){
            path += characters[i];
            // 向树的下一层进行遍历
            backtrace(digits, res, index + 1, path, character2Num);
            // 回溯到上一层的时候,需要去除这一层的元素。
            path.pop_back();
        }   
     }
};

93 复原IP地址(medium)

  • 这个题也可以直接四层循环来做, 因为限制了最多分为4部分
  • 这里依然采用回溯搜索法来处理。这种分割字符串的题目,使用回溯法是一个通用的套路。
class Solution {
public:
    vector restoreIpAddresses(string s) {
        vector< string> res;
        string path;

        backtrace(s, res, path, 0, 0);
        return res;
    }
    void backtrace(string& s, vector& res, string path, int cnt, int startindex){
        // cnt表示分割的部分的数目,一共需要分割为4部分, startindex为接下来这一部分的开始位置
        if (cnt == 4){ 
            if (startindex == s.size())
                res.push_back(path);
            return;
        }
        
        for (int i = startindex; i < s.size(); i++){
            // 每一部分的最长长度为3
            if (i - startindex == 3) break;
            auto prepath = path;
            auto substring = s.substr(startindex, i - startindex + 1);
            // 如果长度部位0, 且开头的数字为0, 不合法的ip
            if ( i - startindex > 0 && s[startindex] == '0') break;

            // 如果大于255直接返回。
            if (stoi(substring) > 255 ) break;
            path += substring;
            if (i != s.size() - 1)
                path += ".";
            backtrace(s, res, path, cnt+1, i + 1);
            path = prepath;
        }
    }
};

131 分割回文串(medium)

  • 与上一题思路差不多, 没有上一题那么多需要排除的边界条件。
class Solution {
public:
    vector> partition(string s) {
        // 得到所有的子字符串,判断得到的是否都是回文串。
        vector> res;
        vector palstring;
        string path;

        backtrace(s, res, palstring, 0);
        return res;
    }
    void backtrace(string& s, vector>& res, vector& palstring, int startindex){
        if (startindex == s.size()){
            res.push_back(palstring);
            return ;
        }
        for (int i = startindex; i < s.size(); i++){
            auto substring = s.substr(startindex, i - startindex + 1);
            if ( ! ispalindrome(substring)) continue;
            palstring.push_back(substring);
            backtrace(s, res, palstring, i + 1);
            palstring.pop_back();
        }

    }
    bool ispalindrome(string s){
        int l = 0, r = s.size() - 1;
        while ( l < r){
            if (s[l] != s[r])
                return false;
            l++; r--;
        }
        return true;
    }

};

46 全排列(medium)

  • 依然是一个树形结构,标准的回溯法套路代码
class Solution {
public:
    vector> permute(vector& nums) {
        vector> res;
        vector path;
        unordered_set visited;
        backtrace(nums, res, path, visited, 0);
        return res;
    }
    void backtrace(vector& nums, vector>& res, vector& path, unordered_set& visited, int index){
        if (index == nums.size()){
            res.push_back(path);
            return ;
        }
        for (int i = 0; i< nums.size(); i++){
            if(visited.find(nums[i]) != visited.end()) continue;
            visited.insert(nums[i]);
            path.push_back(nums[i]);
            backtrace(nums, res, path, visited,  index+1);
            visited.erase(nums[i]);
            path.pop_back();
        }
    }
};

47 全排列 II(medium)

  • 与上一题的不同之处在于,这里存在重复的数字
  • 也就是说如果在同一层中出现了相同的数字,那么会出现重复的情况。
  • 采用排序的方式去重。
class Solution {
public:
    vector> permuteUnique(vector& nums) {
        sort(nums.begin(), nums.end());
        vector path;
        vector< vector > res;
        unordered_set visited;
        backtrace(nums, res, path, visited, 0);
        return res;
    }
    void backtrace(vector& nums, vector>& res, vector& path, unordered_set& visited, int index){
        if (index == nums.size()){
            res.push_back(path);
            return ;
        }
        for (int i=0; i< nums.size(); i++){
            if (visited.find(i) != visited.end()) continue;
            // 这里采用nums中第i-1个元素如果没有访问过,说明在之后依然会出现,造成重复。
            if (i > 0 && nums[i] == nums[i-1] && visited.find(i - 1) == visited.end()) continue;
            visited.insert(i);
            path.push_back(nums[i]);
            backtrace(nums, res, path, visited, index+1);
            path.pop_back();
            visited.erase(i);
        }
    }
};

77 组合(medium)


class Solution {
public:
    vector> combine(int n, int k) {
        vector> res;
        vector path;
        baketrace(n, k, 0, res, path);
        return res;
    }
    void baketrace(int n, int k, int index, vector>& res, vector& path){
        if (k == 0){
            res.push_back(path);
            return;
        }
        // 如果剩余的数字不够组成k个数字的组合,那么直接返回
        if (n - index + 1 < k) return ;

        for(int i = 1; i <= n; i++){
            if (i <= index) continue;
            path.push_back(i);
            baketrace(n, k-1, i, res, path);
            path.pop_back();
        }
    }
};

39 组合总和(medium)

  • 与上一题的主要不同点在于一个元素可以多次选择,因此这里层的起始索引不再是i+1,而是i
class Solution {
public:
    vector> combinationSum(vector& candidates, int target) {
        vector> res;
        vector path;
        
        backtrace(candidates, res, path, target, 0);
        return res;
    }
    void backtrace(vector& candidates, vector>&res, vector& path, int target, int index){
        if (target == 0){
            res.push_back(path);
            return ;
        }
        for (int i = 0; i< candidates.size(); i++){
            if (candidates[i] > target) continue;
            if (i < index) continue;
            path.push_back(candidates[i]);
            backtrace(candidates, res, path, target- candidates[i], i);
            path.pop_back();
        }
    }
};

40 组合总和 II

class Solution {
public:
    vector> combinationSum2(vector& candidates, int target) {
    // 每个数字只可以使用一次,并且可能含有重复的数字
    vector> res;
    vector path;
    // 排序去重
    sort(candidates.begin(), candidates.end());
    unordered_set visited;
    backtrace(candidates, res,  path, visited, 0, target);
    return res;
    }
    void backtrace(vector& candidates, vector>&res, vector& path, unordered_set& visited, int index, int target){
        if(target == 0){
            res.push_back(path);
            return;
        }
        for (int i = index; i < candidates.size(); i++){
            
            if (candidates[i] > target) break;
            if ( i > 0 && candidates[i] == candidates[i-1] && visited.find(i-1) == visited.end()) continue;
            path.push_back(candidates[i]);
            visited.insert(i);
            backtrace(candidates, res, path, visited, i+1, target - candidates[i]);
            path.pop_back();
            visited.erase(i);
        }
    }
};

216 组合总和 III

class Solution {
public:
    vector> combinationSum3(int k, int n) {
        if ( k > 9 || n > 45) return {};
        vector> res;
        vector path;
        vector candidates({1,2,3,4,5,6,7,8,9});
        backtrace(candidates, res,  path,  0, n, k);
        return res;
    }
    void backtrace(vector& candidates, vector>&res, vector& path, int index, int target, int depth){
        if(target == 0 && depth == 0){
            res.push_back(path);
            return;
        }
        for (int i = index; i < candidates.size(); i++){
            if (candidates[i] > target) break;
            path.push_back(candidates[i]);
            backtrace(candidates, res, path, i+1, target - candidates[i], depth-1);
            path.pop_back();
        }
    }
};

78 子集(medium)

class Solution {
public:
    vector> subsets(vector& nums) {
        vector> res;
        vector path;
        backtrace(nums, res, path, 0);
        return res;
    }
    void backtrace(vector& nums, vector>& res, vector& path, int index ){

        res.push_back(path);

        for (int i = index; i < nums.size(); i++){
            path.push_back(nums[i]);
            backtrace(nums, res, path, i+1);
            path.pop_back();
        }
    }
};

90 子集II(medium)


class Solution {
public:
    vector> subsetsWithDup(vector& nums) {
        // 同一层中不能有相同的元素, 排序去重
        sort(nums.begin(), nums.end());
        vector> res;
        vector path;
        unordered_set visited;
        backtrace(nums, res, path, visited, 0);
        return res;
    }
    void backtrace(vector& nums, vector>& res, vector& path,unordered_set& visited, int index){
        res.push_back(path);
        for (int i = index; i< nums.size(); i++){
            if (i > 0 && nums[i] == nums[i-1] && visited.find(i - 1) == visited.end()) continue;
            visited.insert(i);
            path.push_back(nums[i]);
            backtrace(nums, res, path, visited, i + 1);
            path.pop_back();
            visited.erase(i);
        }
    }
};
  • 下边的几道搜索的问题,也是一类非常经典的回溯问题。

79 单词搜索(medicum)


class Solution {
public:
    bool exist(vector>& board, string word) {
        vector> offsets({ {-1, 0}, {0, 1}, {1, 0}, {0 ,-1} });
        vector> visited(board.size(), vector(board[0].size(), 0));

        for (int i = 0; i< board.size(); i++){
            for (int j = 0; j< board[0].size(); j++){
                if (board[i][j] == word[0]){
                    if (backtrace(board, word, visited, offsets, i, j, 1))
                        return true;
                }
            }
        }
        return false;
    }

    bool backtrace(vector>& board, string& word, vector>& visited, vector>& offsets, int x, int y, int index){
        // if (board[x][y] != word[index]) return false;
        visited[x][y] = 1;

        if (index == word.size()) return true;

        for (auto off :offsets){

            int new_x = x + off.first;
            int new_y = y + off.second;

            if (!inboard(board, new_x, new_y)) continue;

            if (visited[new_x][new_y] == 1) continue;

            if (board[new_x][new_y] != word[index]) continue;

            if (backtrace(board, word, visited, offsets, new_x, new_y, index + 1))
                return true;
        }
        visited[x][y] = 0;
        return false;
    }
    bool inboard(vector>& board, int x, int y){
        return (x >= 0 && x < board.size()) && ( y >= 0 && y < board[0].size());
    }
};

200 岛屿数量(medium)

class Solution {
public:
    int numIslands(vector>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        int res = 0;
        vector> offsets({ {-1, 0},{1, 0}, {0, 1}, {0, -1} });
        for (int i = 0; i < m; i ++){
            for (int j = 0; j < n; j++){
                if (grid[i][j] == '1'){
                    res++;
                    backtrace(grid, i, j, m, n, offsets);
                }
            }
        }
        return res;
    }
    void backtrace(vector>& grid, int x, int y, int m, int n, vector>& offsets){
        grid[x][y] = '0';
        for (auto off: offsets){
            int new_x = x + off.first;
            int new_y = y + off.second;
            if (!inboard(m, n, new_x, new_y) || grid[new_x][new_y] == '0') continue;
            backtrace(grid, new_x, new_y, m, n, offsets);
        }    
    }

    bool inboard(int m, int n, int x, int y){
        return (x >= 0 && x < m) && (y >= 0 && y < n);
    }
};

130 被围绕的区域(medium)

class Solution {
public:
    void solve(vector>& board) {
        //找到与边界联通的O,标记为o,然后将剩余的O变为x,o变为O即可
        if (board.empty()) return ;
        vector> offsets({
    {1, 0},{-1, 0}, {0, 1}, {0, -1}});
        int m = board.size(), n = board[0].size();
        // 遍历边界
        for ( int i = 0; i< m; i++){
            if (board[i][0] == 'O')
                backtrace(board, i, 0, offsets);
            if (board[i][n-1] == 'O')
                backtrace(board, i, n-1, offsets);
        }
        for (int j = 0; j < n; j++){
            if (board[0][j] == 'O')
                backtrace(board, 0, j, offsets);
            if (board[m- 1][j] == 'O')
                backtrace(board, m-1, j, offsets);            
        }
        // 替换内部的O
        for ( int i = 0;i< m; i++){
            for (int j = 0; j < n; j++){
                if (board[i][j] == 'O')
                    board[i][j] = 'X';
            }
        }
        // 将边界的o换回O
        for ( int i = 0;i< m; i++){
            for (int j = 0; j < n; j++){
                if (board[i][j] == 'o')
                    board[i][j] = 'O';
            }
        }
    }

    void backtrace(vector>& board, int x, int y, vector>& offsets){
        board[x][y] = 'o';
        for (auto off: offsets ){
            int new_x = x + off.first;
            int new_y = y + off.second;
            if (! inboard(board, new_x, new_y) || board[new_x][new_y] != 'O') continue;
            backtrace(board, new_x, new_y, offsets);
        }
    }

    bool inboard(vector>& board, int x, int y){
        return (x >= 0 && x < board.size()) && ( y >= 0 && y < board[0].size());
    }
};

417 太平洋大西洋水流问题(medium)

class Solution {
public:
    vector> pacificAtlantic(vector>& matrix) {
        //使用两个数组分别表示能流到大西洋atlantic与太平洋pacific的位置
        if (matrix.empty()) return {};
        int m = matrix.size(), n = matrix[0].size();
        vector> atlantic(m, vector(n, false));
        vector> pacific(m, vector(n, false));

        vector> offsets({ {-1, 0},{0, -1},{1, 0},{0, 1}});
        vector> res;

        for (int i  = 0; i< m; i++){
            if (! pacific[i][0])
                backtrace(matrix, i, 0, pacific, offsets);
            if (! atlantic[i][n-1])
                backtrace(matrix, i, n-1, atlantic, offsets);
        }
        for (int j  = 0; j< n; j++){
            if (! pacific[0][j])
                backtrace(matrix, 0, j, pacific, offsets);
            if (! atlantic[m-1][j])
                backtrace(matrix, m-1, j, atlantic, offsets);
        }
        for (int i =0; i>& matrix, int x, int y, vector>& ocean, vector>& offsets){
        ocean[x][y] = true;
        for (auto off: offsets){
            int new_x = x + off.first;
            int new_y = y + off.second;
            if (!inboard(matrix, new_x, new_y) || matrix[new_x][new_y] < matrix[x][y] || ocean[new_x][new_y]) continue;
            backtrace(matrix, new_x, new_y, ocean, offsets);
        }
    }

    bool inboard(vector>& matrix, int x, int y){
        return (x >= 0 && x < matrix.size()) && ( y >= 0 && y < matrix[0].size());
    }
};

51 N皇后(hard)

class Solution {
public:
    vector> queues;
    vector> solveNQueens(int n) {
        //主对角线i- j为常数,范围为1-n到n-1
        // 副对角线i+j为常数,范围为0到2*n-2
        vector diag(2*n-1, false); //主对角线,i - j + n - 1为其索引对应的对角线
        vector otherdiag(2*n-1, false); // 副对角线i + j 为其索引对应的对角线。
        vector queue(n, -1); //表示每行放置在哪一列。
        unordered_set cols;  // 表示已经放置的列


        backtrace(n, diag, otherdiag, queue, cols, 0);

        return queues; 

    }
    void backtrace(int n, vector& diag, vector& otherdiag, vector& queue, unordered_set cols, int row){
        if (row == n){
            queues.push_back(generateString(queue, n));
            return ;
        }
        for (int j = 0; j < n; j++){
            if (diag[row-j+n-1] || otherdiag[row+j] || cols.find(j) != cols.end()) continue;
            diag[row-j+n-1] = true;
            otherdiag[row+j] = true;
            cols.insert(j);
            queue[row] = j;
            backtrace(n, diag, otherdiag, queue, cols, row + 1);
            diag[row-j+n-1] = false;
            otherdiag[row+j] = false;
            queue[row] = -1;
            cols.erase(j);
        }
    }
    vector generateString(vector& queue, int n){
        vector res(n, string(n, '.'));

        for (int i=0; i< n; i++)
            res[i][queue[i]] = 'Q';

        return res;
    }
};

52 N皇后 II(hard)

class Solution {
public:
    int totalNQueens(int n) {
        vector diag(2*n - 1, false); // 主对角线是否占用, i-j+n-1为其主对角线的索引
        vector otherdiag(2*n-1, false); //副对角线,i+j为其索引
        unordered_set cols; // 保存已经被占用的列
        vector queue(n, -1); //表示每个元素分别放置在第j列的位置。
        int res = 0;
        backtrace(n, diag, otherdiag, cols, queue, res, 0);
        return res;
    }
    void backtrace(int n, vector& diag, vector& otherdiag, unordered_set& cols, vector& queue, int& res, int row){
        if (row == n){
            res++;
            return ;
        }
        // 第row行可以放的列的位置,遍历查找
        for (int i=0; i< n; i++){
            if(diag[row - i + n - 1] || otherdiag[row + i] || cols.find(i) != cols.end()) continue;
            diag[row - i + n - 1 ] = true;
            otherdiag[row + i] = true;
            cols.insert(i);
            queue[row] = i;
            backtrace(n, diag, otherdiag, cols, queue, res, row+1);
            diag[row - i + n - 1 ] = false;
            otherdiag[row + i] = false;
            cols.erase(i);
            queue[row] = -1;           
        }
    }
};

37 解数独(hard)

class Solution {
public:
    bool valid = false;
    void solveSudoku(vector>& board) {
        vector> rows(9, unordered_set()); //每行出现过的数字
        vector> cols(9, unordered_set()); //每列出现过的数字
        vector< unordered_set > grids(9, unordered_set()); // 第i个3x3的格子出现过的数字,其索引为(i/3)*3 + j/3
        vector> spaces;
        for ( int i = 0; i <9; i++){
            for (int j = 0; j < 9; j++){
                if (board[i][j] == '.')
                    spaces.push_back(make_pair(i, j));
                else{
                    int digit = int(board[i][j] - '0');
                    rows[i].insert(digit);
                    cols[j].insert(digit);
                    grids[(i/3)*3+j/3].insert(digit);
                }
            }
        }
        backtrace(board, rows, cols, grids, spaces, 0);
        return;
    }
    void backtrace(vector>& board, vector>& rows, vector>& cols, vector>& grids, vector>& spaces, int index){
        if ( index == spaces.size()) {
            valid = true;
            return ;
        }
        int i = spaces[index].first;
        int j = spaces[index].second;
        // 放置 k 这个数字
        for (int k = 1; k <= 9; k++){
            if (valid) break;
            if ( rows[i].find(k) != rows[i].end() || cols[j].find(k) != cols[j].end() || grids[(i/3)*3+j/3] .find(k) != grids[(i/3)*3+j/3].end() ) 
                continue;
            rows[i].insert(k);
            cols[j].insert(k);
            grids[(i/3)*3+j/3].insert(k);
            board[i][j] = (char) ('0' + k);
            backtrace(board, rows, cols, grids, spaces, index+1);
            rows[i].erase(k);
            cols[j].erase(k);
            grids[(i/3)*3+j/3].erase(k);
            // board[i][j] = '.';
        }
    }
};

更多分类刷题资料

  • 微信公众号: 小哲AI

    wechat_QRcode

  • GitHub地址: https://github.com/lxztju/leetcode-algorithm

  • csdn博客: https://blog.csdn.net/lxztju

  • 知乎专栏: 小哲AI

  • AI研习社专栏:小哲AI

你可能感兴趣的:(leetcode算法,小哲AI,leetcode,数据结构)