力扣刷题总结之回溯

回溯算法思想

解决⼀个回溯问题,实际上就是⼀个决策树的遍历过程。你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,⽆法再做选择的条件。
如果你不理解这三个词语的解释,没关系,我们后⾯会⽤「全排列」和「N皇后问题」这两个经典的回溯算法问题来帮你理解这些词语是什么意思,现在你先留着印象。
代码⽅⾯,回溯算法的框架:

result = []
def backtrack(路径, 选择列表):

if 满⾜结束条件:
    result.add(路径)
    return

for 选择 in 选择列表:
    做选择
    backtrack(路径,选择列表)
    撤销选择

其核⼼就是 for 循环⾥⾯的递归,在递归调⽤之前「做选择」,在递归调⽤之后「撤销选择」,特别简单。

回溯搜索是深度优先搜索(DFS)的一种,回溯法通俗的将其采用的思想是“一直向下走,走不通就掉头”,类似于树的先序遍历。dfs和回溯法其主要的区别是:回溯法在求解过程中不保留完整的树结构,而深度优先搜索则记下完整的搜索树。

为了减少存储空间,在深度优先搜索中,用标志的方法记录访问过的状态,这种处理方法使得深度优先搜索法与回溯法没什么区别了。

回溯法在实现上也是遵循深度优先的,即一步一步往前探索,而不像广度优先那样,由近及远一层一层地扫。

力扣回溯算法题目总结

1、全排列 --中等

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        if(nums.empty())
            return {{}};
        vector<int> temp;
        backtrack(nums,temp);
        return res;
    }
    void backtrack(const vector<int> & nums,vector<int> &temp)
    {
        if(temp.size() == nums.size())
        {
            res.push_back(temp);
            return ;
        }
        for(int i = 0;i<nums.size();i++)
            {
                if(find(temp.begin(),temp.end(),nums[i])  != temp.end()) //去重
                    continue;
                temp.push_back(nums[i]);
                backtrack(nums,temp);
                temp.pop_back();
            }
    }
private:
    vector<vector<int>> res;
};
2、子集 --中等

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: [1,2,2]
输出:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]
class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<int> temp;
        sort(nums.begin(),nums.end());
        backtrack(nums,0,temp);
        return res;
    }
void backtrack(const vector<int> & nums,int begin,vector<int> &temp)
{
    res.push_back(temp);

    for(int i = begin;i<nums.size();i++)
    {
        if(i > begin && nums[i] == nums[i-1]) //去重
            continue;
        temp.push_back(nums[i]);

        backtrack(nums,i+1 ,temp);

        temp.pop_back();
    }
}

private:
    vector<vector<int>> res;
};
3、组合 --中等

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        if(n < 1 || k <=0)
            return {};
        
        vector<int> temp;
        backtrack(n,k,1,temp);

        return result;
    }
    void backtrack(int n, int k, int start,vector<int>& temp)
    {
        if(temp.size() == k)
            result.push_back(temp);
        
        for(int i = start;i<=n;i++)
        {
            temp.push_back(i);
            backtrack(n,k,i+1,temp);
            temp.pop_back();
        }
        return;
    }

private:
    vector<vector<int>> result;
};
4、水域面积 --中等

你有一个用于表示一片土地的整数矩阵land,该矩阵中每个点的值代表对应地点的海拔高度。若值为0则表示水域。由垂直、水平或对角连接的水域为池塘。池塘的大小是指相连接的水域的个数。编写一个方法来计算矩阵中所有池塘的大小,返回值需要从小到大排序。

示例:

输入:
[
  [0,2,1,0],
  [0,1,0,1],
  [1,1,0,1],
  [0,1,0,1]
]
输出: [1,2,4]

提示:

0 < len(land) <= 1000
0 < len(land[i]) <= 1000

class Solution {
public:
    vector<int> pondSizes(vector<vector<int>>& land) {
        if(land.empty())
            return {};
        vector<int> result;
        rows = land.size();
        cols = land[0].size();

        for(int i = 0;i<rows;i++)
        {
            for(int j = 0;j<cols;j++)
            {
                if(land[i][j] == 0)
                    result.push_back(backtrack(land,i,j));
            }
        }

        sort(result.begin(),result.end());
        return result;
    }

    int backtrack(vector<vector<int>>& land,int i,int j)
    {
        if(i < 0 || i >= rows || j < 0 ||j>=cols || land[i][j] !=0)
            return 0;
        
        int area = 1;
        land[i][j] = 1;

        for(int k = 0;k<8;k++)
        {
            area += backtrack(land,i+around[k].first,j+around[k].second);
        }

        return area;
    }

private:
    vector<pair<int,int>> around = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};//land       [i][j]周围的方向
    int rows;
    int cols;   
};
5、电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

img

示例:

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        if(digits.empty())
            return vector<string> {};
        
        string temp; 
        backtrack(digits,temp,0);
        return res;
    }
    void backtrack(const string &digits,string & temp,int begin)
    {
        if(temp.size() == digits.size())
            {
                res.push_back(temp);
                return;
            }
            string s = str[digits[begin] - '2'];
            for(int j = 0;j<s.size();j++)
            {
                temp.push_back(s[j]);
                backtrack(digits,temp,begin+1);
                temp.pop_back();
            }

    }
private:
    vector<string> str = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    vector<string> res;
};
6、机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1:

输入:m = 2, n = 3, k = 1
输出:3

示例 2:

输入:m = 3, n = 1, k = 0
输出:1

提示:

1 <= n,m <= 100
0 <= k <= 20
class Solution {
public:
    int movingCount(int m, int n, int k) {
        if( m <=0 || n <=0)
            return 0;
        int count;
        vector<vector<bool>> visited(m,vector<bool>(n,true)); 
        count = backtrack(m,n,k,0,0,visited);
        return count;
    }

    int backtrack(int m, int n,int k, int rows,int cols,vector<vector<bool>>& visited)
    {
        if(!isValid(m,n,k,rows,cols,visited))
            return 0;

        int sum = 1;
        visited[rows][cols] = false;

        for(int i = 0;i<4;i++)
        {
            //visited[rows][cols] = false;
            int x = rows + around[i].first;
            int y = cols + around[i].second;

            sum += backtrack(m,n,k,x,y,visited);
        }
        return sum;
    }

    bool isValid(int m,int n, int k, int rows,int cols,vector<vector<bool>>& visited)
    {
        if(rows < 0 || rows >= m || cols < 0 || cols >=n || check(rows,cols) > k || !visited[rows][cols])
            return false;
        return true;
    }

    int check(int rows,int cols)
    {
        int sum = 0;
        while(rows)
        {
            sum += rows % 10;
            rows = rows / 10;
        }
        while(cols)
        {
            sum += cols % 10;
            cols = cols / 10;
        }
        return sum;
    }

private:
    vector<pair<int,int>> around = {{0,1},{0,-1},{1,0},{-1,0}}; //上下左右四个方向 
};
7、解数独 --困难

编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 ‘.’ 表示。

img

一个数独。

img

答案被标成红色。

Note:

给定的数独序列只包含数字 1-9 和字符 '.' 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。
class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
       if(board.empty())
            return ;
        backtrack(board,0,0);
        return;
    }
    bool backtrack(vector<vector<char>> &board, int i, int j) {
        int m = 9, n = 9;
        if (j == n) {
            // 穷举到最后一列的话就换到下一行重新开始。
            return backtrack(board, i + 1, 0);
        }
        if (i == m) {
            // 找到一个可行解,触发 base case
            return true;
        }

        if (board[i][j] != '.') {
            // 如果有预设数字,不用我们穷举
            return backtrack(board, i, j + 1);
        } 

        for (char ch = '1'; ch <= '9'; ch++) {
            // 如果遇到不合法的数字,就跳过
            if (!isValid(board, i, j, ch))
                continue;
            
            board[i][j] = ch;
            // 如果找到一个可行解,立即结束
            if (backtrack(board, i, j + 1)) {
                return true;
            }
            board[i][j] = '.';
        }
        // 穷举完 1~9,依然没有找到可行解,此路不通
        return false;
    }

// 判断 board[i][j] 是否可以填入 n
    bool isValid(const vector<vector<char>> &board, int r, int c, char n) {
        for (int i = 0; i < 9; i++) {
            // 判断行是否存在重复
            if (board[r][i] == n) return false;
            // 判断列是否存在重复
            if (board[i][c] == n) return false;
            // 判断 3 x 3 方框是否存在重复
            if (board[(r/3)*3 + i/3][(c/3)*3 + i%3] == n)
                return false;
        }
        return true;
    }
};
8、岛屿的数量 --中等

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:
11110
11010
11000
00000
输出: 1

示例 2:

输入:
11000
11000
00100
00011
输出: 3
class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        if(grid.empty())
            return 0;
        rows = grid.size();
        cols = grid[0].size();
       vector<vector<bool>> visited(rows,vector<bool>(cols,false));
        int count = 0;

        for(int i = 0;i<rows;i++)
        {
            for(int j = 0;j<cols;j++)
            {
                if(grid[i][j] == '1' && !visited[i][j])
                {
                    count++;
                    backtrack(i,j,grid,visited);
                }
            }
        }
        return count;
    }

    void backtrack(int i,int j,vector<vector<char>>& grid,vector<vector<bool>>& visited)
    {
        if( i < 0 || i >= rows || j < 0 || j>= cols ||  grid[i][j] == '0' || visited[i][j] )
            return;
        
        visited[i][j] = true;
        for(int k = 0;k<4;k++)
        {
            int x = i + around[k].first;
            int y = j + around[k].second;
            backtrack(x,y,grid,visited);
        }
        return;
    }

private:
    vector<pair<int,int>> around = {{0,1},{0,-1},{1,0},{-1,0}};
    int rows;
    int cols;
};
9、N皇后 --困难

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

img

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

示例:

输入: 4
输出: [
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        if(n <= 0) 
            return {};
        vector<string> board(n,string(n,'.'));

        backtrack(board,0);

        return result; 
    }

    void backtrack(vector<string>& board,int rows)
    {
        if(rows == board.size())
            {
                result.push_back(board);   //相当于模板中的加路径
                return;
            }
        
        for(int cols = 0;cols < board[0].size();cols++)
        {
            if(!isValid(board,rows,cols))
                continue;
            board[rows][cols] = 'Q';   //做选择
            backtrack(board,rows + 1); //回溯
            board[rows][cols] = '.';  //撤销选择
        }
        return;
    }
    
    bool isValid(vector<string>& board, int rows, int cols)
    {
        int n = board.size();
        //检查列是否有皇后冲突,由于每一行只会放一个皇后,所有不需要判断行是否有皇后冲突
        for(int i = 0;i<n;i++)
        {
            if(board[i][cols] == 'Q')
                return false;
        }

        //检查主对角线是否皇后冲突(主对角线性质为:cols - rows = 0),只有算左上方,下边的还没有填充
        for(int i = rows -1,j = cols -1; i>=0 && j>=0; i--,j--)
        {
            if(board[i][j] == 'Q')
                return false;
        }

        //检查次对角线是否皇后冲突(主对角线性质为:cols + rows = n)只有算右上方,下边的还没有填充
        for(int i = rows - 1,j = cols + 1; i>=0 && j<n;i--,j++)
        {
            if(board[i][j] == 'Q')
                return false;
        }
        return true;
    }

private:
    vector<vector<string>> result;
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值