回溯算法OJ刷题(1)

回溯算法

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。

而满足回溯条件的某个状态的点称为“回溯点”,也可以称为剪枝点,所谓的剪枝,指的是把不会找到目标,或者不必要的路径裁剪掉。

若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束;若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

深度优先搜索(Depth First Search)

假如有编号为1-3的3张扑克牌和编号为1-3的3个盒子,现在需要将3张牌分别放到3个盒子中去,且每个盒子只能放一张牌,一共有多少种不同的放法。相当于是求全排列

向一个盒子里放入一张牌,用一个for循环即可,同时我们也需要一个标记数组(用于记录这张牌有没有被放入过),这个简单的框架如下所示

//标记数组flag,盒子数组boxs,index表示现在走到哪一个盒子面前
for(int i = 0; i < n; ++i)
{
    if(flag[i] == 0)
    {        
    	boxs[index] = i;
    	flag[i] = 1;//表示这张牌已经被放过了
    }
}

面前的盒子处理完成之后,继续处理下一个盒子,下一个盒子的处理方法和当前一样。那么把上面的代码块封装一下,给它取一个名字,即为Dfs(Depth First Search)。

//标记数组flag,盒子数组boxs,index表示现在走到哪一个盒子面前
void Dfs(int index, int n, vector<int>& boxs, vector<int>& flag)
{
    for(int i = 1; i <= n; i++)
    {
        if(flag[i] == 0)  //第i号牌仍在手上
        {
            boxs[index] = i;
            flag[i] = 1;  //现在第i号牌已经被用了
        }
    }
}

所以现在再去处理下一个盒子,直接调用Dfs(index + 1, boxs, book)即可,而回退到当前位置时,表示不放入这张牌,需要将flag数组对应牌的标记置为0。

void Dfs(int index, int n, vector<int>& boxs, vector<int>& flag)
{
    for(int i = 1; i <= n; i++)
    {
        if(book[i] == 0)  //第i号牌仍在手上
        {
            boxs[index] = i;
            flag[i] = 1;  //现在第i号牌已经被用了
            //处理下一个盒子
            Dfs(index + 1, n, boxs, flag);
            //从下一个盒子回退到当前盒子,取出当前盒子的牌,尝试放入其它牌
            flag[i] = 0;
        }
    }
}

走到尽头,也就是放到第 n+1个盒子的时候,表明前面的盒子已经放好牌了,这时候直接打印每个盒子中的牌即可。

void Dfs(int index, int n, vector<int>& boxs, vector<int>& flag)
{
    if(index == n+1)
    {
        for(int i = 1; i <= n; i++)
      		cout << boxs[i]<<" ";
    	cout << endl;
    	return; //向上回退
    }
    for(int i = 1; i <= n; i++)
    {
        if(book[i] == 0)  //第i号牌仍在手上
        {
            boxs[index] = i;
            flag[i] = 1;  //现在第i号牌已经被用了
            //处理下一个盒子
            Dfs(index + 1, n, bomxs, flag);
            //从下一个盒子回退到当前盒子,取出当前盒子的牌,尝试放入其它牌
            flag[i] = 0;
        }
    }
}

所以深度优先的模型可以抽象为

Dfs(当前这一步的处理逻辑)
{
    1. 判断边界,是否已经一条道走到黑了:向上回退
    2. 尝试当下的每一种可能,for循环
    3. 确定一种可能之后,继续下一步 Dfs(下一步)
    4. 回退 
}

员工的重要性

题目描述

给定一个保存员工信息的数据结构,它包含了员工 唯一的 id ,重要度 和 直系下属的 id 。

比如,员工1是员工2的领导,员工2是员工3的领导。他们相应的重要度为 15 , 10 , 5 。那么员工1的数据结构是 [1, 15, [2]] ,员工2的数据结构是 [2, 10, [3]] ,员工3的数据结构是 [3, 5, []] 。注意虽然员工3也是员工1的一个下属,但并不是直系下属,因此没有体现在员工1的数据结构中。

现在输入一个公司的所有员工信息,以及单个员工 id ,返回这个员工和他所有下属的重要度之和。

解题思路

边界条件是: 下属为空。该边界是隐含存在的,所以可以不写。每次先加第一个下属的重要性,按照相同的操作再去加下属的第一个下属的重要性。优化:使用unorder_map来存储员工信息。

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

class Solution {
public:
    int DFS(map<int, Employee*>& info, int id)
    {
        //当前员工的重要度 + 他的下属的重要度
        Employee* curE = info[id];
        int sum = curE->importance;
        for(const int& sub_id : curE->subordinates)
        {
            sum += DFS(info, sub_id);
        }
        return sum;
    }
    int getImportance(vector<Employee*> employees, int id) {
        if(employees.empty())
            return 0;
        //使用 unordered_map 提高查找效率
        unordered_map<int, Employee*> info;
        
        for(const auto& e : employees)
        {
            info[e->id] = e;
        }

        return DFS(info, id);
    }
};

图像渲染

题目描述

有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。你也被给予三个整数 sr , sc 和 newColor 。你应该从像素 image[sr][sc] 开始对图像进行上色填充 。

为了完成上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为 newColor 。

最后返回 经过上色渲染后的图像 。

解题思路

这题需要一个标记数组,避免陷入死循环,修改过的位置标记为true。

还要对边界条件进行检查,需要判断当前坐标是否越界。

class Solution {
public:
    void DFS(vector<vector<int>>& image, vector<vector<bool>>& flag, const int& row, const int& col, int curX, int curY, const int& Oldcolor, const int& Newcolor)
    {
        //首先判断坐标是否越界
        if(!(curX >= 0 && curX < row))
            return;
        if(!(curY >= 0 && curY < col))
            return;
        //如果该格子的颜色没被改过
        if(image[curX][curY] == Oldcolor && false == flag[curX][curY])
        {
            //修改 + 标记
            flag[curX][curY] = true;
            image[curX][curY] = Newcolor;
            //再递归修改该格子相邻的四个点
            DFS(image, flag, row, col, curX + 1, curY, Oldcolor, Newcolor);
            DFS(image, flag, row, col, curX, curY + 1, Oldcolor, Newcolor);
            DFS(image, flag, row, col, curX - 1, curY, Oldcolor, Newcolor);
            DFS(image, flag, row, col, curX, curY - 1, Oldcolor, Newcolor);
        }
    }
	vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) 	 {
        int row = image.size();//行
        int col = image[0].size();//列
        vector<vector<bool>> flag(row, vector<bool>(col, false));//标记数组
        int Oldcolor = image[sr][sc];//保存旧颜色
        DFS(image, flag, row, col, sr, sc, Oldcolor, color);
        return image;
    }
};

岛屿的周长

题目描述

给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。

网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。

岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。

解题思路

题目中说明了只有一个岛屿,就直接找规律即可。如果整个二维数组里只有一个1,那么岛屿的周长就是 1 ∗ 4 − 0 ∗ 2 = 4 1*4 - 0*2 = 4 1402=4;有2个1,周长就是 2 ∗ 4 − 1 ∗ 2 = 6 2*4 - 1*2 = 6 2412=6,…,n个1,周长是 4 ∗ n − 2 ∗ ( n − 1 ) = 2 n + 2 4*n - 2*(n-1) = 2n+2 4n2(n1)=2n+2

class Solution {
public:
    int islandPerimeter(vector<vector<int>>& grid) {
        int row = grid.size();
        int col = grid[0].size();
        int num = 0;
        for(int i = 0; i < row; ++i)
        {
            for(int j = 0; j < col; ++j)
            {
                if(grid[i][j] == 1)
                {
                    num += 4;
                    if(i > 0 && grid[i-1][j] == 1) num -= 2;
                    if(j > 0 && grid[i][j-1] == 1) num -= 2;
                }
            }
        }
        return num;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值