深度优先搜索算法,depth-first-search DFS,是一种用于遍历或者搜索树、图的算法,这个算法会尽可能深的去搜索树的分支。
深度优先搜索是图论中的经典算法,DFS基于递归思想,实质是一种借助递归实现的枚举。
基本代码结构
void dfs(int step) {
// 判断边界;
// 尝试每一种可能;
int len = getXxxLen();//define your own length
for (int i = 1; i <= len; ++i) {
//继续下一步
dfs(step + 1);
}
// 返回;
}
例题
113. 路径总和 II
题目
给你二叉树的根节点
root
和一个整数目标和targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。分析:
这题的思路就是深度遍历,利用递归的方式,从根节点一直遍历到叶子节点,然后记录所有满足要求的路径。
问题点:
1、那种路径是满足要求的?
---> 只要从根节点一直加到叶子节点,和刚好等于目标值就是满足要求
2、满足要求的路径,怎么存储?
---> 定义一个LinkedList,用于存储遍历路径上的所有节点的值
遍历到某个节点时,就把这个节点的值添加进去
如果某个节点不符合要求,要跳出这个节点,遍历其他节点,就把这个不符合要求的节点的值,从当前LinkedList中删除,也就是删除最后一次插入的那个值。
代码
// 定义全局变量,用于方法返回值 List
> result = new ArrayList<>(); // 定义一个LinkedList,用于存储单个遍历路径上的数值 LinkedList
list = new LinkedList<>(); public List > pathSum(TreeNode root, int targetSum) { // 定义路径上所有的和 int sum = 0; dfs(root, targetSum, sum); return result; } // 定义dfs方法 void dfs(TreeNode root, int targetSum, int sum) { if (root == null) { return; } // 将当前节点的value值,存到链表中 list.add(root.val); // 路径和加上当前节点的值 sum += root.val; // 如果当前节点为叶子节点,并且此时路径和刚好等于目标值,那么就将数据list添加到result中 if (root.left == null && root.right == null && sum == targetSum) { // 注意这个地方,添加的时候,要new一个list,不能把当前list传进去, // 否则dfs跳出的时候,list里面的值也没了 result.add(new ArrayList<>(list)); } // 递归调用dfs方法 dfs(root.left, targetSum, sum); dfs(root.right, targetSum, sum); // 关键这一步,如果跳出了当前的dfs方法,那么要把当前节点的值,从list中移除。 list.removeLast(); }
class Solution: def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]: res = [] cur_list = [] def dfs(node, sum_value, target): if not node: return cur_list.append(node.val) sum_value += node.val if not node.left and not node.right and sum_value == target: res.append(list(cur_list)) dfs(node.left, sum_value, target) dfs(node.right, sum_value, target) cur_list.pop() dfs(root, 0, targetSum) return res
124. 二叉树中的最大路径和
题目
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点
分析:
一个二叉树,a为当前递归调用中的传入的根节点(左右节点分别是已经计算好的最大子路径),注意题目,同一个节点在一条路径中最多出现一次,因此,最大路径可能情况是
- a节点不连接父节点
- a
- a+left
- a+right
- a + left + right
- a节点连接父节点,为了保证路径上节点最多出现一次,那么左右节点就只能选择一个,
- parent + a + right -- right大则选择right
- parent + a + left -- left大则选择left
此外,因为节点有负值的情况,因此,如果节点为负的情况下,要想办法舍弃负值
代码
public class Test124 { // 定义一个最大值,因为节点的值可能为负 // 因此初始化值,设置为Integer.MIN_VALUE int maxValue = Integer.MIN_VALUE; public int maxPathSum(TreeNode root) { getMaxPath(root); return maxValue; } // dfs方法,寻找最大的子节点路径path private int getMaxPath(TreeNode root) { // 当前节点为null,对路径和没有贡献,因此返回值0; if (null == root) { return 0; } // 递归调用左右子节点 // 如果子节点求出来的路径和最大值为负数,对路径和没有贡献 // 因此,取0 和 返回值 较大的一个值 int leftMaxPath = Math.max(0, getMaxPath(root.left)); int rightMaxPath = Math.max(0, getMaxPath(root.right)); // 更新最大路径和,当前要比较的对象是本节点值,加上左右节点最大贡献值 maxValue = Math.max(maxValue, root.val + leftMaxPath + rightMaxPath); // 返回值,注意此处,因为同一个节点在一条路径只能出现一次 // 因此,返回值要设置为,当前节点的值 加上 左右 节点 比较大的贡献哪个路径。 return root.val + Math.max(leftMaxPath, rightMaxPath); } }
class Solution: def __init__(self): self.max_value = None def maxPathSum(self, root: Optional[TreeNode]) -> int: self.max_value = -(sys.maxsize - 1) def dfs(node): if not node: return 0 left_max = max(0, dfs(node.left)) right_max = max(0, dfs(node.right)) self.max_value = max(self.max_value, node.val + left_max + right_max) return node.val + max(left_max, right_max) dfs(root) return self.max_value
130. 被围绕的区域
题目:
给你一个
m x n
的矩阵board
,由若干字符'X'
和'O'
,找到所有被'X'
围绕的区域,并将这些区域里所有的'O'
用'X'
填充。分析:
这到提要理解清楚,什么是被X围绕的O,弄清题意之后可知:对于所有边界上的O以及和边界上的O直接相连的O,都不属于被X围绕,而剩下的O,就是被X围绕了。
根据这个思路,可以进行如下解:
首先遍历边界上的值,将所有的O,并且与这个O直接相连的O,全部找出来(可以用到深DFS方式)并标记为A
此时,当前数组上所有的O就是被X包围的,可以直接改成X,然后在吧标记为A的,在改回O
注意:O和0很像,键盘也离得近,注意注意在注意o_O;
代码:
public class Test130 { public void solve(char[][] board) { if (board == null) { return; } int m = board.length; int n = board[0].length; // 上下两行进行遍历 for (int i = 0; i < board[0].length; i++) { edgeO(board, 0, i); edgeO(board, m - 1, i); } // 左右两列进行遍历 for (int i = 1; i < m - 1; i++) { edgeO(board, i, 0); edgeO(board, i, n - 1); } // 最后遍历整个数组,将之前没有标记为A的,改成X // 将之前标记为A的,改回为O for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[i].length; j++) { if (board[i][j] == 'O') { board[i][j] = 'X'; } if (board[i][j] == 'A') { board[i][j] = 'O'; } } } } public void edgeO(char[][] board, int x, int y) { int m = board.length; int n = board[0].length; // 数组不越界,并且值为O的时候,将值先标记为A if (x < m && x >= 0 && y < n && y >= 0 && board[x][y] == 'O') { board[x][y] = 'A'; edgeO(board, x - 1, y); edgeO(board, x + 1, y); edgeO(board, x, y - 1); edgeO(board, x, y + 1); } } }
class Solution: def solve(self, board: List[List[str]]) -> None: m, n = len(board), len(board[0]) def dfs(i, j): if i < 0 or j < 0 or i >= m or j >= n or board[i][j] != "O": return board[i][j] = "A" dfs(i - 1, j) dfs(i + 1, j) dfs(i, j - 1) dfs(i, j + 1) for i in range(n): if board[0][i] == "O": dfs(0, i) if board[m - 1][i] == "O": dfs(m - 1, i) for i in range(m): if board[i][0] == "O": dfs(i, 0) if board[i][n - 1] == "O": dfs(i, n - 1) for i in range(m): for j in range(n): # 注意这个改变的顺序,如果先从A改成O,那么下面又会从O改成X,就出问题了 if board[i][j] == "O": board[i][j] = "X" if board[i][j] == "A": board[i][j] = "O"