- 回溯
- 题型8:二叉树上的最长路径和
- 543.二叉树的直径(简单)">543.二叉树的直径(简单)
- 剑指Offer 34.二叉树中和为某一值的路径(中等)">剑指Offer 34.二叉树中和为某一值的路径(中等)
- 124.二叉树中的最大路径和 (困难)">124.二叉树中的最大路径和 (困难)
- 437. 路径总和III (困难)">437. 路径总和III (困难)
- 题型8:二叉树上的最长路径和
- DFS&BFS
- 剑指Offer 13.机器人的运动范围(中等) DFS(已讲)">剑指Offer 13.机器人的运动范围(中等) DFS(已讲)
- 面试题08.10.颜色填充(简单) DFS">面试题08.10.颜色填充(简单) DFS
- 面试题04.01.节点间通路(中等)DFS BFS搜索">面试题04.01.节点间通路(中等)DFS BFS搜索
- 200.岛屿数量(中等) DFS求连通分量(已讲)">200.岛屿数量(中等) DFS求连通分量(已讲)
- 面试题16.19.水域大小(中等) DFS连通性">面试题16.19.水域大小(中等) DFS连通性
- 207.课程表(中等) 拓扑排序,看是否存在环,有两种算法Kahn/DFS">207.课程表(中等) 拓扑排序,看是否存在环,有两种算法Kahn/DFS
- 79.单词搜索(中等)DFS的稍微升级">79.单词搜索(中等)DFS的稍微升级
- 1306.跳跃游戏III(中等) DFS,看着不像,实际上是">1306.跳跃游戏III(中等) DFS,看着不像,实际上是
- 752.打开转盘锁(中等) BFS(已讲)">752.打开转盘锁(中等) BFS(已讲)
- 面试题17.22.单词转换(困难) DFS 流程是标准DFS,但背景要抽象一下">面试题17.22.单词转换(困难) DFS 流程是标准DFS,但背景要抽象一下
- 面试题17.07.婴儿名字(困难) 关系是固定的,并查集或者DFS都能搞定! 关键在于将数据转化成图结构,也就是建模烦!">面试题17.07.婴儿名字(困难) 关系是固定的,并查集或者DFS都能搞定! 关键在于将数据转化成图结构,也就是建模烦!
- 529.扫雷游戏(困难)">529.扫雷游戏(困难)
- 127.单词接龙(困难)">127.单词接龙(困难)
- 126. 单词接龙II(困难)">126. 单词接龙II(困难)
回溯
题型8:二叉树上的最长路径和
543.二叉树的直径(简单)
class Solution {private int ans = 0;public int diameterOfBinaryTree(TreeNode root) {if(root == null) {return 0;}maxDepth(root);return ans;}private int maxDepth(TreeNode root) {if(root == null) {return 0;}int left = maxDepth(root.left);int right = maxDepth(root.right);int dia = left + right;ans = Math.max(ans, dia);return Math.max(left, right) + 1;}}
剑指Offer 34.二叉树中和为某一值的路径(中等)
class Solution {public List<List<Integer>> pathSum(TreeNode root, int target) {List<List<Integer>> res = new ArrayList<>();dfs(root, res, new ArrayList<Integer>(), target, 0);return res;}private void dfs(TreeNode root, List<List<Integer>> res, List<Integer> path, int target, int pathSum) {if(root == null) {return;}pathSum += root.val;path.add(root.val);// 从树的根节点开始往下一直到叶节点所经过的节点形成一条路径,即必须是到叶子节点if(root.left == null && root.right == null && target == pathSum) {List<Integer> tmp = new ArrayList<>(path);res.add(tmp);path.remove(path.size() - 1);return;}dfs(root.left, res, path, target, pathSum);dfs(root.right, res, path, target, pathSum);path.remove(path.size() - 1);}}
124.二叉树中的最大路径和 (困难)
解题思路:将问题转化为:以任意节点为转折点的最大路径和,然后以“怎么计算某个节点为转折点的最大路径和?”这个问题出发思考。
class Solution {private int res = Integer.MIN_VALUE;public int maxPathSum(TreeNode root) {dfs(root);return res;}private int dfs(TreeNode root) {if(root == null) {return 0;}// 左子树的最大路径和int leftMaxPathSum = dfs(root.left);// 右子树的最大路径和int rightMaxPathSum = dfs(root.right);int max = root.val;if(leftMaxPathSum > 0) {max += leftMaxPathSum;}if(rightMaxPathSum > 0) {max += rightMaxPathSum;}res = Math.max(res, max);// 当前节点的最大路径和可能存在以下情况// 当前节点值 > 左/右子树的最大路径和 + 当前节点值// 左子树最大路径和 + 当前节点值 > 右子树最大路径和 + 当前节点值int ret = root.val;ret = Math.max(ret, leftMaxPathSum + root.val);ret = Math.max(ret, rightMaxPathSum + root.val);return ret;}}
437. 路径总和III (困难)
BFS + 回溯
/*** 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 {private int res = 0;public int pathSum(TreeNode root, int targetSum) {if(root == null) {return res;}LinkedList<TreeNode> queue = new LinkedList<>();queue.add(root);while(!queue.isEmpty()) {TreeNode node = queue.poll();if(node.left != null) {queue.offer(node.left);}if(node.right != null) {queue.offer(node.right);}backTrace(node, targetSum, 0);}return res;}private void backTrace(TreeNode root, int targetSum, int sum) {if(root == null) {return;}int curSum = sum + root.val;if(targetSum == curSum) {res++;}backTrace(root.left, targetSum, curSum);backTrace(root.right, targetSum, curSum);}}
前缀和+回溯+递归
class Solution {// key是前缀和, value是大小为key的前缀和出现的次数private Map<Integer, Integer> prefixSumCount = new HashMap<>();public int pathSum(TreeNode root, int targetSum) {// 前缀和为0的一条路径prefixSumCount.put(0, 1);return backTrace(root, targetSum, 0);}private int backTrace(TreeNode node, int targetSum, int curSum) {// 1.递归终止条件if(node == null) {return 0;}// 2.本层要做的事情int res = 0;curSum += node.val;//---核心代码// 看看root到当前节点这条路上是否存在节点前缀和加target为currSum的路径// 当前节点->root节点反推,有且仅有一条路径,如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了// currSum-target相当于找路径的起点,起点的sum+target=currSum,当前点到起点的距离就是targetres += prefixSumCount.getOrDefault(curSum - targetSum, 0);// 更新路径上当前节点前缀和的个数prefixSumCount.put(curSum, prefixSumCount.getOrDefault(curSum, 0) + 1);//---核心代码res += backTrace(node.left, targetSum, curSum);res += backTrace(node.right, targetSum, curSum);// 4.回到本层,恢复状态,去除当前节点的前缀和数量prefixSumCount.put(curSum, prefixSumCount.get(curSum) - 1);return res;}}
树形DP
/*** 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 {private int count = 0;public int pathSum(TreeNode root, int targetSum) {// 树形DP,状态从左右子树转移而来,利用DFS来做// 用 Map 来存储以每个节点作为root节点的路径情况,key为路径和,value为路径条数dfs(root, targetSum);return count;}private Map<Integer, Integer> dfs(TreeNode node, int targetSum) {if(node == null) {return new HashMap<Integer, Integer>();}Map<Integer, Integer> nodeMap = new HashMap();nodeMap.put(node.val, 1);Map<Integer, Integer> leftNodeMap = dfs(node.left, targetSum);Map<Integer, Integer> rightNodeMap = dfs(node.right, targetSum);// 遍历左子节点的Mapfor(Map.Entry<Integer, Integer> entry : leftNodeMap.entrySet()) {Integer newKey = entry.getKey() + node.val;Integer newValue = entry.getValue();if(nodeMap.containsKey(newKey)) {newValue += nodeMap.get(newKey);}nodeMap.put(newKey, newValue);}// 遍历右子节点的Mapfor(Map.Entry<Integer, Integer> entry : rightNodeMap.entrySet()) {Integer newKey = entry.getKey() + node.val;Integer newValue = entry.getValue();if(nodeMap.containsKey(newKey)) {newValue += nodeMap.get(newKey);}nodeMap.put(newKey, newValue);}// 遍历当前节点的 Mapfor(Map.Entry<Integer, Integer> entry : nodeMap.entrySet()){if(entry.getKey() == targetSum) {count += entry.getValue();}}return nodeMap;}}
DFS&BFS
剑指Offer 13.机器人的运动范围(中等) DFS(已讲)
DFS
解题思路:
技巧:整数 % 10 能获取个位数,比如 35 % 10 = 5,整数 / 10 能往左移动一位,例如:35 / 10 = 3,所以整数求每位数相加的模板:
假设有一个整数x,整数x的每位相加为t,代码模板如下:
int t = 0;while(x != 0) {t += x % 10;x /= 10;}
class Solution {private boolean[][] isVisited;public int movingCount(int m, int n, int k) {isVisited = new boolean[m][n];return dfs(0, 0, m, n, k);}private int dfs(int x, int y, int m, int n, int k) {if(x == m || y == n || x < 0 || y < 0 || count(x, y) > k || isVisited[x][y]) {return 0;}isVisited[x][y] = true;return 1 + dfs(x + 1, y, m, n, k) + dfs(x, y + 1, m, n, k) + dfs(x - 1, y, m, n, k) + dfs(x, y - 1, m, n, k) ;}private int count(int x, int y) {int t = 0;while(x != 0) {t+= x % 10;x /= 10;}while(y != 0) {t += y % 10;y /= 10;}return t;}}
面试题08.10.颜色填充(简单) DFS
DFS
class Solution {public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {// 防止重复遍历,造成死循环boolean[][] visited = new boolean[image.length][image[0].length];dfs(image, sr, sc, image[sr][sc], newColor, visited);return image;}private void dfs(int[][] image, int sr, int sc, int initColor, int newColor, boolean[][] visited) {if(sr < 0 || sr >= image.length || sc < 0 || sc >= image[0].length || visited[sr][sc]) {return;}if(initColor != image[sr][sc]) {return;}image[sr][sc] = newColor;visited[sr][sc] = true;dfs(image, sr-1, sc, initColor, newColor, visited);dfs(image, sr, sc - 1, initColor, newColor, visited);dfs(image, sr + 1, sc, initColor, newColor, visited);dfs(image, sr, sc + 1, initColor, newColor, visited);}}
面试题04.01.节点间通路(中等)DFS BFS搜索
DFS
class Solution {public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) {// 访问状态数组boolean[] visited = new boolean[graph.length];return dfs(graph, visited, start, target);}private boolean dfs(int[][] graph, boolean[] visited, int start, int target) {for(int i = 0; i < graph.length; i++) {// 确保当前路径未被访问(该判断主要是为了防止图中自环出现死循环的情况)if(visited[i]) {continue;}if(graph[i][0] == start && graph[i][1] == target) {return true;}// 设置访问标志visited[i] = true;// 核心代码,这里确实巧妙,利用目标值反推起始值,逐渐压缩搜索区间,减少递归次数,剪枝操作if(graph[i][1] == target && dfs(graph, visited, start, graph[i][0])) {return true;}// 清除访问标志visited[i] = false;}return false;}}
DFS-优化版
class Solution {public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) {if(start == target) {return true;}for(int[] g : graph) {// 核心,利用目标值反推起始值,逐渐压缩搜索区间,减少递归次数,剪枝操作if(g[1] == target) {return findWhetherExistsPath(n, graph, start, g[0]);}}return false;}}
邻接表
class Solution {public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) {// 构建邻接表HashSet<Integer>[] map = new HashSet[n];for(int[] g : graph) {if(map[g[0]] == null) {map[g[0]] = new HashSet<Integer>();}if(!map[g[0]].contains(g[1])) {map[g[0]].add(g[1]);}}boolean[] visited = new boolean[n];return dfs(map, visited, start, target);}private boolean dfs(HashSet<Integer>[] map, boolean[] visited, int cur, int target) {if(cur == target) {return true;}if(map[cur] == null) {return false;}boolean found = false;visited[cur] = true;for(Integer idx : map[cur]) {if(!visited[idx]) {found = found || dfs(map, visited, idx, target);}}visited[cur] = false;return found;}}
200.岛屿数量(中等) DFS求连通分量(已讲)
DFS
class Solution {public int numIslands(char[][] grid) {int m = grid.length;int n = grid[0].length;int count = 0;boolean[][] visited = new boolean[m][n];for(int i = 0; i < m; i++) {for(int j = 0; j < n; j++) {// 是陆地且未被访问过的if(grid[i][j] == '1' && !visited[i][j]) {helper(grid, i, j, visited);count++;}}}return count;}private void helper(char[][] grid, int m, int n, boolean[][] visited) {if(n < 0 || n == grid[0].length || m < 0 || m == grid.length || visited[m][n] || grid[m][n] == '0') {return;}// 标记该点已被访问visited[m][n] = true;// 向上移动helper(grid, m - 1, n, visited);// 向下移动helper(grid, m + 1, n, visited);// 向左移动helper(grid, m, n - 1, visited);// 向右移动helper(grid, m, n + 1, visited);}}
面试题16.19.水域大小(中等) DFS连通性
DFS
class Solution {public int[] pondSizes(int[][] land) {List<Integer> res = new ArrayList<>();boolean[][] visited = new boolean[land.length][land[0].length];for(int m = 0; m < land.length; m++) {for(int n = 0; n < land[0].length; n++) {if(!visited[m][n] && land[m][n] == 0) {int size = dfs(land, m, n, visited);res.add(size);}}}int[] result = new int[res.size()];for(int i = 0; i < res.size(); i++) {result[i] = res.get(i);}Arrays.sort(result);return result;}private int dfs(int[][] land, int m, int n, boolean[][] visited) {if(m < 0 || m >= land.length || n < 0 || n >= land[0].length || visited[m][n] || land[m][n] > 0) {return 0;}visited[m][n] = true;// 上、下、左、右、左上、左下、右上、右下int size = 0;size += dfs(land, m - 1, n, visited);size += dfs(land, m + 1, n, visited);size += dfs(land, m, n -1, visited);size += dfs(land, m, n + 1, visited);size += dfs(land, m + 1, n + 1, visited);size += dfs(land, m + 1, n - 1, visited);size += dfs(land, m - 1, n + 1, visited);size += dfs(land, m - 1, n - 1, visited);return size + 1;}}
207.课程表(中等) 拓扑排序,看是否存在环,有两种算法Kahn/DFS
DFS
class Solution {private boolean res = true;private int size = 0;public boolean canFinish(int numCourses, int[][] prerequisites) {boolean[] visited = new boolean[prerequisites.length];for(int i = 0; i < numCourses && res; i++) {dfs(i, prerequisites, new ArrayList<Integer>(), visited);}return res;}private void dfs(int premise, int[][] prerequisites, List<Integer> path, boolean[] visited) {if(path.contains(premise)) {res = false;return;}path.add(premise);for(int i = 0; i < prerequisites.length & res; i++) {if(prerequisites[i][0] == premise && !visited[i]) {visited[i] = true;dfs(prerequisites[i][1], prerequisites, path, visited);path.remove(path.size() - 1);}}}}
DFS - 优化写法
class Solution {List<List<Integer>> edges;int[] visited;boolean valid = true;public boolean canFinish(int numCourses, int[][] prerequisites) {edges = new ArrayList<List<Integer>>();for (int i = 0; i < numCourses; ++i) {edges.add(new ArrayList<Integer>());}visited = new int[numCourses];for (int[] info : prerequisites) {edges.get(info[1]).add(info[0]);}for (int i = 0; i < numCourses && valid; ++i) {if (visited[i] == 0) {dfs(i);}}return valid;}public void dfs(int u) {visited[u] = 1;for (int v: edges.get(u)) {if (visited[v] == 0) {dfs(v);if (!valid) {return;}} else if (visited[v] == 1) {valid = false;return;}}visited[u] = 2;}}
邻接矩阵 + DFS
class Solution {private boolean valid = true;private List<List<Integer>> table = new ArrayList<>();public boolean canFinish(int numCourses, int[][] prerequisites) {// 构建邻接表for(int i = 0; i < numCourses; i++) {table.add(new ArrayList<Integer>());}// 初始化邻接表for(int i = 0; i < prerequisites.length; i++) {table.get(prerequisites[i][1]).add(prerequisites[i][0]);}int[] visited = new int[numCourses];for(int i = 0; i < numCourses && valid; i++) {if(visited[i] == 0) {dfs(visited, i);}}return valid;}private void dfs(int[] visited, int i) {visited[i] = 1;for(int v : table.get(i)) {if(visited[v] == 1) {valid = false;return;} else if(visited[v] == 0) {dfs(visited, v);// 剪枝if(!valid) {return;}}}visited[i] = 2;}}
79.单词搜索(中等)DFS的稍微升级
DFS
class Solution {private boolean res = false;public boolean exist(char[][] board, String word) {int m = board.length;int n = board[0].length;int idx = 0;boolean[][] visited = new boolean[m][n];for(int i = 0; i < m; i++) {for(int j = 0; j < n; j++) {if(res) {break;}if(board[i][j] == word.charAt(idx)) {dfs(board, i, j, visited, word, idx);}}}return res;}private void dfs(char[][] board, int m, int n, boolean[][] visited, String word, int idx) {if(idx == word.length()) {res = true;return;}if(res || m < 0 || m == board.length || n < 0 || n == board[0].length || visited[m][n] || board[m][n] != word.charAt(idx)) {return;}visited[m][n] = true;dfs(board, m - 1, n, visited, word, idx + 1);dfs(board, m + 1, n, visited, word, idx + 1);dfs(board, m, n - 1, visited, word, idx + 1);dfs(board, m, n + 1, visited, word, idx + 1);visited[m][n] = false;}}
1306.跳跃游戏III(中等) DFS,看着不像,实际上是
class Solution {public boolean canReach(int[] arr, int start) {return dfs(arr, start, new boolean[arr.length]);}private boolean dfs(int[] arr, int start, boolean[] visited) {if(start < 0 || start >= arr.length || visited[start]) {return false;}if(arr[start] == 0) {return true;}visited[start] = true;return dfs(arr, start + arr[start], visited) || dfs(arr, start - arr[start], visited);}}
752.打开转盘锁(中等) BFS(已讲)
BFS
解题思路:
- 以”0000”为根子节点,会扩展出”1000”、”0100”、”0010”、”0001”、”9000”、”0900”、”0090”、”0009” 这些节点,扩展出的节点,根据题意每个节点又能扩展出8个节点,这样就形成了一个多叉树。
分别用两个 HashSet 来排除死亡数字集合和重复子节点,就能计算出解锁需要的最小旋转次数。 ```java class Solution { public int openLock(String[] deadends, String target) {
if(target.equals("0000")) {return 0;}Set<String> deadendSet = new HashSet<>();// 初始化 HashSet,用于排除死亡数字for(String deadend : deadends) {deadendSet.add(deadend);}if(deadendSet.contains("0000")) {return -1;}// 使用 HashSet 去除重复节点Set<String> visited = new HashSet<>();Queue<String> queue = new LinkedList<>();queue.offer("0000");visited.add("0000");int step = 0;while(!queue.isEmpty()) {step++;int size = queue.size();for(int i = 0; i < size; i++) {String node = queue.poll();List<String> newNodes = genNewNodes(node);for(String newNode : newNodes) {// 优化点,放外层的话,需要多遍历几个节点if(newNode.equals(target)) {return step;}// 排除死亡数字 或者 排除重复节点if(visited.contains(newNode) || deadendSet.contains(newNode)) {continue;}queue.offer(newNode);visited.add(newNode);}}}return -1;
}
public char numPrev(char x) {
return x == '0' ? '9' : (char) (x - 1);
}
public char numSucc(char x) {
return x == '9' ? '0' : (char) (x + 1);
}
private List<String> genNewNodes(String node) {List<String> newNodes = new ArrayList<>();char[] array = node.toCharArray();for(int i = 0; i < node.length(); i++) {char num = array[i];char oldVal = array[i];array[i] = numPrev(oldVal);newNodes.add(new String(array));array[i] = numSucc(oldVal);newNodes.add(new String(array));array[i] = num;}return newNodes;}
}
<a name="xoZ6M"></a>#### 双向BFS```javaclass Solution {public int openLock(String[] deadends, String target) {// 记录已访问过的记录Set<String> visited = new HashSet<>();// 初始化for(String deadend : deadends) {visited.add(deadend);}Set<String> q1 = new HashSet<>();Set<String> q2 = new HashSet<>();q1.add("0000");q2.add(target);int step = 0;while(!q1.isEmpty() && !q2.isEmpty()) {if(q1.size() > q2.size()) {Set<String> temp = q1;q1 = q2;q2 = temp;}Set<String> temp = new HashSet<>();for(String cur : q1) {if(visited.contains(cur)) {continue;}if(q2.contains(cur)) {return step;}// 添加已访问的记录visited.add(cur);for(int i = 0; i < 4; i++) {String up = plusOne(cur, i);if(!visited.contains(up)) {temp.add(up);}String down = minusOne(cur, i);if(!visited.contains(down)) {temp.add(down);}}}step++;q1 = q2;q2 = temp;}return -1;}private String plusOne(String s, int i) {char[] array = s.toCharArray();if(array[i] == '9') {array[i] = '0';} else {array[i] += 1;}return new String(array);}private String minusOne(String s, int i) {char[] array = s.toCharArray();if(array[i] == '0') {array[i] = '9';} else {array[i] -= 1;}return new String(array);}}
面试题17.22.单词转换(困难) DFS 流程是标准DFS,但背景要抽象一下
解题思路:常规的 DFS 解法
注意点:
- beginWord 必然在结果集中
- endWord 需要在 wordList 中
- beginWord 每一次替换字符的状态,都需要在 wordList 集合中
- 通过建立 visited 集合来记录已转换过的状态,达成剪枝的目的
class Solution {public List<String> findLadders(String beginWord, String endWord, List<String> wordList) {Set<String> wordSet = new HashSet<>(wordList);Set<String> visitedSet = new HashSet<>();List<String> res = new ArrayList<>();// 边界判断if(!wordSet.contains(endWord)) {return res;}res.add(beginWord);visitedSet.add(beginWord);if(!dfs(beginWord.toCharArray(), endWord.toCharArray(), wordSet, visitedSet, res)) {return new ArrayList<>();}return res;}private boolean dfs(char[] beginWord, char[] endWord, Set<String> wordSet, Set<String> visitedSet, List<String> path) {// 终止条件if(Arrays.equals(beginWord, endWord)) {return true;}for(int i = 0; i < beginWord.length; i++) {for(int j = 0; j < 26; j++) {char oldChar = beginWord[i];beginWord[i] = (char)('a' + j);String word = new String(beginWord);if(wordSet.contains(word) && !visitedSet.contains(word)) {path.add(word);visitedSet.add(word);if(dfs(beginWord, endWord, wordSet, visitedSet, path)) {return true;}// 回溯path.remove(path.size() - 1);}// 复原beginWord[i] = oldChar;}}return false;}}
