【回溯算法经典题目练习】

1. 目标和

这个题目很类似我们之前做过的子集问题,对于子集我们是当前值选或者是不选,而对于这道题目,是对于+或者-我们选或者不选,我们依然是先画出决策树。

接下来我们就可以直接去写代码了。

class Solution {
    int count = 0;
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        dfs(nums, 0, target, 0);
        return count;
    }

    void dfs(vector<int>& nums, int pos, int target, int path)
    {
        if(pos == nums.size())
        {
            if(path == target) count++;
                return;
        }
        // 加法
        dfs(nums, pos + 1, target, path + nums[pos]);
    
        // 减法
        dfs(nums, pos + 1, target, path - nums[pos]);
    }
};

2. 组合总和

解法一:每个位置选择选一个数,那么此时就会有一个决策树

class Solution {
    vector<vector<int>> ret;
    vector<int> path;
    int aim;
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        aim = target;
        dfs(candidates, 0, 0);
        return ret;
    }
    void dfs(vector<int>& candidates, int pos, int sum)
    {
        if(sum > aim || pos == candidates.size())
        {
            return;
        }
        if(sum == aim)
        {
            ret.push_back(path);
            return;
        }
        for(int i = pos; i < candidates.size(); i++)
        {
            path.push_back(candidates[i]);
            dfs(candidates, i, sum + candidates[i]);
            // 恢复现场
            path.pop_back();
        }
    }
};

解法二:枚举每个值出现的次数,那么此时就会有一个决策树

class Solution
{
	int aim;
	vector<int> path;
	vector<vector<int>> ret;
public:
	vector<vector<int>> combinationSum(vector<int>& candidates, int target)
	{
		aim = target;
		dfs(candidates, 0, 0);
		return ret;
	}
	void dfs(vector<int>& candidates, int pos, int sum)
	{
		if (sum == aim)
		{
			ret.push_back(path);
			return;
		}
		if (sum > aim || pos == candidates.size()) return;
		// 枚举个数
		for (int k = 0; k * candidates[pos] + sum <= aim; k++)
		{
			if (k) path.push_back(candidates[pos]);
			dfs(candidates, pos + 1, sum + k * candidates[pos]);
		}
		// 恢复现场
		for (int k = 1; k * candidates[pos] + sum <= aim; k++)
		{
			path.pop_back();
		}
	}
};

3. 字母大小写全排列

这个题目我们可以从开始位置开始,如果是数字就直接忽略,如果是字母就有两种情况,一种是变,一种是不变,此时我们来画一下决策树

class Solution {
    string path;
    vector<string> ret;
public:
    vector<string> letterCasePermutation(string s) {
        dfs(s, 0);
        return ret;
    }

    void dfs(string& s, int pos)
    {
        if(s.size() == pos)
        {
            ret.push_back(path);
            return;
        }

        // 不变
        path += s[pos];
        dfs(s, pos + 1);
        path.pop_back(); // 恢复现场
        
        // 改变
        if(s[pos] <= 'z' && s[pos] >= 'a')
        {
            s[pos] -= 32;
            path += s[pos];
            dfs(s, pos + 1);
            path.pop_back(); // 恢复现场
        }   
        else if(s[pos] <= 'Z' && s[pos] >= 'A')
        {
            s[pos] += 32;
            path += s[pos];
            dfs(s, pos + 1);
            path.pop_back(); // 恢复现场
        } 
    }
};

4. 优美的排列

此时我们可以按照每个位置选不同的值来画出我们的决策树

class Solution {
    bool check[16] = {false};
    int ret;
public:
    int countArrangement(int n) {
        dfs(1, n); // 下标从1开始
        return ret;
    }

    void dfs(int pos, int n)
    {
        if(pos == n  + 1)
        {
            ret++;
            return;
        }
        for(int i = 1; i <= n; i++)
        {
            // 1.递归的过程中不能使用重复元素
            // 2.递归的过程中下标和值能够整除
            // pos是下标
            if(!check[i]&& (pos % i == 0 || i % pos == 0))
            {
                check[i] = true;
                dfs(pos + 1, n);
                check[i] = false; // 恢复现场
            }
        }
    }
};

5. N 皇后

此时我们不能一个一个位置的考虑,而应该一行一行考虑,先来画出我们的决策树

class Solution {
    bool checkCol[10] = { false };
    bool checkRow[10] = { false };
    bool checkDig1[20] = { false };
    bool checkDig2[20] = { false };
    vector<vector<string>> ret;
    vector<string> path;
public:
    vector<vector<string>> solveNQueens(int n) {
        // 先初始化将全都path为"."
        path.resize(n);
        for(int i = 0; i < n; i++)
        {
            path[i].append(n, '.');
        }
        dfs(0, n);
        return ret;
    }

    void dfs(int row, int& n)
    {
        if(n == row)
        {
            ret.push_back(path);
            return;
        }
        // 尝试在这一行放皇后
        for(int col = 0; col < n; col++)
        {
            // 判断四条线是否有其他皇后放了
            if(!checkRow[row] && !checkCol[col] && !checkDig1[row - col + n] && !checkDig2[row + col])
            {
                path[row][col] = 'Q';
                checkRow[row] = checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = true;
                dfs(row + 1, n);
                // 恢复现场
                path[row][col] = '.';
                checkRow[row] = checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = false;
            }
        }
    }
};

6. 有效的数独

我们这个题目其实和递归没有任何关系,但是这个题目能够很好的帮我们理解上一个题目N皇后的剪枝策略以及下一个题目做支撑,建三个数组标记⾏、列以及 3*3 ⼩⽅格中是否出现 1~9 之间的数字即可解决。

class Solution {
    bool row[9][10] = { false }; // 表示这一行出否出现y坐标元素
    bool col[9][10] = { false }; // 表示这一列是否出现y坐标的值
    bool grid[3][3][10] = { false }; //表示这个小方块是否出现z坐标的值
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        for(int i = 0; i < board.size(); i++)
        {
            for(int j = 0; j < board[i].size(); j++)
            {
                if(board[i][j] != '.')
                {
                    int num = board[i][j] - '0';
                    // 是否出现该数字
                    if(row[i][num] || col[j][num] || grid[i/3][j/3][num])
                    {
                        return false;
                    }
                    row[i][num] = col[j][num] = grid[i/3][j/3][num] = true;
                }
            }
        }
        return true;
    }
};

7. 解数独

有了上一个题目的判断,我们这个题目就比较简单了,先画出决策树,我们没遇到一个格子,先判断能不能填,能填就填,不能填就换下一个数。

class Solution {
    bool row[9][10] = { false }; // 表示这一行出否出现y坐标元素
    bool col[9][10] = { false }; // 表示这一列是否出现y坐标的值
    bool grid[3][3][10] = { false }; //表示这个小方块是否出现z坐标的值
public:
    void solveSudoku(vector<vector<char>>& board) {
        for(int i = 0; i < board.size(); i++)
        {
            for(int j = 0; j < board[i].size(); j++)
            {
                if(board[i][j] != '.')
                {
                    // 先把所有为数字的标记为已使用
                    int val = board[i][j] - '0';
                    row[i][val] = true;
                    col[j][val] = true;
                    grid[i/3][j/3][val] = true;
                }
            }
        }
        dfs(board);
    }

    bool dfs(vector<vector<char>>& board)
    {
        for(int i = 0; i < board.size(); i++)
        {
            for(int j = 0; j < board[i].size(); j++)
            {
                if(board[i][j] == '.')
                {
                    // 填数,填写的数字分别为1-9
                    for(int k = 1; k <=9 ; k++)
                    {
                        if(!row[i][k] && !col[j][k] && !grid[i/3][j/3][k])
                        {
                            board[i][j] = k + '0';
                            row[i][k] = col[j][k] = grid[i/3][j/3][k] = true;
                            // 本次填数成功
                            if(dfs(board)) return true;
                            board[i][j] = '.';
                            row[i][k] = col[j][k] = grid[i/3][j/3][k] = false;
                        }
                    }
                    // 1-9全部都填写了,但是依然不满足条件
                    return false;
                }
            }
        }
        return true;
    }
};

8. 单词搜索

我们依然是先画出这个题目的决策树。

我们可以看到上面的决策树之后,我们会发现当我们找到word里面的一个单词在矩阵里面的时候,我们会遍历上下左右四个方向的字母,此时我们就可以使用下面的策略来帮助我们访问这四个位置。

此时我们就可以直接来写代码啦。 

class Solution {
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    bool vis[7][7] = {false};
    int m, n;
public:
    bool exist(vector<vector<char>>& board, string word) {
        m = board.size(), n = board[0].size();
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                // 找深搜的起点
                if(board[i][j] == word[0])
                {
                    vis[i][j] = true;
                    if(dfs(board, i, j, word, 1)) return true;
                    vis[i][j] = false; // 恢复现场
                }
            }
        }
        return false;
    }

    bool dfs(vector<vector<char>>& board, int i, int j, string word, int pos)
    {
        if(pos == word.size())
        {
            return true;
        }
        for(int k = 0; k < 4; k++)
        {
            int x = dx[k] + i;
            int y = dy[k] + j;
            if((x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && board[x][y] == word[pos]))
            {
                vis[x][y] = true;
                if(dfs(board, x, y, word, pos + 1)) return true;
                vis[x][y] = false; // 恢复现场
            }
        }
        return false;
    }
};

9. 黄金矿工

class Solution {
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    bool vis[16][16] = {false};
    int m, n;
    int ret;
public:
    int getMaximumGold(vector<vector<int>>& grid) {
        m = grid.size();
        n = grid[0].size();
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                // 只要不为0,就可以开始深度递归
                if(grid[i][j] != 0)
                {
                    vis[i][j] = true;
                    dfs(grid, i, j, grid[i][j]);
                    vis[i][j] = false; // 恢复现场
                }
            }
        }
        return ret;
    }

    void dfs(vector<vector<int>>& grid, int i, int j, int path)
    {
        ret = max(ret, path);

        for(int k = 0; k < 4; k++)
        {
            // 遍历上下左右四个方向
            int x = dx[k] + i;
            int y = dy[k] + j;
            if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] != 0)
            {
                vis[i][j] = true;
                dfs(grid, x, y, path + grid[x][y]);
                vis[i][j] = false;
            }
        }
    }
};

10. 不同路径 Ⅲ

在 dfs 函数中,对于当前的位置 (i, j):如果到达终点 (2),并且走过的步数等于 step,则找到了一条合法路径,增加 ret 的计数。否则,遍历四个方向(上、下、左、右):计算新的位置 (x, y)。如果新位置在网格内,且未被访问过,并且不是障碍物,则:标记新位置为已访问。递归调用 dfs 函数,传入新位置和增加的步数。递归返回后,恢复新位置的访问状态(即未访问)。

class Solution {
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    bool vis[21][21] = { false };
    int m, n, step;
    int ret = 0;
public:
    int uniquePathsIII(vector<vector<int>>& grid) {
        m = grid.size();
        n = grid[0].size();
        // 定义起始位置坐标
        int x, y;
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
                if(grid[i][j] == 0)
                    step++;
                else if(grid[i][j] == 1)
                    x = i, y = j;

        // 开始位置和结束位置也当作一步
        step += 2;
        // 开始深搜递归
        vis[x][y] = true;
        dfs(grid, x, y, 1);
        return ret;
    }


    void dfs(vector<vector<int>>& grid, int i, int j, int count)
    {
        // 递归出口
        // 到达终点
        if(grid[i][j] == 2)
        {
            // 判断步数是否正确
            if(count == step)
                ret++;
            return;
        }
        for(int k = 0; k < 4; k++)
        {
            int x = dx[k] + i;
            int y = dy[k] + j;
            if(x >= 0 && x < m && y >=0 && y < n && !vis[x][y] && grid[x][y] != -1)
            {
                vis[x][y] = true;
                dfs(grid, x, y, count + 1);
                vis[x][y] = false; // 恢复现场
            }
        }
    }
};
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值