【算法模板】DFS秒杀模板—附练习题
📒博客首页:铁甲小宝同学 📒
🌞文章目的:DFS解题模板分享 🌞
🙏博主也在学习阶段,如若发现问题,请告知,非常感谢🙏
💗同时也非常感谢各位小伙伴们的支持 💗
🌈每日一语:心之所愿,无所不能! 🌈
? オレはいつかこの一味にも负けない仲间を集めて、世界一の财宝を见つけて、绝対なってやる!海贼王に!
? 总有一天,我会聚集一群不输给这些人的伙伴,并找到世界第一的财宝,我要当海贼王!!!
相关算法模板推荐:
DFS算法简介
DFS 其实叫深度优先搜索算法,起始它只是一种搜索的方法思路,并没有固定的算法格式。我们通常形容他是一条路走到黑。
事实上,深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。
举例说明之:下图是一个无向图,如果我们从A点发起深度优先搜索(以下的访问次序并不是唯一的,第二个点既可以是B也可以是C,D),则我们可能得到如下的一个访问过程:A->B->E(没有路了!回溯到A)->C->F->H->G->D(没有路,最终回溯到A,A也没有未访问的相邻节点,本次搜索结束).简要说明深度优先搜索的特点:每次深度优先搜索的结果必然是图的一个连通分量.深度优先搜索可以从多点发起.如果将每个节点在深度优先搜索过程中的"结束时间"排序(具体做法是创建一个list,然后在每个节点的相邻节点都已被访问的情况下,将该节点加入list结尾,然后逆转整个链表),则我们可以得到所谓的"拓扑排序",即topological sort。
DFS复杂度的分析
DFS 算法是一一个递归算法,需要借助一个递归工作栈,故它的空问复杂度为O(V) 。
遍历图的过程实质上是对每个顶点查找其邻接点的过程,其耗费的时间取决于所采用结构。
邻接表表示时,查找所有顶点的邻接点所需时间为O(E) ,访问顶点的邻接点所花时间为O(V) ,此时,总的时间复杂度为O(V+E) 。
邻接矩阵表示时,查找每个顶点的邻接点所需时间为O(V) ,要查找整个矩阵,故总的时间度为O(V^2) 。
v 为图的顶点数,E 为边数。
DFS算法的基本思路
深度优先遍历图的方法是,从图中某顶点v出发:
DFS代码模板(Java版和Python版)
前言:
因为博主也是双语言使用者,但是由于对Java 基础的不扎实之前的模板和题解就没有写Java 版的。但是我觉得还是要挑战一下自己,因为这样不仅可以帮助的学习Java 的小伙伴们,而且还能提升博主自己的Java 基础水准(在用Java写算法的时候是真的痛苦5555)。看在博主这么用心的份上大伙来个一键三联吧!!!好了,话不多说直接开始上模板!
排列类型模板
众所周知,在LeetCode 上我们经常能看见一些排列的问题,我们就拿全排列 来举例吧。
题目:全排列
给定一个不含重复数字的数组nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例 2:
输入:nums = [0,1] 输出:[[0,1],[1,0]] 示例 3:
输入:nums = [1] 输出:[[1]]
从题目中我们能大概的了解题意—就是把一个nums 数组中的所有的数拿来进行一个打乱顺序的排序。这种类型的题就是输入DFS 中的回溯剪枝 问题。而且本题还是面试中的高频题!!!
**思路:**本题就是通过一个递归 然后一直给path 路径数组添加新的元素。最后如果path 数组的长度是等于nums 数组的长度,则则需要把path 数组添加到我们最终需要返回的数组res 里面。而本题的核心就在于剪枝 和递归的终止条件 问题上面。剪枝是需要在递归时不会让path 数组反复把同一个元素添加到里面,而是添加nums 数组中不同的元素。而递归的终止条件 在上面我们也说过,就是如果路径数组path 的长度达到了和nums 数组相同的长度时,我们就需要终止其继续递归并把路径数组path 添加到结果数组res 里面。
欧克,大体的思路就是这样。为了更深入的了解本题,我们可以继续向下看看本题的解决代码。
Java版本:
class Solution {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
public List<List<Integer>> permute(int[] nums) {
int n = nums.length;
dfs(n,nums);
return res;
}
public void dfs(int n,int[] nums){
if(path.size() == n){
res.add(new ArrayList<Integer>(path));
return ;
}
for (int i = 0 ; i < n ; i ++){
if (path.contains(nums[i])){
continue;
}
path.add(nums[i]);
dfs(n , nums);
path.remove(path.size() - 1);
}
}
}
Python版本:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def dfs(path,nums):
if len(nums) == len(path):
res.append(path[:])
return
for i in nums:
if i in path:
continue
dfs(path + [i] , nums)
dfs([],nums)
return res
**本题小结:**通过上面的思路和代码,我们能也能总结出来dfs 方法的一些特点:
- dfs需要一直递归到一个终止条件。
- 在使用路径数组path时我们可以发现,其实path数组就是一个栈的形式,每次都会有
入栈 和出栈 。 - 递归当中其实就好比一个
树 ,需要不断的剪枝和终止条件。
其实dfs掌握上面的核心特点,在做题的时候我们就能知道大概的思路。因为有些题就是在这个上面稍微做了一个简单的修改,我们只需要掌握核心科技 ,妈妈就不用担心我不会做dfs类型的题啦。
在做过了全排列 这个简单的DFS 习题后我们接着再来一个简单的DFS 习题吧!
题目: 子集 II
给你一个整数数组nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] 示例 2:
输入:nums = [0] 输出:[[],[0]]
从题目我们大概的能知道:
OK,我们做题知道了大题的思路后我们就更容易的下手来完成这个题目了。
思路:
首先我们知道这个nums里面包含有重复的元素,但是题目没有给我们说是不是有序的。所有这个我们首先需要对这个nums 数组进行一个排序。我们还知道解集中不让出现重复的解,也就是说如果在nums 数组里面有相同的元素一般会有两个相同的解。所有为了去除这个重复的解我们就需要在循环时跳过相同的元素,避免出现相同的结果。
大体思路我们已经分析了,接下来就是对代码的逐一分析。
Java版本:
class Solution {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
dfs(nums,0);
return res;
}
void dfs(int[] nums,int idx){
res.add(new ArrayList<Integer>(path));
if (path.size() == nums.length){
return ;
}
for (int i = idx ; i < nums.length ; i ++){
if (i > idx && nums[i-1] == nums[i]) continue;
path.add(nums[i]);
dfs(nums,i + 1);
path.remove(path.size() - 1);
}
}
}
Python版本:
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
def dfs(begin,path):
for i in range(begin,len(nums)):
if i>begin and nums[i-1] == nums[i]:
continue
path.append(nums[i])
res.append(path[:])
dfs(i+1,path)
path.pop()
res = []
nums.sort()
if len(nums) == 0:
return [[]]
res.append([])
dfs(0,[])
return res
欧克,上面两题就是一个大概的DFS 模板。大家只需要记住DFS 核心就是需要递归 和一个栈 。
接下来还是老规矩——给大家加餐哦!!
老规矩,博主可是使用模板 都给拿捏了哦。大家也要加油哦!!!
树类型模板
我们在写一些树 的算法题的时候其实最常用的就是DFS 。因为我们需要使用一直递归直到找到树 的叶子节点,才能直到这棵树的深度且有办法继续去写。那么说到这里我们就来一道求深度的算法题吧!
题目:剑指 Offer 55 - I. 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3,9,20,null,null,15,7],
? 3
/ 9 20 / 15 7 返回它的最大深度 3 。
从本题我们能大概的直到本题的目的就是求一颗二叉树的最大深度。
思路:求最大深度,我们可以使用递归,找到左右子树到叶子节点的深度并且取最大值。
我们可以看上图左子树的最大深度是1 ,而右子树的最大深度是2 。则我们取深度的最大值并加上1 (根节点本身也算1 个深度)就是我们所需要得到的结果了。(图做的有点抽象,大家别建议5555)。
Java版本:
class Solution {
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
Python版本:
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return max(self.maxDepth(root.left),self.maxDepth(root.right)) + 1
因为树 的很多大部分都是递归写法,和这个相差不太大,所以我就只给上面一个例题。接下来就是给大家附练习题了。
表格遍历模板
本模板和BFS 其实有一些地方是相同的:最常见的就是上下左右四个方向的判断。
那我们还是拿最经典的岛屿问题 来讲解这个问题。
题目:200. 岛屿数量
题目:
给你一个由'1'(陆地) 和'0'(水) 组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [ [“1”,“1”,“1”,“1”,“0”], [“1”,“1”,“0”,“1”,“0”], [“1”,“1”,“0”,“0”,“0”], [“0”,“0”,“0”,“0”,“0”] ] 输出:1 示例 2:
输入:grid = [ [“1”,“1”,“0”,“0”,“0”], [“1”,“1”,“0”,“0”,“0”], [“0”,“0”,“1”,“0”,“0”], [“0”,“0”,“0”,“1”,“1”] ] 输出:3
其实本题使用DFS 也是使用沉岛的思想,使用循环逐一遍历这个grid 如果为1 就res 结果集就加一,且使用DFS 沉岛。
具体的流程我们可以看代码来逐步分析。
Java版本:
class Solution {
int res = 0;
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
for (int i = 0 ; i < m ; i++ ){
for (int j = 0 ; j < n ;j ++ ){
if (grid[i][j] == '1'){
res += 1;
dfs(i,j,m,n,grid);
}
}
}
return res;
}
void dfs(int x , int y,int m , int n , char[][] grid) {
if (x < 0 || y < 0 || x >= m || y >= n || grid[x][y]== '0') return ;
grid[x][y] = '0';
dfs(x - 1,y,m,n,grid);
dfs(x + 1,y,m,n,grid);
dfs(x , y + 1,m,n,grid);
dfs(x , y - 1,m,n,grid);
}
}
Python版本:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
x = len(grid)
y = len(grid[0])
def dfs (grid,i,j):
if i < 0 or j < 0 or i >= x or j >= y or grid[i][j] =='0':
return
grid[i][j] = '0'
dfs (grid,i-1,j)
dfs (grid,i+1,j)
dfs (grid,i,j-1)
dfs (grid,i,j+1)
num = 0
for i in range(x):
for j in range(y):
if grid[i][j] == '1':
dfs(grid,i,j)
num += 1
return num
因为岛屿这种表格题也是非常简单的,无论是使用DFS 还是BFS 。我们都可以套用模板来解决这种类型的问题,但是为了能更好的提高自己的算法能能力,博主还是建议大家能够去多多练习的,同时下面也整理了一些习题为大家加餐 。
总结
本文也是到了尾声,同时非常感谢能看到这里的小伙伴们,也更希望本文能对你们有所帮助。这个系列—算法模板 如果没有什么意外博主是会一直更新下去的,更希望能帮助一下初学的小伙伴们。
每一篇文章都是博主十分用心创作出来的,如果能给大家带来帮助希望大家来个一键三连 。这将会是博主最大的动力哦!!!
OK,今天的文章分享就到这里,我们下期见!!!
|