深度优先算法
题目:695 岛屿的最大数量
给了一个01组成的数组,1为岛屿,求其中最大岛屿的面积。
解题
使用DFS+沉岛法,把搜索过了的岛屿下沉
class Solution {
public int maxAreaOfIsland(int[][] grid) {
//特判
if(grid.length==0||grid[0].length==0)return 0;
//DFS+沉岛思想
//主函数遍历从所有点作为起点时,对其相连的面积做判断,找出最大的一块连续区域
int ans = 0;
for(int i = 0;i<grid.length;i++){
for(int j = 0;j<grid[0].length;j++){
if(grid[i][j]==1){
ans = Math.max(ans,dfs(grid,i,j));
}
}
}
return ans;
}
public int dfs(int[][] grid,int x,int y){
//返回值为该块为起点的整个岛屿的大小
//辅函数做递归,从主函数找到的第一块陆地开始,往其四周扩展,将这个岛屿的四周使用dfs的方式全部沉落
//如果越界了,或者本身就是0,则返回0
if(x<0||x>=grid.length||y<0||y>=grid[0].length||grid[x][y]==0)return 0;
//否则说明该块为岛屿,下沉其和其周围的所有岛屿
grid[x][y] = 0;
int num = 1;
num += dfs(grid,x-1,y);
num += dfs(grid,x+1,y);
num += dfs(grid,x,y-1);
num += dfs(grid,x,y+1);
return num;
}
}
剑指 Offer II 116. 朋友圈
题目:
给了一个数组,ij位置的元素表示i与j之间的关系,所有直接/间接认识的人组成了一个朋友圈,求一共有多少个朋友圈。
使用DFS+每个人是否加入朋友圈的一个一维数组实现
class Solution {
public int findCircleNum(int[][] isConnected) {
//特判
if(isConnected.length==0||isConnected[0].length==0)return 0;
//使用一个数组表示每一个元素是否有归属
//使用DFS遍历所有元素,直至所有元素都有了自己的所属
int cnt = 0;
boolean used[] = new boolean[isConnected.length];
for(int i =0;i<isConnected.length;i++){
//遍历所有元素,如果它没有被遍历过,则从它开始找到所有属于它的圈子的
if(used[i]==false){
used[i] = true;
dfs(used,isConnected,i);
cnt++;
}
}
return cnt;
}
public void dfs(boolean used[] ,int[][] isConnected,int x){
for(int j = 0;j<isConnected[0].length;j++){
if(isConnected[x][j]==1&&used[j]==false){
used[j] = true;
dfs(used,isConnected,j);
}
}
return;
}
}
417、大西洋、太平洋水流
给了一个矩阵,其中的元素表示每个位置的高度,求哪些位置放水可以流到两个洋?
解法:从每个点出发去看水能不能往低处流流入到两个洋比较耗时,不如直接从两个洋的水往高处流,看能能流到哪一些格子。看两个洋共同能流到的格子。
class Solution {
private:
vector<vector<int>> ans;
vector<int> direction{-1,0,1,0,-1};
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
//特判
if(heights.size()==0||heights[0].size()==0)return{};
//水往高处流,从大陆的边界进行遍历,找到两个海域分别能到达的位置
int m = heights.size(), n = heights[0].size();
vector<vector<bool>> pacifit_reach(m,vector<bool>(n,false));
vector<vector<bool>> atlantic_reach(m,vector<bool>(n,false));
for(int i = 0;i<m;i++){
// cout<<"i = "<<i<<" pacific"<<endl;
dfs(heights,pacifit_reach,i,0);
// cout<<"i = "<<i<<" atlantic"<<endl;
dfs(heights,atlantic_reach,i,n-1);
}
for(int j = 0;j<n;j++){
// cout<<"j = "<<j<<" pacific"<<endl;
dfs(heights,pacifit_reach,0,j);
// cout<<"j = "<<j<<" atlantic"<<endl;
dfs(heights,atlantic_reach,m-1,j);
}
for(int i = 0;i<m;i++){
for(int j =0;j<n;j++){
if(atlantic_reach[i][j]==true&&pacifit_reach[i][j]==true){
ans.push_back(vector<int>{i,j});
}
}
}
return ans;
}
void dfs(vector<vector<int>>& heights,vector<vector<bool>>& can_reach,int x,int y){
//从某块海域出发,dfs搜索其周围的所有自己可到达的,海水倒流
//只有对可以到达的地方,才使用dfs来遍历
if(can_reach[x][y]==true)return;
can_reach[x][y] = true;
cout<<"x = "<<x<<" y = "<<y<<endl;
//继续向四个方向找
for(int i = 0;i<4;i++){
int cur_x = x+direction[i];
int cur_y = y+direction[i+1];
if(cur_x>=0&&cur_x<heights.size()&&cur_y>=0&&cur_y<heights[0].size()&&heights[cur_x][cur_y]>=heights[x][y])
dfs(heights,can_reach,cur_x,cur_y);
}
}
};
257.二叉树的遍历
返回二叉树所有从根节点到叶节点的路径。
解法:使用dfs实现
注意中间一个重要的点:这里面的字符串的回溯感觉会比较麻烦,因此在往下一层传递参数的时候,就只是值传递,因此回到上一层的时候就不需要再从字符串中把东西抽出来。
在确保了下一层会有东西的时候,在函数中添加->
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> ans;
string sol;
if(root==nullptr)return ans;
dfs(root,ans,sol);
return ans;
}
void dfs(TreeNode* cur,vector<string>& ans,string sol){
//特判
//如果为叶子节点,就将当前路径添加到结果中
sol+=to_string(cur->val);
if(cur->left==nullptr&&cur->right==nullptr){
ans.push_back(sol);
}
//否则的话,继续dfs下面一层
if(cur->left!=nullptr){
dfs(cur->left,ans,sol+"->");
}
if(cur->right!=nullptr){
dfs(cur->right,ans,sol+"->");
}
return;
}
};
//Line 1034: Char 34: runtime error: addition of unsigned offset to 0x610000000040 overflowed to 0x610000000028 (stl_vector.h)
// SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/stl_vector.h:1043:34
//出现了下标-1
注意⚠️
出现问题
- Line 1034: Char 34: runtime error: addition of unsigned offset to 0x610000000040 overflowed to 0x610000000028 (stl_vector.h)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/stl_vector.h:1043:34 - 一般是因为出现了下标-1
回溯法
- 回溯法是优先搜索的特殊情况,需要记录节点状态的深度优先搜索。
- 使用条件
排列、组合、选择 - 深度搜索 vs 回溯
深度搜索:修改当前节点-递归求自节点
回溯:修改当前节点-递归求子节点-回改当前节点状态
深度遍历是不需要完全的从每个点出发、搜索每一种可能的。
但是回溯基本上都是要使用一遍
79、单词搜索
给定一个由单词组成的矩阵,去遍历这个矩阵,如果字母都与其上下左右四个方向上的字母相连。给定一个字符串,看她能不能在字母矩阵中找到。
其中回溯体现的点在于使用了一个used数组,每次使用一个元素将其设置为used(每个元素只能用一遍,避免下一层调用时候再使用一遍)、使用完后将它的使用撤回
class Solution {
private:
vector<int> direction{1,0,-1,0,1};
public:
bool exist(vector<vector<char>>& board, string word) {
//使用回溯的方法实现,来找从每个节点出发是否可以从深度遍历走到想要的字符串
int m = board.size();
int n = board[0].size();
//特判
if(m==0||n==0)return false;
vector<vector<bool>> used(m,vector<bool>(n,false));
for(int i = 0;i<board.size();i++){
for(int j = 0;j<board[0].size();j++){
if(traceback(board,used,word,0,i,j))
return true;
}
}
return false;
}
bool traceback(vector<vector<char>>& board,vector<vector<bool>>& used,string word,int cur_p,int x,int y){
//只有当前指针值相同时,才可以进入该函数
//特判
if(x<0||x>=board.size()||y<0||y>=board[0].size()||used[x][y]==true||word[cur_p]!=board[x][y]){
return false;
}
if(cur_p+1==word.length())return true;
//从当前节点出发,然后按照上下左右的方向去找是否能构成
used[x][y] =true;
for(int i=0;i<4;i++){
int cur_x = x + direction[i];
int cur_y = y+direction[i+1];
if(traceback(board,used,word,cur_p+1,cur_x,cur_y)==true)
return true;
}
used[x][y] = false;
return false;
}
};
51、N皇后
给了一个N*N的棋盘,往上放N个皇后,两个皇后不能在同一行,同一列,同一斜线。求有多少种算法。
解法:使用回溯算法,遍历所有的可能。
注意:其中希望能高效的求出是否有两个元素会在同一条斜线上,因此需要使用一个集合set来记录哪一些斜线上面已经有值了。
class Solution {
private:
vector<vector<string>> ans;
public:
vector<vector<string>> solveNQueens(int n) {
//特判
if(n==0)return {};
//使用回溯算法
//定义已经使用向量
vector<bool> used(n,false);
vector<string> sol(n,string(n,'.'));
//标明两个斜线上面的数组
auto diagonals1 = unordered_set<int>();
auto diagonals2 = unordered_set<int>();
//定义返回列表
traceback(n,0,used,sol,diagonals1,diagonals2);
return ans;
}
void traceback(int n,int level, vector<bool>& used,vector<string> sol,unordered_set<int>& diagonals1,unordered_set<int>& diagonals2){
//特判
if(level==n){
ans.push_back(sol);
return;
}
for(int j = 0;j<n;j++){
//否则的化为当前行寻找合适的位置
if(used[j]==false){
if(diagonals1.find(level-j)!=diagonals1.end())continue;
if(diagonals2.find(level+j)!=diagonals2.end())continue;
diagonals1.insert(level-j);
diagonals2.insert(level+j);
used[j]=true;
sol[level][j] = 'Q';
traceback(n,level+1,used,sol,diagonals1,diagonals2);
sol[level][j] = '.';
used[j]=false;
diagonals1.erase(level-j);
diagonals2.erase(level+j);
}
}
}
};
使用了unordered_set,其基本操作有find、insert、erase等。
find如果找不到元素,会返回set的end。
广度优先算法
使用队列实现。
适用于找最短路径等。
与深度优先都可以实现可达性问题,即一个点能否到达另一个点。
934、最短的桥
给了一个由01组成的矩阵,其中0表示水,1表示陆地。其中有且仅有两块陆地,求最少需要填多少块陆地来使得两个岛屿联通。
解答:实际上就是求两个岛屿之间的最短的路径长度。
使用DFS先求出一整个岛屿,将其放进队列中去,然后再从这个队列出发,使用BFS一直遍历到另外一个岛屿,其中的步数就是最短路径长度。
注意:其中很巧妙的将已经遍历过的岛屿和水都使用2来标记,使得在DFS寻找整个岛屿(类似于沉岛的思想)和使用BFS来找另一个岛屿的时候都不会出现有回头路的可能。而且在BFS找最短路的时,由于如果有岛屿遍历到了为2的位置,说明前面已经有人走过这了,此时遍历这个的人已经是慢一步的,因此不需要继续了,可以剪枝。
class Solution {
private:
vector<int> direction{-1,0,1,0,-1};
int m,n;
public:
int shortestBridge(vector<vector<int>>& grid) {
//找两座岛之间的最短距离
m = grid.size();
n = grid[0].size();
//先使用DFS方法找到一个岛,把它的坐标存入到队列里面
queue<pair<int,int>> island1;
bool flag = false;
for(int i = 0;i<m&&flag==false;i++){
for(int j = 0;j<n&&flag==false;j++){
if(grid[i][j]==1){
getisland1(grid,island1,i,j);
flag = true;
}
}
}
//使用BFS的方法,去找两座岛之间的最小距离
//从islands1出发,BFS搜索,直至第一个找到值为1的格子
int distance =0;
while(!island1.empty()){
distance++;
int num = island1.size();
for(int i = 0;i<num;i++){
auto [x,y]= island1.front();
island1.pop();
//朝四个方向找
for(int j=0;j<4;j++){
int cur_x = x+direction[j];
int cur_y = y+direction[j+1];
if(cur_x>=0&&cur_x<m&&cur_y>=0&&cur_y<n){
if(grid[cur_x][cur_y]==1)return distance-1;
if(grid[cur_x][cur_y]==2)continue;
island1.push({cur_x,cur_y});
grid[cur_x][cur_y]=2;
}
}
}
}
return 0;
}
void getisland1(vector<vector<int>>& grid,queue<pair<int,int>>& island1,int x,int y){
//特判
//如果定位已经超出了边界||不是陆地,就返回
if(x<0||x>=m||y<0||y>=n||grid[x][y]==0||grid[x][y]==2)return;
//否则将其加入到队列中,并且递归判断其四周的点是否为小岛
island1.push({x,y});
grid[x][y] = 2;
for(int i = 0;i<4;i++){
int cur_x = x+direction[i];
int cur_y = y+direction[i+1];
getisland1(grid,island1,cur_x,cur_y);
}
return;
}
};
310
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if(n==2)return edges[0];
vector<vector<int>> edges_pic(n,vector<int>(n,0));
for(int i = 0;i<edges.size();i++){
int x= edges[i][0];
int y = edges[i][1];
edges_pic[x][y] = 1;
edges_pic[y][x] = 1;
}
//给一棵树的图,求这棵树的最小高度
auto ans = unordered_map<int,vector<int>>();
//从每个节点出发,找到每个根节点对应的高度
for(int i= 0;i<n;i++){
queue<int> queue;
queue.push(i);
unordered_set<int> set;
set.insert(i);
int level = bfs(n,edges_pic,set,queue,i);
ans[level].push_back(i);
}
for(int i =0;i<n;i++){
//遍历所有,找到最小的n值
if(ans.count(i)!=0)return ans[i];
}
return vector<int>{0};
}
int bfs(int n, vector<vector<int>>& edges,unordered_set<int> set,queue<int>& queue,int cur_i){
cout<<"进入函数,cur_i="<<cur_i<<endl;
//按照bfs的方式按层搜索节点,直至队列中没有元素
int level = 0;
while(!queue.empty()){
level++;
if(set.size()==n)break;
// cout<<"level="<<level<<endl;
//还有元素时,就批量处理这一组元素
int queue_n = queue.size();
// cout<<"queue_n = "<<queue_n<<endl;
for(int i =0;i<queue_n;i++){
int point = queue.front();
// cout<<"queue.front执行完,point ="<<point<<endl;
queue.pop();
// cout<<"queue.pop执行完"<<endl;
//从这个元素出发,找与他相连但是没有被遍历过的界定啊
for(int j= 0;j<n;j++){
// cout<<"j = "<<j<<endl;
// cout<<"edge ="<<edges[point][j]<<endl;
if(edges[point][j]==1&&set.count(j)==0){
//相连,且这个节点不在set中
// cout<<"符合条件,要加入set的有"<<j<<endl;
queue.push(j);
set.insert(j);
// cout<<"set.size = "<<set.size()<<endl;
}
}
}
}
// cout<<"level = "<<level<<endl;
return level;
}
};
方法应该没问题,但是43个用例时超时了
修改
注意到越在这个图中央的点约有可能是拥有最小高度的树的root节点,因此类似剥洋葱的方法,其中有出度为1的节点为当前的最外面一层的节点。需要把这个节点剥掉,一层一层找到最里面的节点,那么从这个/这些节点出发,往外面走的最长路径最小,整棵树的高度越小。
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
//使用剥洋葱的方法,其中出度为1的节点表示洋葱中的最外一层。
//首先处理边节点,然后找出所有当前的出度为1的节点
//邻接节点map
unordered_map<int,vector<int>> edge_map;
//每个节点的出度
vector<int> degree(n,0);
//特判
if(n==1){
return vector<int>{0};
}
for(vector<int> edge:edges){
int v0 = edge[0];
int v1 = edge[1];
edge_map[v0].push_back(v1);
edge_map[v1].push_back(v0);
degree[v0]++;
degree[v1]++;
}
//所有出度为1的节点组成的队列
queue<int> edge_queue;
//找出所有出度为1的节点
for(int i =0;i<n;i++){
if(edge_map[i].size()==1){
edge_queue.push(i);
}
}
//从当前这些节点出发,进行bfs的搜索,其中最后一层剩下的就是
return bfs(edge_queue,degree,edge_map);
}
vector<int> bfs(queue<int> edge_queue,vector<int> degree,unordered_map<int,vector<int>> edge_map){
vector<int> ans;
while(!edge_queue.empty()){
//只要它不为空就可以
int num = edge_queue.size();
cout<<"num = "<<num<<endl;
//将里面当前的所有元素加入到一个vector中去,可能是返回值
ans.clear();
for(int i =0;i<num;i++){
//取当前队列的头
int cur_v = edge_queue.front();
// cout<<"cur_v ="<<cur_v<<endl;
edge_queue.pop();
degree[cur_v]--;
ans.push_back(cur_v);
//找与它相邻的节点,将他们的度--;
for(int v:edge_map[cur_v]){
degree[v]--;
if(degree[v]==1)
edge_queue.push(v);
}
}
}
return ans;
}
};
//queue的操作是push
//set的插入是insert
各种集合的操作整理
//queue的操作是push
//set的插入是insert