回溯算法
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。
而满足回溯条件的某个状态的点称为“回溯点”,也可以称为剪枝点,所谓的剪枝,指的是把不会找到目标,或者不必要的路径裁剪掉。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束;若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
深度优先搜索(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 1∗4−0∗2=4;有2个1,周长就是 2 ∗ 4 − 1 ∗ 2 = 6 2*4 - 1*2 = 6 2∗4−1∗2=6,…,n个1,周长是 4 ∗ n − 2 ∗ ( n − 1 ) = 2 n + 2 4*n - 2*(n-1) = 2n+2 4∗n−2∗(n−1)=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;
}
};