深度优先搜索(Depth First Search)------ 一条道走到黑
Dfs(当前这一步的处理逻辑) {
1. 判断边界,是否已经一条道走到黑了:向上回退
2. 尝试当下的每一种可能
3. 确定一种可能之后,继续下一步 Dfs(下一步)
}
题目目录:
第1题 盒子放卡牌
第2题 员工的重要性
第3题 图像渲染
第4题 岛屿的周长
第5题 被围绕的区域
第6题 岛屿数量
第7题 岛屿的最大面积
第1题 盒子放卡牌
/*
深度优先搜索(Depth First Search)------ 一条道走到黑
n个盒子放n张牌的方法总数
*/
#include <iostream>
#include <vector>
using namespace std;
void Dfs(int index, int n, vector<int> &boxs, vector<int> &book)
{
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;
book[i] = 1;
//现在第i号牌已经被用了
//处理下一个盒子
Dfs(index + 1, n, boxs, book);
//从下一个盒子回退到当前盒子,取出当前盒子的牌,
//尝试放入其它牌。
book[i] = 0;
}
}
}
/*
总结:深度优先搜索模型
Dfs(当前这一步的处理逻辑) {
1. 判断边界,是否已经一条道走到黑了:向上回退
2. 尝试当下的每一种可能
3. 确定一种可能之后,继续下一步 Dfs(下一步)
}
*/
int main(){
int n;
cin >> n;
vector<int> boxs(n + 1, 0);
vector<int> books(n + 1, 0);
Dfs(1, n, boxs, books);
return 0;
}
第2题 员工的重要性
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/employee-importance
给定一个保存员工信息的数据结构,它包含了员工唯一的id,重要度 和 直系下属的id。
比如,员工1是员工2的领导,员工2是员工3的领导。他们相应的重要度为15, 10, 5。那么:
员工1的数据结构是[1, 15, [2]],员工2的数据结构是[2, 10, [3]],员工3的数据结构是[3, 5, []]。
注意虽然员工3也是员工1的一个下属,但是由于并不是直系下属,因此没有体现在员工1的数据结构中。
现在输入一个公司的所有员工信息,以及单个员工id,返回这个员工和他所有下属的重要度之和。
示例:
输入: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1
输出: 11
解释: 员工1自身的重要度是5,他有两个直系下属2和3,而且2和3的重要度均为3。因此员工1的总重要度是 5 + 3 + 3 = 11。
注意:一个员工最多有一个直系领导,但是可以有多个直系下属员工数量不超过2000。
*/
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
struct Employee
{
int id;
int importance;
vector<int> subid;
};
/*
边界: 下属为空
每次先加第一个下属的重要性
按照相同的操作再去加下属的第一个下属的重要性
*/
class Solution
{
public:
void Dfs(vector<Employee*> &employees, int &sum, int id){
// 这里无需判断边界,for循环就是一个边界,下属为空,直接结束
// 当前员工的重要性
sum += employees[id-1]->importance;
// 遍历其下属
for(const auto& subid : employees[id-1]->subid){
Dfs(employees, sum, subid);
}
}
int getImportance(vector<Employee*> &employees, int id) {
if(employees.empty()){
return 0;
}
int sum = 0;
Dfs(employees, sum, id);
return sum;
}
};
// 使用map进行简化,就不需要进行id-1了(因为有时候员工id不一定和下标id-1匹配)
class Solution1
{
public:
void Dfs(unordered_map<int, Employee*> &info, int &sum, int id){
// 这里无需判断边界,for循环就是一个边界,下属为空,直接结束
// 当前员工的重要性
sum += info[id]->importance;
// 遍历其下属
for(const auto& id : info[id]->subid){
Dfs(info, sum, id);
}
}
int getImportance(vector<Employee*> &employees, int id) {
if(employees.empty()){
return 0;
}
//把员工信息用map存储,方便后面的使用
unordered_map<int, Employee*> info;
for(auto& e : employees){
info[e->id] = e; // 键值对:键存id,值存员工结构体指针
}
int sum = 0;
Dfs(info, sum, id);
return sum;
}
};
// 继续简化代码,使Dfs直接返回求和的值
class Solution2
{
public:
int Dfs(unordered_map<int, Employee*> &info, int id){
int sum = info[id]->importance;
for(const auto& id : info[id]->subid){
sum += Dfs(info, id);
}
return sum;
}
int getImportance(vector<Employee*> &employees, int id) {
if(employees.empty()){
return 0;
}
unordered_map<int, Employee*> info;
for(auto& e : employees){
info[e->id] = e;
}
return Dfs(info, id);
}
};
int main(){
vector<Employee*> employees(3);
employees[0] = new Employee, employees[0]->id = 1, employees[0]->importance = 5, employees[0]->subid = {2, 3};
employees[1] = new Employee, employees[1]->id = 2, employees[1]->importance = 3, employees[1]->subid = {};
employees[2] = new Employee, employees[2]->id = 3, employees[2]->importance = 3, employees[2]->subid = {};
Solution S;
cout << S.getImportance(employees, 1) << endl;
Solution S1;
cout << S1.getImportance(employees, 1) << endl;
Solution S2;
cout << S2.getImportance(employees, 1) << endl;
return 0;
}
第3题 图像渲染
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/flood-fill
有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。
给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。
为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点。
接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。
将所有有记录的像素点的颜色值改为新的颜色值。
最后返回经过上色渲染后的图像。
示例:
输入:
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
输出:
[[2,2,2],[2,2,0],[2,0,1]]
解析:
在图像的正中间,(坐标(sr,sc)=(1,1)),
在路径上所有符合条件的像素点的颜色都被更改成2。
注意,右下角的像素没有更改为2,
因为它不是在上下左右四个方向上与初始点相连的像素点。
注意:
image 和 image[0] 的长度在范围 [1, 50] 内。
给出的初始点将满足 0 <= sr < image.length 和 0 <= sc < image[0].length。
image[i][j] 和 newColor 表示的颜色值在范围 [0, 65535]内。
*/
#include <iostream>
#include <vector>
using namespace std;
/*
把和初始坐标开始,颜色值相同的点的颜色全部改为新的颜色,并且只要某个点颜色被更改,则继续以此点向周围渲染
比如题目的意思: 以位置(1,1)开始,向外渲染,只要渲染点的颜色值和(1,1)位置的颜色值相同,则继续向外渲染
1 1 1
1 1 0
1 0 1
2 2 2
2 2 0
2 0 1
这里每一个符合要求的点都要向四个方向渲染
边界:位置是否越界
这里需要用的标记,避免重复修改,使时间复杂度不超过O(row * col)
*/
class Solution
{
public:
void Dfs(vector<vector<int>>& image, int row, int col, vector<vector<int>>& book, int sr, int sc, int oldColor, int newColor){
// 处理当前逻辑,修改颜色,并且标记已经修改过了
image[sr][sc] = newColor;
book[sr][sc] = 1;
// 遍历每一种可能,四个方向
for(int i = 0; i < 4; ++i){
int newSr = sr + _nextPosition[i][0];
int newSc = sc + _nextPosition[i][1];
// 判断新位置是否越界
if(newSr < 0 || newSr >= row || newSc < 0 || newSc >= col){
continue;
}
// 如果颜色符合要求,并且之前也没有渲染过,则继续渲染
if(image[newSr][newSc] == oldColor && book[newSr][newSc] == 0){
Dfs(image, row, col, book, newSr, newSc, oldColor, newColor);
}
}
}
void floodFill(vector<vector<int>>& image, int sr, int sc, int newColor){
if(image.empty()){
return;
}
int row = image.size();
int col = image[0].size();
// 建立标记
vector<vector<int>> book(row, vector<int>(col, 0));
// 获取旧的颜色
int oldColor = image[sr][sc];
Dfs(image, row, col, book, sr, sc, oldColor, newColor);
}
private:
// 四个方向的位置更新:顺时针更新
int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
};
void print_image(vector<vector<int>>& image){
for(int i = 0; i < image.size(); ++i){
for(int j = 0; j < image[0].size(); ++j){
cout << image[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
int main(){
vector<vector<int>> image = {{1, 1, 1}, {1, 1, 0}, {1, 0, 1}};
print_image(image);
Solution S;
S.floodFill(image, 1, 1, 2);
print_image(image);
return 0;
}
第4题 岛屿的周长
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/island-perimeter
给定一个包含 0 和 1 的二维网格地图,其中 1 表示陆地 0 表示水域。
网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
示例 :
输入:[[0, 1, 0, 0],
[1, 1, 1, 0],
[0, 1, 0, 0],
[1, 1, 0, 0]]
输出: 16
*/
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int Dfs(vector<vector<int>>& island, int row, int col, vector<vector<int>>& book, int sr, int sc){
book[sr][sc] = 1;
int sum = 0;
for(int i = 0; i < 4; ++i){
int newSr = sr + _nextPosition[i][0];
int newSc = sc + _nextPosition[i][1];
// 判断新位置是否越界
if(newSr < 0 || newSr >= row || newSc < 0 || newSc >= col || island[newSr][newSc] == 0){
++sum;
continue;
}
// 如果新位置为1,且未标记过则进入
if(island[newSr][newSc] == 1 && book[newSr][newSc] == 0){
sum += Dfs(island, row, col, book, newSr, newSc);
}
}
return sum;
}
int islandPerimeter(vector<vector<int>>& island, int row, int col, int sr, int sc){
if(island.empty()){
return 0;
}
// 建立标记
vector<vector<int>> book(row, vector<int>(col, 0));
return Dfs(island, row, col, book, sr, sc);
}
private:
// 四个方向的位置更新:顺时针更新
int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
};
void print_island(vector<vector<int>>& island){
for(int i = 0; i < island.size(); ++i){
for(int j = 0; j < island[0].size(); ++j){
cout << island[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
int main(){
vector<vector<int>> island = {{0, 1, 0, 0}, {1, 1, 1, 0}, {0, 1, 0, 0}, {1, 1, 0, 0}};
print_island(island);
int row = island.size();
int col = island[0].size();
int sr = -1, sc = -1;
// 找出第一个 1
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(island[i][j] == 1){
sr = i;
sc = j;
break;
}
}
if(sr != -1){
break;
}
}
if(sr == -1){
cout << "inexistence island !!" << endl;
}
else{
Solution S;
cout << S.islandPerimeter(island, row, col, sr, sc) << endl;
}
return 0;
}
第5题 被围绕的区域
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/surrounded-regions
给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。
找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。
任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。
如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
*/
#include <iostream>
#include <vector>
using namespace std;
/*
本题的意思被包围的区间不会存在于边界上,所以边界上的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:
void Dfs(vector<vector<char>>& area, int row, int col,int sr, int sc ){
// 当前位置设为'#'
area[sr][sc] = '#';
for(int i = 0; i < 4; ++i){
// 向四个方向扩散
int newSr = sr + _nextPosition[i][0];
int newSc = sc + _nextPosition[i][1];
// 判断新位置是否越界
if(newSr < 0 || newSr >= row || newSc < 0 || newSc >= col){
continue;
}
// 是'O'说明和边联通,继续搜索是否还有联通的
if(area[newSr][newSc] == 'O'){
Dfs(area, row, col, newSr, newSc);
}
}
}
void surroundingArea(vector<vector<char>>& area){
if(area.empty()){
return;
}
int row = area.size();
int col = area[0].size();
// 寻找边上的每一个o,如果没有,表示所有的o都被包围
// 寻找第一行和最后一行
for(int i = 0; i < col; ++i){
if(area[0][i] == 'O'){
Dfs(area, row, col, 0, i);
}
if(area[row - 1][i] == 'O'){
Dfs(area, row, col, row - 1, i);
}
}
// 寻找第一列和最后一列
for(int i = 0; i < row; ++i){
if(area[i][0] == 'O'){
Dfs(area, row, col, i, 0);
}
if(area[i][col - 1] == 'O'){
Dfs(area, row, col, i, col - 1);
}
}
// 将被包围的O替换成X
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(area[i][j] == 'O'){
area[i][j] = 'X';
}
else if(area[i][j] == '#'){
area[i][j] = 'O';
}
}
}
}
private:
// 四个方向的位置更新:顺时针更新
int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
};
void print_area(vector<vector<char>>& area){
for(int i = 0; i < area.size(); ++i){
for(int j = 0; j < area[0].size(); ++j){
cout << area[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
int main(){
vector<vector<char>> area = {{'X', 'X', 'X', 'X'}, {'X', 'O', 'O', 'X'}, {'X', 'X', 'O', 'X'}, {'X', 'O', 'X', 'X'}};
print_area(area);
Solution S;
S.surroundingArea(area);
print_area(area);
return 0;
}
第6题 岛屿数量
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-islands
给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。
一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
1 1 1 1 0
1 1 0 1 0
1 1 0 0 0
0 0 0 0 0
输出: 1
示例 2:
输入:
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出: 3
*/
#include <iostream>
#include <vector>
using namespace std;
/*
本题的意思是连在一起的陆地都算做一个岛屿,本题可以采用类似渲染的做法,尝试以每个点作为渲染的起点,可以渲染
的陆地都算做一个岛屿,最后看渲染了多少次,即深度优先算法执行了多少次
*/
class Solution
{
public:
void Dfs(vector<vector<char>>& island, int row, int col, vector<vector<int>>& book, int sr, int sc){
//处理当前逻辑
book[sr][sc] = 1;
//遍历每一种可能,四个方向
for(int i = 0; i < 4; ++i){
int newSr = sr + _nextPosition[i][0];
int newSc = sc + _nextPosition[i][1];
//判断新位置是否越界
if(newSr < 0 || newSr >= row || newSc < 0 || newSc >= col){
continue;
}
//如果符合要求,并且之前也没有标记过,则继续
if(island[newSr][newSc] == '1' && book[newSr][newSc] == 0){
Dfs(island, row, col, book, newSr, newSc);
}
}
}
int islandNum(vector<vector<char>>& island){
if(island.empty()){
return 0;
}
int num = 0;
int row = island.size();
int col = island[0].size();
vector<vector<int>> book(row, vector<int>(col, 0));
//以每一个网格点为起点开始
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(island[i][j] == '1' && book[i][j] == 0){
++num;
Dfs(island, row, col, book, i, j);
}
}
}
return num;
}
private:
// 四个方向的位置更新:顺时针更新
int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
};
void print_island(vector<vector<char>>& island){
for(int i = 0; i < island.size(); ++i){
for(int j = 0; j < island[0].size(); ++j){
cout << island[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
int main(){
vector<vector<char>> island = {{'1', '1', '1', '1', '0'}, {'1', '1', '0', '1', '0'}, {'1', '1', '0', '0', '0'}, {'0', '0', '0', '0', '0'}};
print_island(island);
Solution S;
cout << S.islandNum(island) << endl;
vector<vector<char>> island1 = {{'1', '1', '0', '0', '0'}, {'1', '1', '0', '0', '0'}, {'0', '0', '1', '0', '0'}, {'0', '0', '0', '1', '1'}};
print_island(island1);
cout << S.islandNum(island1) << endl;
return 0;
}
第7题 岛屿的最大面积
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/max-area-of-island
给定一个包含了一些 0 和 1 的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。你可以假设二维矩阵的四个边缘都被水包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0。)
示例 1:
{0,0,1,0,0,0,0,1,0,0,0,0,0},
{0,0,0,0,0,0,0,1,1,1,0,0,0},
{0,1,1,0,1,0,0,0,0,0,0,0,0},
{0,1,0,0,1,1,0,0,1,0,1,0,0},
{0,1,0,0,1,1,0,0,1,1,1,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0},
{0,0,0,0,0,0,0,1,1,1,0,0,0},
{0,0,0,0,0,0,0,1,1,0,0,0,0}}
对于上面这个给定矩阵应返回 6。注意答案不应该是11,因为岛屿只能包含水平或垂直的四个方向的‘1’。
示例 2:
{{0,0,0,0,0,0,0,0}}
对于上面这个给定的矩阵, 返回 0。
注意: 给定的矩阵grid 的长度和宽度都不超过 50。
*/
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int Dfs(vector<vector<int>>& island, int row, int col, vector<vector<int>>& book, int sr, int sc){
//处理当前逻辑
int area = 1;
book[sr][sc] = 1;
//遍历每一种可能,四个方向
for(int i = 0; i < 4; ++i){
int newSr = sr + _nextPosition[i][0];
int newSc = sc + _nextPosition[i][1];
//判断新位置是否越界
if(newSr < 0 || newSr >= row || newSc < 0 || newSc >= col){
continue;
}
//如果符合要求,并且之前也没有标记过,则继续
if(island[newSr][newSc] == 1 && book[newSr][newSc] == 0){
area += Dfs(island, row, col, book, newSr, newSc);
}
}
return area;
}
int maxAreaOfIsland(vector<vector<int>>& island){
if(island.empty()){
return 0;
}
int maxArea = 0;
int row = island.size();
int col = island[0].size();
vector<vector<int>> book(row, vector<int>(col, 0));
//以每一个网格点为起点开始
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(island[i][j] == 1 && book[i][j] == 0){
maxArea = max(maxArea, Dfs(island, row, col, book, i, j));
}
}
}
return maxArea;
}
private:
// 四个方向的位置更新:顺时针更新
int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
};
void print_island(vector<vector<int>>& island){
for(int i = 0; i < island.size(); ++i){
for(int j = 0; j < island[0].size(); ++j){
cout << island[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
int main(){
vector<vector<int>> island = {{0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
{0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0},
{0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}};
print_island(island);
Solution S;
cout << S.maxAreaOfIsland(island) << endl;
return 0;
}