【常见算法】第三篇:回溯算法


一、概念

回溯法

  • 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
  • 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。也可以称为剪枝点,所谓的剪枝,指的是把不会找到目标,或者不必要的路径裁剪掉。
  • 许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
  • 在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。
  • 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
  • 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
  • 除过深度优先搜索,常用的还有广度优先搜索。

二、深度优先搜索(Depth First Search)------ 一条道走到黑

2.1 放牌

假如有编号为1-3的3张扑克牌和编号为1~3的3个盒子,现在需要将3张牌分别放到3个盒子中去,且每个盒子只能放一张牌,一共有多少种不同的放法
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

#include <iostream>
#include <vector>
using namespace std;

void DFS(vector<int>& book, vector<int>& box, int index, int n)
{
	if (index == n + 1)
	{
		for (int i = 1; i <= n; ++i)
		{
			cout << box[i] << " ";
		}
		cout << endl;
		return;
	}

	for (int i = 1; i <= n; ++i)
	{
		if (book[i] == 0)
		{
			box[index] = i;
			book[i] = 1;//设置成已用
			//深度优先遍历
			DFS(book, box, index+1, n);
			//回收
			book[i] = 0;
		}
	}
}

int main()
{
	int n = 0;
	cin >> n;
	vector<int> book(n + 1, 0);//标记是否被用,0--未用, 1--已用
	vector<int> box(n + 1, 0);//放入牌的盒子
	DFS(book, box, 1, n);
	return 0;
}

2.2 员工的重要性

在这里插入图片描述

/*
// Definition for Employee.
class Employee {
public: 
    int id;
    int importance;
    vector<int> subordinates;
};
*/

class Solution {
public:
    int DFS(map<int, Employee*>& info, int id)
    {
        int curImportance = info[id]->importance;
        for(auto& e : info[id]->subordinates)
        {
            curImportance += DFS(info, e);
        }
        return curImportance;
    }

    int getImportance(vector<Employee*> employees, int id) 
    {
        if(employees.empty())
        {
            return 0;
        }
        //将id的员工的地址保存在map里面
        map<int, Employee*> info;
        for(auto& e : employees)
        {
            info[e->id] = e;
        }

        return DFS(info, id);
    }
};

2.3 图像渲染

在这里插入图片描述

class Solution {
    int nextP[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:
    void DFS(vector<vector<int>>& image, int row, int col, vector<vector<int>>&book, int curX, int curY, int oldColor, int newColor)
    {
        //修改当前位置颜色
        image[curX][curY] = newColor;
        book[curX][curY] = 1;
        for(int i = 0; i < 4; ++i)
        {
            int newX = curX + nextP[i][0];
            int newY = curY + nextP[i][1];

            if(newX < 0 || newY < 0 || newX >= row || newY >= col)
                continue;
            if(image[newX][newY] == oldColor && book[newX][newY] == 0)
                DFS(image, row, col, book, newX, newY, oldColor, newColor);
        }

    }

    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) 
    {
       if(image.empty())
            return image;
        int row = image.size();
        int col = image[0].size();
        vector<vector<int>> book(row, vector<int>(col, 0));
        int oldColor = image[sr][sc];
        int newColor = color;
        DFS(image, row, col, book, sr, sc, oldColor, newColor);
        return image;
    }
};

2.4 被围绕的区域

在这里插入图片描述

本题的意思被包围的区间不会存在于边界上,所以边界上的o以及与o联通的都不算做包围,只要把边界上的o以及与之联通的o进行特殊处理,剩下的o替换成x即可。故问题转化为,如何寻找和边界联通的o,我们需要考虑如下情况。
X X X X
X O O X
X X O X
X O O X
从每一个边缘的o开始,只要和边缘的o联通,则它就没有被包围。
1.首先寻找边上的每一个o,如果没有,表示所有的o都被包围
2.对于边上的每一个o进行dfs进行扩散,先把边上的每一个o用特殊符号标记,比如*,#等,
3.把和它相邻的o都替换为特殊符号,每一个新的位置都做相同的dfs操作
4.所有扩散结束之后,把特殊符号的位置(和边界连通)还原为o,原来为o的位置(和边界不连通)替换为x即可。这里一定要注意这里是大’O’和大’X’

class Solution {
public:
    int nextP[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };
    void DFS(vector<vector<char>>& board, int row, int col, int curX, int curY)
    {
        //将当前位置修改成为'A'
        board[curX][curY] = 'A';
        //上下左右分别遍历,将所有和边界相连的O字符变为A字符
        for(int i = 0; i < 4; ++i)
        {
            int newX = curX + nextP[i][0];
            int newY = curY + nextP[i][1];
            //下表越界了
            if(newX < 0 || newX >= row || newY < 0 || newY >= col)
                continue;
            if(board[newX][newY] == 'O')
                DFS(board, row, col, newX, newY);
        }
    }

    void solve(vector<vector<char>>& board) {
        if(board.empty())
            return;
        int row = board.size();
        int col = board[0].size();
        for(int i = 0; i < row; ++i)
        {
            if(board[i][0] == 'O')
                DFS(board, row, col, i, 0);
            if(board[i][col-1] == 'O')
                DFS(board, row, col, i, col-1 );
        }
        for(int i  = 1; i < col - 1; ++i)
        {
            if(board[0][i] == 'O')
                DFS(board, row, col, 0, i);
            if(board[row-1][i] == 'O')
                DFS(board, row, col, row-1, i);
        }

        for(int i = 0; i < row; ++i)
        {
            for(int j = 0; j < col; ++j)
            {
                if(board[i][j] == 'O')
                    board[i][j] = 'X';
                if(board[i][j] == 'A')
                    board[i][j] = 'O';
            }
        }
    }
};

2.5 岛屿数量

在这里插入图片描述

class Solution {
public:
    int nextP[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };

    void DFS(vector<vector<char>>& grid, int row, int col, int curX, int curY)
    {
        //将当前位置置为0
        grid[curX][curY] = '0';
        //遍历上下左右将所有与当前位置相连的1字符置为0字符
        for(int i = 0; i < 4; ++i)
        {
            int newX = curX + nextP[i][0];
            int newY = curY + nextP[i][1];

            if(newX < 0 || newX >= row || newY < 0 || newY >= col )
                continue;
            if(grid[newX][newY] == '1')
                DFS(grid, row, col, newX, newY);            
        }
    }

    int numIslands(vector<vector<char>>& grid) {
        if(grid.empty())
            return 0;
        int row = grid.size();
        int col = grid[0].size();
        int count = 0;
        for(int i = 0; i < row; ++i)
        {
            for(int j = 0; j < col; ++j)
            {
                if(grid[i][j] == '1')
                {
                    DFS(grid, row, col, i, j);
                    ++count;
                }
            }
        }
        return count;
    }
};

2.6 电话号码的字母组合

在这里插入图片描述

class Solution {
public:
    map<char, string> mapstr = { {'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"}, {'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"} };

    void DFS(vector<string>& ret, string& digits, string curStr, int digitsIndex)
    {
        if(digitsIndex == digits.size())
        {
            ret.push_back(curStr);
            return;
        }

        string str = mapstr[digits[digitsIndex]];
        for(auto ch : str)
        {
            DFS(ret, digits, curStr + ch, digitsIndex+1);
        }
        
    }

    vector<string> letterCombinations(string digits) 
    {
        vector<string> ret;
        if(digits.empty())
            return ret;
        DFS(ret, digits, "", 0);
        return ret;
    }
};

2.7 组合总和

在这里插入图片描述

class Solution {
public:
    void DFS(vector<int>& candidates, vector<vector<int>>& vv, vector<int> v, int curSum, int prevPosition, int target)
    {
        if(curSum >= target)
        {
            if(curSum == target)
                vv.push_back(v);
            return;
        }          
        for(int i = prevPosition; i < candidates.size(); ++i)
        {
            if(candidates[i] > target)
                continue;
            v.push_back(candidates[i]);
            DFS(candidates, vv, v, curSum + candidates[i], i, target);
            v.pop_back();
        }  
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> vv;
        vector<int> v;

        if(candidates.empty())
            return vv;
        int curSum = 0;
        DFS(candidates, vv, v, curSum, 0, target);
        return vv;
    }
};

2.8 活字印刷

在这里插入图片描述

class Solution {
public:
    void DFS(string& tiles, string curStr, vector<int>& book, unordered_set<string>& ret)
    {
        if(!curStr.empty())
        {
            ret.insert(curStr);
        }

        for(int i = 0; i < tiles.size(); ++i)
        {
            if(book[i] == 0)
            {
                book[i] = 1;
                DFS(tiles, curStr + tiles[i], book, ret);
                book[i] = 0;
            }
        }
    }

    int numTilePossibilities(string tiles) {
        if(tiles.empty())
        {
            return 0;
        }
        int size = tiles.size();
        vector<int> book(size, 0);//标记位
        unordered_set<string> ret;//存储每个字符串
        string curStr;
        DFS(tiles, curStr, book, ret);
        return ret.size();
    }
};

2.9 N皇后

在这里插入图片描述

class Solution {
public:
    bool isValuePos(vector<pair<int, int>>& curRet, int row, int col)
    {
        for(pair<int, int> pos : curRet)
        {
            if(pos.second == col || pos.first + pos.second == row + col || pos.second - pos.first == col - row)
                return false;
        }
        return true;
    }

    void DFS(vector<vector<pair<int, int>>>& AllRet, vector<pair<int, int>>& curRet,  int curRow, int n)
    {
        if(curRow == n)
        {
            AllRet.push_back(curRet);
            return;
        }

        for(int i = 0; i < n; ++i)
        {
            //判断当前位置是否有效
            if(isValuePos(curRet, curRow, i))
            {
                curRet.push_back(make_pair(curRow, i));
                 //递归下一行
                DFS(AllRet, curRet, curRow + 1, n);
                //递归到最后一行后回溯
                curRet.pop_back();
            }
           
        }
    }

    vector<vector<string>> Transfer(vector<vector<pair<int, int>>>& AllRet, int n)
    {
        vector<vector<string>> allMat;
        //所有方案
        for(auto curRet : AllRet)
        {
            //一种方案
            vector<string> curMat(n, string(n, '.'));
            for(auto pos : curRet)
            {
                curMat[pos.first][pos.second] = 'Q';
            }
            allMat.push_back(curMat);
        }
        return allMat;
    }

    vector<vector<string>> solveNQueens(int n) {
        vector<vector<pair<int, int>>> AllRet;
        vector<pair<int,int>> curRow;
        DFS(AllRet, curRow, 0, n);
        return Transfer(AllRet, n);
    }
};

三、广度优先搜索(Breadth First Search) ------ 一石激起千层浪

3.1 迷宫

假设有一个迷宫,里面有障碍物,迷宫用二维矩阵表示,标记为0的地方表示可以通过,标记为1的地方表示障碍物,不能通过。现在给一个迷宫出口,让你判断是否可以从入口进来之后,走出迷宫,每次可以向任意方向走
在这里插入图片描述

#include <vector>
#include <iostream>
#include <queue>
using namespace std;

struct node
{
	node(int x, int y)
		:_x(x)
		,_y(y)
	{}

	int _x;
	int _y;
};

bool BFS(vector<vector<int>>& graph, int sx, int sy, int ex, int ey)
{
	int row = graph.size();
	int col = graph[0].size();
	vector<vector<int>> book(row, vector<int>(col, 0));//标记位置是否已走 0--未走  1--已走
	int next[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };

	queue<node> q;
	//将其实位置入队
	q.push(node(sx, sy));
	book[sx][sy] = 1;
	int flag = 0;
	while (!q.empty())
	{
		node cur = q.front();
		q.pop();
		for (int i = 0; i < 4; ++i)
		{
			int nextX = cur._x + next[i][0];
			int nextY = cur._y + next[i][1];

			if (nextX < 0 || nextX >= row || nextY < 0 || nextY >= col)
				continue;
			//判断是否入队
			if (graph[nextX][nextY] == 0 && book[nextX][nextY] == 0)
			{
				q.push(node(nextX, nextY));
				book[nextX][nextY] = 1;
			}
			//当前点是终点
			if (nextX == ex && nextY == ey)
			{
				flag = 1;
				break;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}

	return flag;
}

int main()
{
	int m = 0;//行
	int n = 0;//列
	cin >> m >> n;

	vector<vector<int>> graph(m, vector<int>(n, 0));

	for (int i = 0; i < m; ++i)
	{
		for (int j = 0; j < n; ++j)
		{
			cin >> graph[i][j];
		}
	}
	
	int sx = 0;//其实位置
	int sy = 0;
	int ex = m - 1;//结束位置
	int ey = n - 1;

	cout << BFS(graph, sx, sy, ex, ey) << endl;
	return 0;
}

3.2 N叉树的层序遍历

在这里插入图片描述

/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root)
    {
        queue<Node*> q;
        if(root)
            q.push(root);
        vector<vector<int>> ret;
        while(!q.empty())
        {
            int size = q.size();
            vector<int> rowV;
            while(size--)
            {
                Node* front = q.front();
                q.pop();
                rowV.push_back(front->val);
                //将结点的孩子全部入队
                for(auto* e : front->children)
                {
                    q.push(e);
                }
            }
            ret.push_back(rowV);
        }
        return ret;
    }
};

3.3 腐烂的橘子

在这里插入图片描述

class Solution {
public:
    struct Node{
        Node(int x, int y)
            :_x(x)
            ,_y(y)
        {}
        int _x;
        int _y;
    };
    int next[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1} };

    int orangesRotting(vector<vector<int>>& grid) {
        if(grid.empty())
            return 0;
        int row = grid.size();
        int col = grid[0].size();
        queue<Node> q;

        for(int i = 0; i < row; ++i)
        {
            for(int j = 0; j < col; ++j)
            {
                //将所有腐烂的橘子入队
                if(grid[i][j] == 2)
                {
                    q.push(Node(i, j));
                }
            }
        }  
        int count = 0;
        while(!q.empty())
        {
            int size = q.size();
            int flag = false;    
            while(size--) 
            {
                Node front = q.front();
                q.pop();
                for(int i = 0; i < 4; ++i)
                {
                    int newX = front._x + next[i][0];
                    int newY = front._y + next[i][1];

                    if(newX < 0 || newX >= row || newY < 0 || newY >= col)
                        continue;
                    if(grid[newX][newY] == 1)
                    {
                        flag = true;
                        q.push(Node(newX, newY));
                        grid[newX][newY] = 2;
                    }
                }//end of for
            }//end of while
            if(flag)
                ++count;
        }

        for(int i = 0; i < row; ++i)
        {
            for(int j = 0; j < col; ++j)
            {
                if(grid[i][j] == 1)
                    return -1;
            }
        }

        return count;
    }
};

3.4 单词接龙

在这里插入图片描述

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        //用hash表查找更快
        unordered_set<string> dict(wordList.begin(), wordList.end());
        unordered_set<string> book;//标记单词是否用过
        queue<string> q;
        int step = 1;//步数

        q.push(beginWord);
        book.insert(beginWord);
        while(!q.empty())
        {//while 1
            int size = q.size();
            while(size--)
            {//while 2
                string curStr = q.front();
                q.pop();
                if(curStr == endWord)
                    return step;
                //替换单词的每个字符
                for(int i = 0; i < curStr.size(); ++i)
                {//for 1
                    string temp = curStr;
                    for(char ch = 'a'; ch <= 'z'; ++ch)
                    {
                        temp[i] = ch;
                        //替换后的单词是否能在词典中找到, 并且没有被使用过
                        if(dict.find(temp) != dict.end() && book.find(temp) == book.end())
                        {
                            q.push(temp);
                            book.insert(temp);
                        }
                    }
                }//end of for 1
            }//end of while 2
            ++step;
        }//end of while 1
        return 0;
    }
};

3.5 打开转盘锁

在这里插入图片描述

class Solution {
public:
    int openLock(vector<string>& deadends, string target) {
        //方便查找
        unordered_set<string> deadendsMap(deadends.begin(), deadends.end());
        unordered_set<string> book;//标记当前密码是否已经出现过
        queue<string> q;
        if(deadendsMap.find("0000") != deadendsMap.end())
            return -1;
        q.push("0000");
        book.insert("0000");
        int step = 0;
        while(!q.empty())
        {//while 1
            int size = q.size();
            while(size--)
            {//while 1
                string curStr = q.front();
                q.pop();
                if(curStr == target)
                    return step;
                //改变每一位的数字
                for(int i = 0; i < 4; ++i)
                {
                    //每一位数字向上旋转也可以向下旋转
                    string tmpStr1 = curStr;
                    string tmpStr2 = curStr;

                    tmpStr1[i] = tmpStr1[i] == '9' ? '0' : tmpStr1[i] + 1;
                    tmpStr2[i] = tmpStr2[i] == '0' ? '9' : tmpStr2[i] - 1;

                    if(deadendsMap.find(tmpStr1) == deadendsMap.end() 
                    && book.find(tmpStr1) == book.end())
                    {
                        q.push(tmpStr1);
                        book.insert(tmpStr1);
                    }
                    if(deadendsMap.find(tmpStr2) == deadendsMap.end() 
                    && book.find(tmpStr2) == book.end())
                    {
                        q.push(tmpStr2);
                        book.insert(tmpStr2);
                    }
                }
            }//end of while 2
            ++step;
        }//end of while 1
        return -1;
    }
};
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小唐学渣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值