文章目录
一、算法核心内容
1.1深度优先搜索
1、概念:深度优先搜索(depth-first seach,DFS)在搜索到一个新的节点时,立即对该新节点进行遍历;因此遍历需要用先入后出的栈来实现,也可以通过与栈等价的递归来实现。对于树结构而言,由于总是对新节点调用遍历,因此看起来是向着“深”的方向前进。
2、举例:1—>2—>4—>3
3、作用:检测环路,记录每个遍历过的节点的父节点,若一个节点被再次遍历且父节点不同,则说明有环。
4、状态记录:对已经搜索过的节点进行标记,以防止在遍历时重复搜索某个节点,这种做法叫做状态记录。
1.2回溯法
1、回溯法(backtracking)是优先搜索的一种特殊情况,又称为试探法,常用于需要记录节点状态的深度优先搜索。通常来说,排列、组合、选择类问题使用回溯法比较方便。
2、在搜索到某一节点的时候,如果我们发现目前的节点(及
其子节点)并不是需求目标时,我们回退到原来的节点继续搜索,并且把在目前节点修改的状态还原。
3、优点:我们可以始终只对图的总状态进行修改,而非每次遍历时新建一个图来储存状态。
4、步骤: [修改当前节点状态]→[递归子节点]→[回改当前节点状态]。
5、修改情况:回溯法修改一般有两种情况,一种是修改最后一位输出,比如排列组合;一种是修改访问标记,比如矩阵里搜字符串。
1.3广度优先搜索
1、概念:广度优先搜索(breadth-first search,BFS)是一层层进行遍历的,因此需要用先入先出的队列而非先入后出的栈进行遍历。由于是按层次进行遍历,广度优先搜索时按照“广”的方向进行遍历的,也常常用来处理最短路径等问题。
2、考虑如下一颗简单的树。我们从 1 号节点开始遍历,假如遍历顺序是从左子节点到右子节点,那么按照优先向着“广”的方向前进的策略,队列顶端的元素变化过程为 [1]->[2->3]->[4],其中方括号代表每一层的元素。
二、深度优先搜索题目
2.1Leetcode695
2.1.1题目描述
给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0
2.1.2输入输出格式
[[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.1.3代码
class Solution {
public:
int direction[5]={1,0,-1,0,1};
int maxAreaOfIsland(vector<vector<int>>& grid) {
if(grid.size()==0 || grid[0].size()==0) return 0;
int ans=0;
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
ans=max(ans,dfs(grid,i,j));
}
}
return ans;
}
int dfs(vector<vector<int>>& grid,int row,int column){
if(grid[row][column]==0) return 0;
int area=1;
grid[row][column]=0;
for(int i=0;i<4;i++){
int x=row+direction[i];
int y=column+direction[i+1];
if(x>=0 && y>=0 && x<grid.size() && y<grid[0].size()){
area+=dfs(grid,x,y);
}
}
return area;
}
};
2.2Leetcode547
2.2.1题目描述
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
2.2.2输入输出格式
2.2.3代码
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
if(isConnected.size()==0 || isConnected[0].size()==0) return 0;
int n=isConnected.size();
int cnt=0;
vector<bool> flag(n,false);
for(int i=0;i<n;i++){
if(!flag[i]){
dfs(isConnected,i,flag);
cnt++;
}
}
return cnt;
}
void dfs(vector<vector<int>>& isConnected,int n, vector<bool>& flag){
flag[n]=true;
for(int i=0;i<isConnected.size();i++){
int temp=isConnected[n][i];
if(temp==1 && !flag[i]){
dfs(isConnected,i,flag);
}
}
}
};
2.3Leetcode417
2.3.1题目描述
给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
2.3.2输入输出格式
给定下面的 5x5 矩阵:
太平洋 ~ ~ ~ ~ ~
~ 1 2 2 3 (5) *
~ 3 2 3 (4) (4) *
~ 2 4 (5) 3 1 *
~ (6) (7) 1 4 5 *
~ (5) 1 1 2 4 *
* * * * * 大西洋
返回:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).
2.3.3代码
class Solution {
public:
int direction[5]={1,0,-1,0,1};
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
if(heights.size()==0 || heights[0].size()==0) {
return {};
}
vector<vector<int>> ans;
int m=heights.size(),n=heights[0].size();
//太平洋
vector<vector<bool>> can_reachp(m,vector<bool>(n,false));
//大西洋
vector<vector<bool>> can_reacha(m,vector<bool>(n,false));
for(int i=0;i<m;i++){
dfs(heights,can_reachp,i,0);
dfs(heights,can_reacha,i,n-1);
}
for(int i=0;i<n;i++){
dfs(heights,can_reachp,0,i);
dfs(heights,can_reacha,m-1,i);
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(can_reacha[i][j] && can_reachp[i][j]){
ans.push_back(vector<int>{i,j});
}
}
}
return ans;
}
void dfs(vector<vector<int>>& heights,vector<vector<bool>>& can_reach,int row,int column){
if(can_reach[row][column]) return;
can_reach[row][column]=true;
for(int i=0;i<4;i++){
int x=row+direction[i];
int y=column+direction[i+1];
if(x>=0 && y>=0 && x<heights.size() && y<heights[0].size() && heights[row][column]<=heights[x][y]){
dfs(heights,can_reach,x,y);
}
}
}
};
2.4Leetcode419
2.4.1题目描述
给定一个二维的甲板, 请计算其中有多少艘战舰。 战舰用 'X’表示,空位用 '.'表示。 你需要遵守以下规则:
给你一个有效的甲板,仅由战舰或者空位组成。
战舰只能水平或者垂直放置。换句话说,战舰只能由 1xN (1 行, N 列)组成,或者 Nx1 (N 行, 1 列)组成,其中N可以是任意大小。
两艘战舰之间至少有一个水平或垂直的空位分隔 - 即没有相邻的战舰。
2.4.2输入输出格式
2.4.3代码
class Solution {
public:
int direction[5]={1,0,-1,0,1};
int countBattleships(vector<vector<char>>& board) {
if(board.size()==0 || board[0].size()==0) return 0;
int cnt=0;
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
if(board[i][j]=='X'){
dfs(board,i,j);
cnt++;
}
}
}
return cnt;
}
void dfs(vector<vector<char>>& board,int row,int column){
if(board[row][column]=='.') return;
board[row][column]='.';
for(int i=0;i<4;i++){
int x=row+direction[i];
int y=column+direction[i+1];
if(x>=0 && y>=0 && x<board.size() && y<board[0].size()){
dfs(board,x,y);
}
}
}
};
2.5Leetcode200
2.5.1题目描述
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
2.5.2输入输出格式
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
2.5.3代码
class Solution {
public:
int direction[5]={1,0,-1,0,1};
int numIslands(vector<vector<char>>& grid) {
if(grid.size()==0 || grid[0].size()==0) return 0;
int cnt=0;
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(grid[i][j]=='1'){
dfs(grid,i,j);
cnt++;
}
}
}
return cnt;
}
void dfs(vector<vector<char>>& grid,int row,int column){
if(grid[row][column]=='0') return;
grid[row][column]='0';
for(int i=0;i<4;i++){
int x=row+direction[i];
int y=column+direction[i+1];
if(x>=0 && y>=0 && x<grid.size() && y<grid[0].size()){
dfs(grid,x,y);
}
}
}
};
2.6Leetcode130
2.6.1题目描述
给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
2.6.2输入输出格式
2.6.3代码
class Solution {
public:
int direction[5]={1,0,-1,0,1};
void solve(vector<vector<char>>& board) {
int m=board.size();
int n=board[0].size();
vector<vector<bool>> flag(m,vector<bool>(n,false));
for(int i=0;i<m;i++){
if(board[i][0]=='O' && !flag[i][0]) dfs(board,flag,i,0);
if(board[i][n-1]=='O' && !flag[i][n-1]) dfs(board,flag,i,n-1);
}
for(int i=0;i<n;i++){
if(board[0][i]=='O' && !flag[0][i]) dfs(board,flag,0,i);
if(board[m-1][i]=='O' && !flag[m-1][i]) dfs(board,flag,m-1,i);
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(board[i][j]=='O' && !flag[i][j]){
board[i][j]='X';
}
}
}
}
void dfs(vector<vector<char>>& board,vector<vector<bool>>& flag,int row,int column){
if(flag[row][column] || board[row][column]=='X') return;
flag[row][column]=true;
for(int i=0;i<4;i++){
int x=row+direction[i];
int y=column+direction[i+1];
if(x>=0 && y>=0 && x<board.size() && y<board[0].size()){
dfs(board,flag,x,y);
}
}
}
};
2.7Leetcode257
2.7.1题目描述
给定一个二叉树,返回所有从根节点到叶子节点的路径。
2.7.2输入输出格式
2.7.3代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> ans;
string temp;
dfs(root,ans,temp);
return ans;
}
void dfs(TreeNode* root,vector<string>& ans,string temp){
if(root!=nullptr){
temp+=to_string(root->val);
if((root->left==nullptr)&&(root->right==nullptr)){
ans.push_back(temp);
return ;
}else{
temp+="->";
dfs(root->left,ans,temp);
dfs(root->right,ans,temp);
}
}
return;
}
};
三、回溯法
3.1Leetcode46
3.1.1题目描述
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
3.1.2输入输出格式
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
3.1.3代码
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;
track(nums,0,ans);
return ans;
}
void track(vector<int>& nums,int level,vector<vector<int>>& ans){
if(level==nums.size()-1){
ans.push_back(nums);
return;
}
for(int i=level;i<nums.size();i++){
swap(nums[i],nums[level]);
track(nums,level+1,ans);
swap(nums[i],nums[level]);
}
}
};
3.2Leetcode77
3.2.1题目描述
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
3.2.2输入输出格式
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
3.2.3代码
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> ans;
vector<int> temp(k,0);
int count=0;
backtracking(ans,temp,count,1,n,k);
return ans;
}
void backtracking(vector<vector<int>>& ans,vector<int>& temp,int count,int pos,int n,int k){
if(count==k){
ans.push_back(temp);
return;
}
for(int i=pos;i<=n;i++){
temp[count++]=i;
backtracking(ans,temp,count,i+1,n,k);
count--;
}
}
};
3.3Leetcode79
3.3.1题目描述
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
3.3.2输入输出格式
3.3.3代码
class Solution {
public:
int direction[5]={1,0,-1,0,1};
bool exist(vector<vector<char>>& board, string word) {
if(board.empty()){
return false;
}
int m=board.size(),n=board[0].size();
vector<vector<bool>> flag(m,vector<bool>(n,false));
bool ans=false;
int size=word.size();
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
backtracking(board,flag,word,0,i,j,ans);
}
}
return ans;
}
void backtracking(vector<vector<char>>& board,vector<vector<bool>>& flag,string word,int pos,int row,int column,bool& ans){
if(board[row][column]!=word[pos] || flag[row][column] || ans){
return;
}
if(pos==word.size()-1){//返回正确值的if
ans=true;
return;
}
flag[row][column]=true;
for(int i=0;i<4;i++){
int x=row+direction[i];
int y=column+direction[i+1];
if(x>=0 && y>=0 && x<board.size() && y<board[0].size() && !flag[x][y]){
backtracking(board,flag,word,pos+1,x,y,ans);
}
}
flag[row][column]=false;//回溯
}
};
3.4Leetcode51
3.4.1题目描述
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
3.4.2输入输出格式
3.4.3代码
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> ans;//存储最终结果
vector<string> temp(n,string(n,'.'));//存储每一个解
backtracking(ans,temp,0,n);//回溯
return ans;//返回结果
}
//row:遍历每一行
//n:总行数
void backtracking(vector<vector<string>>& ans,vector<string>& temp,int row,int n){
if(row==n){
ans.push_back(temp);
return;
}
for(int col=0;col<n;col++){
if(isvalid(row,col,temp,n)){
temp[row][col]='Q';
backtracking(ans,temp,row+1,n);
temp[row][col]='.';
}
}
}
//判断是否符合下棋条件
bool isvalid(int row,int col,vector<string>& temp,int n){
for(int i=0;i<row;i++){
if(temp[i][col]=='Q') return false;
}
for(int i=row-1,j=col-1;i>=0 && j>=0;i--,j--){
if(temp[i][j]=='Q') return false;
}
for(int i=row-1,j=col+1;i>=0 && j<n;i--,j++){
if(temp[i][j]=='Q') return false;
}
return true;
}
};
四、广度优先搜索
4.1Leetcode934
4.1.1题目描述
在给定的二维二进制数组 A 中,存在两座岛。(岛是由四面相连的 1 形成的一个最大组。)
现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。
返回必须翻转的 0 的最小数目。(可以保证答案至少是 1 。)
4.1.2输入输出格式
输入:A = [[0,1,0],[0,0,0],[0,0,1]]
输出:2
4.1.3代码
class Solution {
public:
int direction[5]={1,0,-1,0,1};
int shortestBridge(vector<vector<int>>& grid) {
//dfs查找第一个岛屿,并将其置2.
int m=grid.size(),n=grid[0].size();
queue<pair<int,int>> points;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]==1){
dfs(grid,i,j,points);
goto next;
}
}
}
next:
int ans=bfs(points,0,grid);
return ans;
}
int bfs(queue<pair<int,int>>& points,int level,vector<vector<int>>& grid){
while(!points.empty()){
level++;
int points_size=points.size();
while(points_size--){
auto [r,l]=points.front();
points.pop();
for(int i=0;i<4;i++){
int x=r+direction[i];
int y=l+direction[i+1];
if(x>=0 && y>=0 && x<grid.size() && y<grid[0].size()){
if(grid[x][y]==2) continue;
else if(grid[x][y]==1) return level;
else if(grid[x][y]==0){
points.push({x,y});
grid[x][y]=2;
}
}
}
}
}
return 0;
}
void dfs(vector<vector<int>>& grid,int row,int col,queue<pair<int,int>>& points){
if(grid[row][col]==0){
points.push({row,col});
return;
}
grid[row][col]=2;
for(int i=0;i<4;i++){
int x=row+direction[i];
int y=col+direction[i+1];
if(x>=0 && y>=0 && x<grid.size() && y<grid[0].size() &&(grid[x][y]!=2)){
dfs(grid,x,y,points);
}
}
}
};
4.2Leetcode463
4.2.1题目描述
给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
4.2.2输入输出格式
输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边
4.2.3代码
class Solution {
public:
int direction[5]={1,0,-1,0,1};
int islandPerimeter(vector<vector<int>>& grid) {
int ans=0;
int m=grid.size(),n=grid[0].size();
vector<vector<bool>> flag(m,vector<bool>(n,false));
queue<pair<int,int>> points;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]==1){
points.push({i,j});
bfs(grid,flag,points,ans);
goto next;
}
}
}
next:
return ans;
}
void bfs(vector<vector<int>>& grid,vector<vector<bool>>& flag,queue<pair<int,int>>& points,int& ans){
while(!points.empty()){
auto [r,l]=points.front();
points.pop();
if(flag[r][l]) continue;
flag[r][l]=true;
int temp=4;
for(int i=0;i<4;i++){
int x=r+direction[i];
int y=l+direction[i+1];
if(x>=0 && y>=0 && x<grid.size() && y<grid[0].size()){
if(grid[x][y]==1){
temp--;
if(!flag[x][y]){
points.push({x,y});
}
}
}
}
ans+=temp;
}
}
};