简介:
找了几道关于DFS比较经典的题目,其中,最经典的题目就是N-queen.
同时,因为DFS的提出是基于图或者树的数据结构,所以找了一道关于路径总和的题目,让我们熟悉一下DFS的原始情况.
此外,海岛作为应用题也是一道很好的例子.
N-QUEEN问题描述:
原题: leetcode
N*N的棋盘放置N个棋子,棋子之间不能相互攻击,(不能位于相邻对角线,同一行,同一列)
思路:从第一行开始,往接下来的行搜索,找到答案或者发现此路不通后返回.
思考的关注点有几个:
1.返回情况(成功/失败)
既然是深度优先,那么理想情况就是从第一行直接找到最后一行.存储下当前结果.不理想的情况就是,找到中间的部分,就发现不满足条件,然后返回,所以,返回的情况有两种,已经找到了数据的最后一行,或者,当前位置的元素不满足条件.
2. 判断元素是否有效
我们要考虑同列的上部分元素,(同行的不用考虑),以及两条对角线.
所以,有三个判断条件.
3. 返回过程中的状态更新
当我们遍历了一个点的所有情况,进行返回时,之前的修改要被替换,比如第三行第一列遍历结束后,我们需要重新让Mat[3,1]='*';
对于4*4的棋局,那么可能的N queen解法如下
我提供的代码:(如果把代码粘贴到leetcode,估计接口需要改变)
/*************************************************************************
> File Name: NQueen.cpp
> Author:
> Mail:
> Created Time: Do 05 Aug 2021 09:24:22 CEST
************************************************************************/
#include <iostream>
#include <string>
#include <vector>
using namespace std;
bool isValid(vector<string> &grid, int row, int col) {
for (int i = row; i >= 0; i--) {
if (grid[i][col] == 'Q')
return false;
}
for (int i = col; i >= 0; i--) {
if (grid[row][i] == 'Q')
return false;
}
for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) {
if (grid[i][j] == 'Q') {
return false;
}
}
for (int i = row, j = col; i >= 0 && j < grid.size(); i--, j++) {
if (grid[i][j] == 'Q') {
return false;
}
}
return true;
}
void dfs(vector<vector<string>> &res, vector<string> &grid, int row) {
// end condition
if (row == grid.size()) {
res.push_back(grid);
return;
}
// iterate over col
for (int col = 0; col < grid.size(); col++) {
if (isValid(grid, row, col)) {
grid[row][col] = 'Q';
dfs(res, grid, row + 1);
grid[row][col] = '.';
}
}
}
int main() {
vector<vector<string>> res;
int n = 4;
vector<string> board(n, string(n, '.'));
// board[0][1]='Q';
for (auto &ele : board) {
cout << ele << endl;
}
dfs(res, board, 0);
cout << res.size() << endl;
for (auto &&vec : res) {
std::cout<<"******\n";
for (auto &ele : vec) {
cout << ele << endl;
}
}
// std::cout<<isValid(board,0,2)<<endl;
}
岛屿数量问题
这道题目的精髓在于
1. 避免岛屿的重复遍历,当一个点被遍历后,要及时标注
2. 当一个岛屿被找到后,要进行深度搜索,继续遍历
3. 对于已经标注过或者为0的点,可以跳过
/*************************************************************************
> File Name: island.cpp
> Author:
> Mail:
> Created Time: Do 05 Aug 2021 11:31:51 CEST
************************************************************************/
#include <iostream>
#include <vector>
using namespace std;
bool checkgrid(vector<vector<char>> &mat, int x, int y) {
if (x > 0) {
if (mat[x - 1][y] != '0')
return false;
}
if (y > 0) {
if (mat[x][y - 1] != '0')
return false;
}
return true;
}
/****
* mark if it is 1
*/
void markgrid(vector<vector<char>> &mat, int x, int y) {
mat[x][y] = '2';
if (x + 1 < mat.size()) {
if (mat[x + 1][y] == '1') {
markgrid(mat, x + 1, y);
return;
}
}
if (y + 1 < mat[0].size()) {
if (mat[x][y+1] == '1')
markgrid(mat, x, y + 1);
return;
}
return;
}
int main() {
vector<vector<char>> grid{{'1', '1', '0', '1', '0'},
{'1', '1', '0', '0', '0'},
{'1', '1', '0', '1', '0'},
{'0', '0', '1', '0', '0'}};
int count = 0;
for (int i = 0; i < grid.size(); i++) {
for (int j = 0; j < grid[0].size(); j++) {
if (grid[i][j] != '1')
continue;
if (checkgrid(grid, i, j)) {
count++;
markgrid(grid, i, j);
}
// 0 check if it is water is water nothing, is not water
// 1.check if it is a unvisisted point
// 2. if unvisisted. count++;
// 2. if visited skip
}
}
std::cout<<"count number "<<count<<endl;
}
延伸: 岛屿最大面积
关于岛屿的组大面积,相比于上一题,有两个地方要注意,传参应该包括计数的引用,用dfs搜索岛屿的时候,应该前后左右四个方向都进行搜索.
class Solution {
public:
int max(int a, int b) {return (a>b)?a:b;}
void dfs(int x,int y, int& counter,vector<vector<int>>& grid)
{
if(x<0||y<0||x>=grid.size()||y>=grid[0].size()||grid[x][y]!=1)
return;
counter++;
grid[x][y]=2;
dfs(x+1,y,counter,grid);
dfs(x,y+1,counter,grid);
dfs(x-1,y,counter,grid);
dfs(x,y-1,counter,grid);
return;
}
int maxAreaOfIsland(vector<vector<int>>& grid) {
int max_cnt=0;
for(int i=0;i<grid.size();i++)
{
for (int j=0;j<grid[0].size();j++)
{
int cnt=0;
dfs(i,j,cnt,grid);
max_cnt=max(cnt,max_cnt);
}
}
return max_cnt;
}
};
路径总和问题
建议两道题都做一下,循序渐进的感觉很爽.
简单版很简单,但是,思路却很经典,首先是搜索的终止条件要想清楚,只有当节点左右都是nullptr时才停止,此外,如果第一个节点就是nullptr,这种情况也要想好.
/**
* 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:
bool hasPathSum(TreeNode* root, int targetSum) {
if(root==nullptr)
{
return false;
}
if(root->left==nullptr &&root->right==nullptr)
return (targetSum-root->val==0);
return(hasPathSum(root->left,targetSum-root->val)||hasPathSum(root->right,targetSum-root->val));
}
};
加强版中,我们要记录搜索过程中的路线,因为,当最后满足条件时,我们要把它添加到最终答案中,
除此之外,如何传上一个时刻累计的状态也是一个值得思考的问题,如果直接传vector的话,操作起来时最方便的,但同时,内存占用会增加,所以传引用是个更合理的选择,对应的就是我们在修改了元素后,要对他进行还原,每次push_back后,除了dfs子节点之外,还要进行pop_back()
(从传值变为传引用后,效率骤增)
此外,我还尝试了将push_back变为emplace_back,事实上,效率不升反降,所以,emplace_back提升效率的场景是有限的
我贴上效率最高的一个答案
/**
* 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<vector<int>> pathsum;
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<int> roadtmp;
DFS(root,targetSum,roadtmp);
return pathsum;
}
void DFS(TreeNode* root, int sum, vector<int>& road)
{
if(root==nullptr)
{
return;
}
road.push_back(root->val);
if(root->left==nullptr&&root->right==nullptr)
{
if(sum==root->val)
{
pathsum.push_back(road);
}
}
DFS(root->left,sum-root->val,road);
DFS(root->right,sum-root->val,road);
road.pop_back();
return;
}