dfs时间复杂度_从零单排leetcode第十三期之DFS(1):各种各样的DFS 上

9ee99ef675e076c7c03a86905acb6dc5.png

从本期开始我们又到了深度优先搜索(即DFS),DFS和之前讲的回溯法在大多数情况下基本是一样的,所以我们讲起来可能会比较轻松。不过在这个部分我们还要探讨更多的比较深入的事情,包括引入“状态”以及在搜索的时候添加记忆环节。我们会遇到一类甲乙相互对抗的博弈问题,我们会看到更加灵活的剪枝策略以及从top-down和bottom-up两个方向来对比DFS和动态规划的联系。

首先我们做一个概括,DFS都有哪些经典应用场景呢?我们列举了以下几个:

  1. 递归用于解决问题时,也是之前“一切战术转递归”的实例
  2. 二维网格中进行DFS
  3. 在树或者图结构中进行DFS
  4. 探索问题解法的时候

本讲作为DFS的第一讲,我们先广泛的掌握一些例子,就能很快把DFS应用在很多题目中。

DFS用在递归中

105. Construct Binary Tree from Preorder and Inorder Traversal(Medium)
Given preorder and inorder traversal of a tree, construct the binary tree. Note:
You may assume that duplicates do not exist in the tree.
For example, given preorder = [3,9,20,15,7] inorder = [9,3,15,20,7]

这个问题给出了先序和中序结果,那么我们知道一进来先序的第一个就是当前的根节点。当我们在中序的结果里面找到这个点的时候,前面已经遍历的都是左子树,还没有遍历的是右子树,那么就可以把inorder和preorder分开,继续进行相同的操作,一直这么下去,函数的返回是左右子树,中间点接收下层输出即可。

不过要做两点优化:第一,在递归的过程中,并不是真的把数组分开,而是用左右两个标识来标记需要访问的范围(因为每次都是访问连续的区间)。第二,根据先序和中序的规律,中序经过了多少个元素,先序也经过多少个元素,就刚好是全部左子树。

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n=preorder.size();
        TreeNode* res=gen(preorder,inorder,0,n,0,n);
        return res;
    }
    TreeNode* gen(vector<int>& preorder, vector<int>& inorder,int lp,int rp,int li,int ri){//1
        if(lp>=rp||li>=ri) return NULL;
        if(lp==rp-1){
            TreeNode* root=new TreeNode(preorder[lp]);
            return root;
        }
        int head=preorder[lp];
        int spliti=-1,cnt=0;
        for(spliti=li;spliti<ri;spliti++){
            if(inorder[spliti]==head){
                break;
            }
            cnt++;
        }
        TreeNode* root=new TreeNode(head);
        root->left=gen(preorder,inorder,lp+1,lp+cnt+1,li,spliti);
        root->right=gen(preorder,inorder,lp+cnt+1,rp,spliti+1,ri);
        return root;
    }
  1. 这里的lp,rp,li,ri就是四个标识,其代表left_preorder,right_order,left_inorder和right_inorder。

二维网格中进行DFS

200. Number of Islands(Medium)
Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

二维网格也是比较经典的一类问题,在下一讲我们会详细探讨各种变形。这个题目本身很简单,只是用来举例在这类问题中DFS应该怎么写。

想像有一个点标记为1,我们从这个点进入开始“扩散”,向四个方向移动,碰到边界就消失。为了防止回到原来的点,我们设置visited数组来记录这个点是不是被访问过了,但是和前面回溯法不一样的地方在于,此处不需要visited复原。从一个没访问的点进入,经过“扩散”就可以把所有和它相连的点标记到visited中。那么我们在外层循环,就可以访问所有的点。小岛的个数,其实就是我们进入点并且“扩散”的次数。

那么这样做的时间复杂度是多少呢?由于每一个点只需要一次访问就会设置visited,所以时间复杂度就是所有1的个数,也就是

的,mn分别是二维网格的高和宽。
int numIslands(vector<vector<char>>& grid) {
        int m=grid.size();
        if(m<1) return 0;
        int n=grid[0].size();
        vector<int> row(n,0);
        vector<vector<int>> visited(m,row);
        int res=0;
        for(int i=0;i<m;i++){//1
            for(int j=0;j<n;j++){
                if(visited[i][j]==1||grid[i][j]=='0') continue;
                res++;
                gen(grid,visited,i,j,m,n);
            }
        }
        return res;
    }
    void gen(vector<vector<char>>& grid,vector<vector<int>>& visited, int i,int j,int m,int n){
        if(i<0||i>=m||j<0||j>=n||grid[i][j]=='0'||visited[i][j]==1) return;
        visited[i][j]=1;//2
        gen(grid,visited,i-1,j,m,n);//3
        gen(grid,visited,i+1,j,m,n);
        gen(grid,visited,i,j-1,m,n);
        gen(grid,visited,i,j+1,m,n);
        return;
    }
  1. 这里是外层循环,只有符合grid不为0并且没被访问过的才会进入内层“扩散”
  2. 设置visited,这里和回溯法的区别要注意,visited不需要被复原
  3. 上下左右扩散,重复一样的过程

下期预告


从零单排leetcode第十四期之DFS(2):各种各样的DFS 下

往期回顾

从零单排leetcode​zhuanlan.zhihu.com
7af783e9dbb3958205294192eb6251bc.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值