恭喜发现宝藏!微信搜索公众号【TechGuide】关注更多新鲜好文和互联网大厂的笔经面经。
作者@TechGuide【全网同名】
点赞再看,养成习惯,您动动手指对原创作者意义非凡🤝
文章目录
前言
在笔者大大小小笔试了五六场之后,似乎得到了一点规律,互联网大厂的笔试题集中在两大块,一是dp动态规划,另外一个就是dfs深度优先搜索(也包含回溯问题)了。之前笔试的时候,总是觉得面对这种类型的题目,即使勉强写出来了,也远远没有达到融会贯通的程度,很是苦恼。看了几十篇高赞高评论的总结对比文章后,并没有觉得有什么帮助,我总结了两个原因,第一个原因就是作者讲的太浅了,最然容易理解,却无法实际运用到更加复杂的场景中,另一个原因就是,很多别人得出的宝贵结论,你因为缺少自己的思考和大量题目的训练而无法感同身受,因而无法体会到其中的精妙之处。所以,我最后总结出的解决dfs的最好方法是我要打十个!!!:
做
出
l
e
e
t
c
o
d
e
问
题
中
d
f
s
专
项
下
面
的
所
有
题
目
!
做出leetcode问题中dfs专项下面的所有题目!
做出leetcode问题中dfs专项下面的所有题目!
(咋感觉比打十个还猖狂。。orz)
正文
以下是关于dfs的所有中等难度题目,一共
给大家感受一下题量,剩下的就不粘了,完整版可以看这里。所以这也告诉我们,题目是千变万化的,只有具备解出dfs题目的能力才是关键。然鹅!!这里一共有90道左右,我。。。后悔了。。。但但是!!规律我一定要找到,总结给大家,前提是你能像我一样自己绞尽脑汁、花了大量时间在上面钻研,你才能有所感悟。
1. 路径和问题
(代码已详细注释,注释很细节,是引导思维的)
//Given a binary tree and a sum, find all root-to-leaf paths where each path's s
//um equals the given sum.
//
// Note: A leaf is a node with no children.
//
// Example:
//
// Given the below binary tree and sum = 22,
//
//
// 5
// / \
// 4 8
// / / \
// 11 13 4
// / \ / \
//7 2 5 1
//
//
// Return:
//
//
//[
// [5,4,11,2],
// [5,8,4,5]
//]
//leetcode submit region begin(Prohibit modification and deletion)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//这里为什么写在方法外呢?这样可以作为一个类方法共同使用,简化代码。
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
//为什么要重新写个函数呢?
//因为回溯的关键是有记忆的,除了栈,还有的就是参数传递,主函数的参数不适合递归。
dfs(root,new LinkedList<Integer>(),sum);//也可以用ArrayList
return res;
}
private void dfs(TreeNode node,LinkedList<Integer> temp,int target){
if(node==null) {
return;
}
temp.add(node.val);
if(node.left==null && node.right==null && node.val == target) {
res.add(new LinkedList<Integer>(temp));
}
//以上都是递归出口(判断条件),可以理解为dfs中我走到头了,
//发现对或者不对,我标记之后,都要回退一步。
//下面两句就是常规情况,发现还没到递归出口,我深入往前探进,
//同时用栈存了我之前的位置,并用参数传递给下一步我已经存储的信息。
dfs(node.left,temp,target-node.val);
dfs(node.right,temp,target-node.val);
//注意下面这句是带着上面递归出口共用的
temp.removeLast();
}
}
temp的变化过程(从上至下):
5
5 4
5 4 11
5 4 11 7 //到这满了,下一个为null,到达叶子节点
=============
5 4 11 7 //有两次是因为两次打印了两次,和上一次可以只看作一次
5 4 11
5 4 11 2
=============
5 4 11 2
5 4 11
=============
5 4 11
5 4
=============
5 4
5
5 8
5 8 13
=============
5 8 13
5 8
5 8 4
5 8 4 5
=============
5 8 4 5
5 8 4
5 8 4 1
=============
5 8 4 1
5 8 4
=============
5 8 4
5 8
=============
5 8
5
=============
5
2.打印二叉树为链表
//Given a binary tree, flatten it to a linked list in-place.
//
// For example, given the following tree:
//
//
// 1
// / \
// 2 5
// / \ \
//3 4 6
//
//
// The flattened tree should look like:
//
//
//1
// \
// 2
// \
// 3
// \
// 4
// \
// 5
// \
// 6
//
//leetcode submit region begin(Prohibit modification and deletion)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
TreeNode pre = null;
public void flatten(TreeNode root) {
if(root==null) return;
flatten(root.right); //后序串接
flatten(root.left); //先一口气遍历到最靠右的叶子节点,再往回拼接
root.right = pre;//以下三句,拼接串,此时的pre实际是上次弹栈之前保存的节点值。
root.left = null;
pre = root;
}
}
3.二叉树打印层链表
//You are given a perfect binary tree where all leaves are on the same level, an
//d every parent has two children. The binary tree has the following definition:
//
//
//struct Node {
// int val;
// Node *left;
// Node *right;
// Node *next;
//}
//
//
// Populate each next pointer to point to its next right node. If there is no ne
//xt right node, the next pointer should be set to NULL.
//
// Initially, all next pointers are set to NULL.
//
//
//
// Follow up:
//
//
// You may only use constant extra space.
// Recursive approach is fine, you may assume implicit stack space does not coun
//t as extra space for this problem.
//
//
//
// Example 1:
//
//
//
//
//Input: root = [1,2,3,4,5,6,7]
//Output: [1,#,2,3,#,4,5,6,7,#]
//Explanation: Given the above perfect binary tree (Figure A), your function sho
//uld populate each next pointer to point to its next right node, just like in Fig
//ure B. The serialized output is in level order as connected by the next pointers
//, with '#' signifying the end of each level.
//
//
//
// Constraints:
//
//
// The number of nodes in the given tree is less than 4096.
// -1000 <= node.val <= 1000
//leetcode submit region begin(Prohibit modification and deletion)
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
if(root==null) return null;
dfs(root.left,root.right); //分出的原因同上
return root;
}
private void dfs(Node n1, Node n2){
if(n1==null) return; //出口,针对不同情况都要连接,并判断是否到头
n1.next = n2; //左子树为空才返回,右子树为空还是可以被连上的
dfs(n1.left,n1.right); //对于每个node来说,需要连接左右孩子有三种不同的情况
dfs(n2.left,n2.right);
dfs(n1.right,n2.left);
}
}
4. 各路径解析数字和
(这一题和第一题类似,只是稍微变化一下,是练习的重要一步,套一遍当作复习,提高了信心,从这题开始也有了点手感)
//Given a binary tree containing digits from 0-9 only, each root-to-leaf path co
//uld represent a number.
//
// An example is the root-to-leaf path 1->2->3 which represents the number 123.
//
//
// Find the total sum of all root-to-leaf numbers.
//
// Note: A leaf is a node with no children.
//
// Example:
//
//
//Input: [1,2,3]
// 1
// / \
// 2 3
//Output: 25
//Explanation:
//The root-to-leaf path 1->2 represents the number 12.
//The root-to-leaf path 1->3 represents the number 13.
//Therefore, sum = 12 + 13 = 25.
//
// Example 2:
//
//
//Input: [4,9,0,5,1]
// 4
// / \
// 9 0
// / \
//5 1
//Output: 1026
//Explanation:
//The root-to-leaf path 4->9->5 represents the number 495.
//The root-to-leaf path 4->9->1 represents the number 491.
//The root-to-leaf path 4->0 represents the number 40.
//Therefore, sum = 495 + 491 + 40 = 1026.
//leetcode submit region begin(Prohibit modification and deletion)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
import java.util.*;
import java.lang.*;
class Solution {
List<StringBuilder> arr = new ArrayList<>();
public int sumNumbers(TreeNode root) {
dfs(root,new StringBuilder(""));
int sum = 0;
for(StringBuilder e:arr){
sum+=Integer.parseInt(e.toString());
}
return sum;
}
private void dfs(TreeNode node, StringBuilder num){
if(node==null) return;
num.append(node.val);
//唯一的变化就是这里出口条件变了一下,是不是慢慢有了套路的感觉?
if(node.left==null && node.right==null){
arr.add(new StringBuilder(num));
}
dfs(node.left,num);
dfs(node.right,num);
num = num.deleteCharAt(num.length()-1);
}
}
5. 图的深拷贝
有时候做到这里,部分同学的畏难心理就发作了,关于图的题目都不做,实际上,关于图的算法在面试中也是有一定比例考察的,所以最好拿下几个比较经典的题目。这道题目表面上是图的题目,实际就是二叉树的变体而已(可以认为二叉树是一种特殊的图)。
//Given a reference of a node in a connected undirected graph.
//
// Return a deep copy (clone) of the graph.
//
// Each node in the graph contains a val (int) and a list (List[Node]) of its ne
//ighbors.
//
//
//class Node {
// public int val;
// public List<Node> neighbors;
//}
//
//
//
//
// Test case format:
//
// For simplicity sake, each node's value is the same as the node's index (1-ind
//exed). For example, the first node with val = 1, the second node with val = 2, a
//nd so on. The graph is represented in the test case using an adjacency list.
//
// Adjacency list is a collection of unordered lists used to represent a finite
//graph. Each list describes the set of neighbors of a node in the graph.
//
// The given node will always be the first node with val = 1. You must return th
//e copy of the given node as a reference to the cloned graph.
//
//
// Example 1:
//
//
//Input: adjList = [[2,4],[1,3],[2,4],[1,3]]
//Output: [[2,4],[1,3],[2,4],[1,3]]
//Explanation: There are 4 nodes in the graph.
//1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
//2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
//3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
//4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
//
//
// Example 2:
//
//
//Input: adjList = [[]]
//Output: [[]]
//Explanation: Note that the input contains one empty list. The graph consists o
//f only one node with val = 1 and it does not have any neighbors.
//
//
// Example 3:
//
//
//Input: adjList = []
//Output: []
//Explanation: This an empty graph, it does not have any nodes.
//
//
// Example 4:
//
//
//Input: adjList = [[2],[1]]
//Output: [[2],[1]]
//
//
//
// Constraints:
//
//
// 1 <= Node.val <= 100
// Node.val is unique for each node.
// Number of Nodes will not exceed 100.
// There is no repeated edges and no self-loops in the graph.
// The Graph is connected and all nodes can be visited starting from the given n
//ode.
//leetcode submit region begin(Prohibit modification and deletion)
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> neighbors;
public Node() {
val = 0;
neighbors = new ArrayList<Node>();
}
public Node(int _val) {
val = _val;
neighbors = new ArrayList<Node>();
}
public Node(int _val, ArrayList<Node> _neighbors) {
val = _val;
neighbors = _neighbors;
}
}
*/
class Solution {
HashMap<Integer,Node> map = new HashMap<>();
public Node cloneGraph(Node node) {
return clone(node); //这题参数一样为什么还是分开呢?从实际意义出发就好理解了。
}
private Node clone(Node node){
//1.如果图是空的,直接返回
if(node==null) return null;
//4.万一拷到了已经放进map的点咋办?做了5,就可以直接取,而不用重复拷贝啦。
if(map.containsKey(node.val)) return map.get(node.val);
//2.如果不空,拷贝该点,并创建一个空的邻接表
Node newNode = new Node(node.val,new ArrayList<>());
//5.我得提前标记一下,放进map,需要时取就好了。
map.put(newNode.val,newNode);
//拷贝该点的每个邻居点
for(Node neighbor:node.neighbors){
newNode.neighbors.add(clone(neighbor));
}
//...100.最后的最后,最开始的点终于递归拷贝完成并返回。
return newNode;
}
}
6.火柴摆矩形
(先用的贪心,发现不行,还是用dfs)
//Remember the story of Little Match Girl? By now, you know exactly what matchst
//icks the little match girl has, please find out a way you can make one square by
// using up all those matchsticks. You should not break any stick, but you can lin
//k them up, and each matchstick must be used exactly one time.
//
// Your input will be several matchsticks the girl has, represented with their s
//tick length. Your output will either be true or false, to represent whether you
//could make one square using all the matchsticks the little match girl has.
//
// Example 1:
//
//Input: [1,1,2,2,2]
//Output: true
//
//Explanation: You can form a square with length 2, one side of the square came
//two sticks with length 1.
//
//
//
// Example 2:
//
//Input: [3,3,3,3,4]
//Output: false
//
//Explanation: You cannot find a way to form a square with all the matchsticks.
//
//
//
// Note:
//
// The length sum of the given matchsticks is in the range of 0 to 10^9.
// The length of the given matchstick array will not exceed 15.
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public boolean makesquare(int[] nums) {
if(nums.length<4) return false;
int sum = 0;
for (int i = 0; i < nums.length; i++) sum += nums[i];
if(sum%4!=0) return false;
int edge = sum/4;
for (int i = 0; i < nums.length; i++) {
if(nums[i]>edge) return false;
}
//以上是一系列判断,长度不足4||不能被4整除||有任何一个num大于edge均需要返回false
//下面是dfs,核心思想是,我拿着第一根火柴开始往四个容器里试,走不通就会退,重试。
return dfs(nums,edge,new int[4],0);
}
private boolean dfs(int[] nums,int target, int[] count, int idx){
if(idx==nums.length){
if(count[0]==target && count[1]==target && count[2]==target) return true;
else return false;
}
for (int i = 0; i < count.length; i++) {
if(nums[idx]+count[i]>target) continue;
count[i]+=nums[idx];
if(dfs(nums,target,count,idx+1)) return true;
count[i]-=nums[idx];
}
return false;
}
}
7. 列举所有上升子序列
到这里开始有点感觉,自己写的,性能不是最优,可以查看leetcode评论。
//Given an integer array, your task is to find all the different possible increa
//sing subsequences of the given array, and the length of an increasing subsequenc
//e should be at least 2.
//
//
//
// Example:
//
//
//Input: [4, 6, 7, 7]
//Output: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4
//,7,7]]
//
//
//
// Constraints:
//
//
// The length of the given array will not exceed 15.
// The range of integer in the given array is [-100,100].
// The given array may contain duplicates, and two equal integers should also be
// considered as a special case of increasing sequence.
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
List<List<Integer>> res = new ArrayList<>();
//用到set是为了避免重复,标记用
Set<List<Integer>> set = new HashSet<>();
public List<List<Integer>> findSubsequences(int[] nums) {
if(nums==null || nums.length==0) return res;
dfs(nums,0,new ArrayList<Integer>());
return res;
}
private void dfs(int[] nums,int idx, List<Integer> temp){
//递归出口
if(temp.size()>1){
if(isAscend(temp)){
//长度大于1且升序且不重复则加入
if(!set.contains(temp))
{
res.add(new ArrayList<Integer>(temp));
set.add(new ArrayList<Integer>(temp));
//注意加完之后不用return,接着往后加,
//看看能不能找到更长并且符合条件的上升序列
}
}
//如果不是上升,后面的也没必要试了
else return;
}
//以每个num为起点都要用到
for (int i = idx; i < nums.length; i++) {
temp.add(nums[i]);
dfs(nums,i+1,temp);
temp.remove(temp.size()-1);
}
}
46
467
4677
47
477 //4开头的
67
677 //6开头的
77 //7开头的
//判断升序
private boolean isAscend(List<Integer> arr){
for (int i = 0; i < arr.size()-1; i++) {
if(arr.get(i+1)<arr.get(i)) return false;
}
return true;
}
}
8.Number of Closed Islands
//Given a 2D grid consists of 0s (land) and 1s (water). An island is a maximal 4
//-directionally connected group of 0s and a closed island is an island totally (a
//ll left, top, right, bottom) surrounded by 1s.
//
// Return the number of closed islands.
//
//
// Example 1:
//
//
//
//
//Input: grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,
//0,1,0,1],[1,1,1,1,1,1,1,0]]
//Output: 2
//Explanation:
//Islands in gray are closed because they are completely surrounded by water (gr
//oup of 1s).
//
// Example 2:
//
//
//
//
//Input: grid = [[0,0,1,0,0],[0,1,0,1,0],[0,1,1,1,0]]
//Output: 1
//
//
// Example 3:
//
//
//Input: grid = [[1,1,1,1,1,1,1],
// [1,0,0,0,0,0,1],
// [1,0,1,1,1,0,1],
// [1,0,1,0,1,0,1],
// [1,0,1,1,1,0,1],
// [1,0,0,0,0,0,1],
// [1,1,1,1,1,1,1]]
//Output: 2
//
//
//
// Constraints:
//
//
// 1 <= grid.length, grid[0].length <= 100
// 0 <= grid[i][j] <=1
//
class Solution {
int[][] dirs = new int[][]{{-1,0},{1,0},{0,-1},{0,1}};
int res = 0;
public int closedIsland(int[][] grid) {
int m = grid.length, n= grid[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if(i*j*(i-m+1)*(j-n+1)==0) dfs(grid, i, j);
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if(grid[i][j]==0){
res++;
dfs(grid,i,j);
}
}
}
return res;
}
public void dfs(int[][] g, int x, int y){
if(x<0 || x>g.length-1 || y<0 || y>g[0].length-1 || g[x][y]==1) return;
g[x][y] = 1;
for(int[] dir:dirs){
dfs(g,x+dir[0], y+dir[1]);
}
}
}
9. Number of Operations to Make Network Connected
//There are n computers numbered from 0 to n-1 connected by ethernet cables conn
//ections forming a network where connections[i] = [a, b] represents a connection
//between computers a and b. Any computer can reach any other computer directly or
// indirectly through the network.
//
// Given an initial computer network connections. You can extract certain cables
// between two directly connected computers, and place them between any pair of di
//sconnected computers to make them directly connected. Return the minimum number
//of times you need to do this in order to make all the computers connected. If it
//'s not possible, return -1.
//
//
// Example 1:
//
//
//
//
//Input: n = 4, connections = [[0,1],[0,2],[1,2]]
//Output: 1
//Explanation: Remove cable between computer 1 and 2 and place between computers
// 1 and 3.
//
//
// Example 2:
//
//
//
//
//Input: n = 6, connections = [[0,1],[0,2],[0,3],[1,2],[1,3]]
//Output: 2
//
//
// Example 3:
//
//
//Input: n = 6, connections = [[0,1],[0,2],[0,3],[1,2]]
//Output: -1
//Explanation: There are not enough cables.
//
//
// Example 4:
//
//
//Input: n = 5, connections = [[0,1],[0,2],[3,4],[2,3]]
//Output: 0
//
//
//
// Constraints:
//
//
// 1 <= n <= 10^5
// 1 <= connections.length <= min(n*(n-1)/2, 10^5)
// connections[i].length == 2
// 0 <= connections[i][0], connections[i][1] < n
// connections[i][0] != connections[i][1]
// There are no repeated connections.
// No two computers are connected by more than one cable.
class Solution {
public int makeConnected(int n, int[][] connections) {
if(connections.length<n-1) return -1;
HashMap<Integer, ArrayList<Integer>> graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i,new ArrayList<Integer>());
}
for (int i = 0; i < connections.length; i++) {
graph.get(connections[i][0]).add(connections[i][1]);
graph.get(connections[i][1]).add(connections[i][0]);
}
int blocks = 0;
boolean[] vis = new boolean[n];
for (int i = 0; i < n; i++) {
blocks+=dfs(i,graph,vis);
}
return blocks-1;
}
int dfs(int v, HashMap<Integer, ArrayList<Integer>> map, boolean[] vis){
if(vis[v]) return 0;
vis[v] = true;
for (int i = 0; i < map.get(v).size(); i++) {
System.out.println(map.get(v).get(i)+" ");
}
for(Integer next:map.get(v)){
dfs(next,map,vis);
}
return 1;
}
}
精华
最后的最后,给出笔者总结出的做dfs的思想(注意是思想,不是模板!),请参考如下:
- 常常用到一个数组作为标记visited[]
- 传参!传参!传什么参!很重要!
- 结合大量实战练习才能体会精妙!!!!
- 形式如下:
dfs(某状态) //怎么确定参数(很灵活)?<----出口决定 --->可以做哪些选择
如果搜出一种结果,即返回;
在所有能做的选择中: //<------visited[]决定
做出选择并标记做过; //<---用visited[]
dfs(选择后的状态);
撤销该选择以及该记录; //<----假装自己没做过【看不见我看不见我看不见我】
关于所有能做的选择的一点点说明:
他既可以是这样式儿的:
(循环形式表示)
也可以是这样式儿的:
分行列举(实质是一样的)
包括上面的例子你也可以回过头去对应这个思路看一看,加深理解。
以上就是能通往运用dfs自如的入口,只有通过大量练习加以辅助,才能够真正融会贯通,加油~
后记
重点放在第一句, " 就 上 面 这 七 题 肯 定 是 不 够 的 ! 就 上 面 这 七 题 肯 定 是 不 够 的 " {\color{red}"就上面这七题肯定是不够的!就上面这七题肯定是不够的"} "就上面这七题肯定是不够的!就上面这七题肯定是不够的"。希望这上面几道题目可以让你对dfs的一般难度题有一定的了解,可以为你后面大量刷题做个铺垫,目录还是前言那些,感觉一般难度的题目差不多了,可以去挑战一下hard级别的,在有兴趣的可以专门去研究一下相关的算法,结合着图的部分一起看(毕竟关于图的题目就是笔试难度上限了)。
奥里给!
创作不易,你的鼓励是我创作的动力,如果你有收获,点个赞吧👍 |
---|