文章目录
- [94. 二叉树的中序遍历 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/)
- [98. 验证二叉搜索树 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/validate-binary-search-tree/)
- [494. 目标和 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/target-sum/)
- [101. 对称二叉树 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/symmetric-tree/)
- [104. 二叉树的最大深度 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/)
- [200. 岛屿数量 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/number-of-islands/)
- [105. 从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)
- [124. 二叉树中的最大路径和 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/)
- [114. 二叉树展开为链表 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/)
- [207. 课程表 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/course-schedule/)
- [226. 翻转二叉树 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/invert-binary-tree/)
- [236. 二叉树的最近公共祖先 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)
- [297. 二叉树的序列化与反序列化 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/)
- [337. 打家劫舍 III - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/house-robber-iii/)
- [437. 路径总和 III - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/path-sum-iii/)
- [538. 把二叉搜索树转换为累加树 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/convert-bst-to-greater-tree/)
- [543. 二叉树的直径 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/diameter-of-binary-tree/)
- [617. 合并二叉树 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/merge-two-binary-trees/)
- [17. 电话号码的字母组合 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/)
- [114. 二叉树展开为链表 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/)
- [39. 组合总和 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/combination-sum/)
- [46. 全排列 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/permutations/)
- [78. 子集 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/subsets/)
- [79. 单词搜索 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/word-search/)
- [22. 括号生成 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/generate-parentheses/)
- [129. 求根节点到叶节点数字之和 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/)
- [110. 平衡二叉树 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/balanced-binary-tree/)
- [113. 路径总和 II - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/path-sum-ii/)
94. 二叉树的中序遍历 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 中序遍历:左根右。
- 需要一个全局的list保存最后的结果。
- 设计一个dfs函数,因为有全局的list,所以dfs没有返回值。每次判断当前节点是否为空,空就返回,否则就向左递归,添加节点值,向右递归。
-
代码
class Solution { List<Integer> res = new LinkedList<>(); public List<Integer> inorderTraversal(TreeNode root) { dfs(root); return res; } public void dfs(TreeNode root){ if(root == null){ return; } dfs(root.left); res.add(root.val); dfs(root.right); } }
98. 验证二叉搜索树 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 有效的二叉搜索树就是跟大于左小于右,并且,所有的子树也满足。
- 子问题的答案就是判断自己是否是一个二叉搜索树,如果为空,返回true。
- 如果节点值小于左,或者大于右,都是false。
- 依次递归遍历数的左子树右子树,最后返回左右子树是否都满足二叉搜索树的条件。
-
注:
因为涉及到比较值,那么左递归就是当前结点值和左子节点值比较,右递归就是当前节点值和右子结点值比较。 -
注意一点:传入的值要是Long,不然在最后几个案例可能过不了。
-
代码
class Solution { public boolean isValidBST(TreeNode root) { return dfs(root, Long.MIN_VALUE, Long.MAX_VALUE); } //lower代表左节点的值,upper代表右节点的值 public boolean dfs(TreeNode root, long lower, long upper){ if(root == null){ return true; } if(root.val <= lower || root.val >= upper){ return false; } boolean left = dfs(root.left, lower, root.val); boolean right = dfs(root.right, root.val, upper); return left && right; } }
494. 目标和 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
本题既可以用dfs,也可以是使用动态规划的思想去完成,这里使用dfs思想完成。
- dfs的参数除了给定的两个之外,还需要对位置进行判断,以及在这之前的数字和,所以还有其余两个参数(位置以及数字之和)。
- 递归出口:如果当前已经走到最后一个数字,然后对之前数字和进行判断,如果和等于目标,那么就进行结果累加。
- 如果没有走到最后一个数字,那么说明,还需要向下,对下一个数字进行加减的判断,每走一次索引就+1,向下传递的时候是
+
就是求和,-
就是求差。
- 代码:
class Solution {
private int res = 0;
public int findTargetSumWays(int[] nums, int target) {
//方式1:dfs达到快速AC
dfs(nums, target, 0, 0);
return res;
}
private void dfs(int[] nums, int target, int idx, int sum){
if(idx == nums.length){
if(sum == target){
res++;
}
}else{
dfs(nums, target, idx + 1, sum + nums[idx]);
dfs(nums, target, idx + 1, sum - nums[idx]);
}
}
}
101. 对称二叉树 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 设计dfs,传入左右子树。返回值是boolean类型,即判断左右子树是否相等。
- 如果都为空,则是true。
- 如果有一个为空,则为false。
- 否则就比较二者的节点值以及递归判断他们的左右子树。
- 注意点:应该先判断都不是空的时候,再判断有一个为空的时候,因为当返回false写在前面的时候,那么只要有一个为空,就返回false,不管另个是否为空。
- 代码:
class Solution {
public boolean isSymmetric(TreeNode root) {
return dfs(root.left, root.right);
}
public boolean dfs(TreeNode left, TreeNode right){
if(left == null && right == null){
return true;
}
if(left ==null || right == null){
return false;
}
return left.val == right.val &&
dfs(left.left, right.right) &&
dfs(left.right, right.left);
}
}
104. 二叉树的最大深度 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 分别向左子树和右子树进行递归。
- 因为该问题作为一个子问题也是其父问题的答案,所以,在递归的时候就是不断的调用自己。
- 找到左右子树最大的那个,再加1即可。
- 代码:
class Solution {
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
}
200. 岛屿数量 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 分析题目,就是找到矩阵中的岛屿数,但是岛屿是可以相连的,即:上下左右相连的1会被认作
连续岛屿
。\- 当对矩阵扫描的时候,只要某一个位置是1,那么就进行深度优先搜索。同时让结果++。
- dfs的作用,寻找该岛屿(或者连续岛屿)的边界。那么此时边界就是,给定的位置不符合要求,或者给定的位置已经被访问过。
- 对访问过的岛屿进行删除,这里简单一点,就直接标记为
水(0)
。同时对四周进行深度优先搜索。
注:对四周搜索的时候,不需要知道四周情况,因为dfs函数的作用是去找边界,找到了边界自己会自动返回的。
- 代码:
class Solution {
public int numIslands(char[][] grid) {
int row = grid.length;
int col = grid[0].length;
int res = 0;
for(int i = 0; i < row; i++){
for(int j = 0; j < col; j++){
if(grid[i][j] == '1'){
++res;
dfs(grid, i, j);
}
}
}
return res;
}
private void dfs(char[][] grid, int i, int j){
if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || 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);
}
}
105. 从前序与中序遍历序列构造二叉树
- 思路:
- 前序遍历的顺序是根左右,中序遍历的是左根右。
- 以示例1为例:前序的结果是[3,9,20,15,7],中序的结果是[9,3,15,20,7]。前序遍历的第一个永远都是根节点,这样就找到根节点了,然后去中序遍历找到根节点的位置,在根节点的前面就是左子树,在根节点的后面就是右子树。
- 最后就是不断的将这个问题规模进行缩小,划分为一个一个子问题进行解决。
- 代码:
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0 || inorder.length == 0){
return null;
}
TreeNode root = new TreeNode(preorder[0]);
for(int i = 0; i < preorder.length; i++){
if(preorder[0] == inorder[i]){
int[] pre_left = Arrays.copyOfRange(preorder, 1, i + 1);
int[] pre_right = Arrays.copyOfRange(preorder, i + 1,preorder.length);
int[] in_left = Arrays.copyOfRange(inorder, 0, i);
int[] in_right = Arrays.copyOfRange(inorder, i + 1, inorder.length);
root.left = buildTree(pre_left, in_left);
root.right = buildTree(pre_right, in_right);
break;
}
}
return root;
}
}
- 代码方面注意的点就是:Arrays.copyOfRange()
124. 二叉树中的最大路径和 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 进行dfs。
- dfs的作用:找到一条路径,可以从任意节点到子结点的最大值。
- 因为问题本身需要向子问题得到答案,即:走哪条道(
不是走成华大道嗷)能得到最好的值。所以在递归的时候,需要分别求出左右子树的能得到的最大值,又因为可能存在负值,所以每次要和0比较,求出最大值。- 此时左右的最大值已经求出了,那么,如果路径是左+根+右,此时计算路径的和,同时与历史的路径最大和做比较。
- 但是返回的是走哪条道能给根的父结点作为选择。
- 其实就是理解:走一个
人
字型的路径能取得最大值,还是走一撇一捺
这样的路径,哪个路径能取得最大值。
- 代码:
class Solution {
private int res = -Integer.MAX_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return res;
}
private int dfs(TreeNode root){
if(root == null){
return 0;
}
//计算左边的最大值
int left = Math.max(dfs(root.left), 0);
//计算左边的最大值
int right = Math.max(dfs(root.right), 0);
//左+根+右,人字形能取得的最大值
res = Math.max(res, root.val + left + right);
//一撇一捺能取得的最大值。
return Math.max(root.val + left, root.val + right);
}
}
114. 二叉树展开为链表 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 先序遍历是:根左右,使用dfs进行节点的存储。
- 每次读取从列表中,然后指针移动,因为左侧永远都置空,那么每次指针下移到右侧。
- 代码:
class Solution {
List<TreeNode> list = new LinkedList<>();
public void flatten(TreeNode root) {
dfs(root);
int size = list.size();
TreeNode pre = null, cur = null;
for(int i = 1; i < size; i++){
pre = list.get(i - 1);
cur = list.get(i);
pre.left = null;
pre.right = cur;
}
}
public void dfs(TreeNode root){
if(root == null){
return;
}
list.add(root);
dfs(root.left);
dfs(root.right);
}
}
207. 课程表 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 创建一个图,用来保存每个节点以及它相邻的邻接点信息。
- 给定一个访问数组,数组的大小就是课程数的大小。初值全为0。
为0表示没有被dfs访问。
为1表示当前结点启动的dfs访问了。
3.为-1表示其他节点启动的dfs访问了。
- 对每个节点进行dfs。
- dfs的作用就是找有没有
成环
。如果成环,那么返回false
,没有成环返回true
。- 终止条件:
- visited[i] == -1,表示当前结点已经被其他节点启动的dfs访问过了,不需要再重复搜索,返回true。
- visited[i] == 1,表示在当前结点启动的dfs,该节点已经是被第二次访问了,那么说明有环,返回false。
- 每次结点访问了,改变访问数组情况,置为1,即:当前结点开启的dfs访问了。
- 然后对当前结点的临接结点进行dfs,如果有成环的情况,就返回false。如果遍历完,都没有发现环,那么就将结点访问情况标记为-1,即:该节点已经被其他节点开启的dfs访问过了。
- 最后没成环,返回false即可。
- 在主循环中,一旦发现成环,直接返回false。没有循环走完,还没发现,直接返回true。
- 代码:
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 图的创建
List<List<Integer>> adjacency = new ArrayList<>();
for(int i = 0; i < numCourses; i++){
adjacency.add(new ArrayList<>());
}
int[] visited = new int[numCourses];
//添加先验课
for(int[] cp : prerequisites){
adjacency.get(cp[1]).add(cp[0]);
}
//
for(int i = 0; i < numCourses; i++){
if(!dfs(adjacency, visited, i)){
return false;
}
}
return true;
}
private boolean dfs(List<List<Integer>> adjacency, int[] visited, int i){
if(visited[i] == 1){
return false;
}
if(visited[i] == -1){
return true;
}
visited[i] = 1;
for(Integer j : adjacency.get(i)){
if(!dfs(adjacency, visited, j)){
return false;
}
}
visited[i] = -1;
return true;
}
}
- 总结:
- 首先建立临接结点图,同时还有一个访问数组。
- dfs函数的作用就是判断给定的当前结点进行访问有没有成环。成环返回false,没有返回true。
- 在dfs函数中,设置终止的条件,即:当前结点在访问数组中的值情况,为1表示在当前传入的节点启动的dfs已经是被第二次访问过了。为-1,表示当前传入的节点已经被其他节点启动的dfs访问过了,不需要再重复搜索了。
- 如果传入结点没有被访问,那么置为1(当前结点启动的dfs已经访问过了),随后对当前结点的所有临接结点进行遍历,看是否成环。
- 最后将当前结点值为-1,提示提起结点启动的dfs,这个结点自己已经遍历过了,不要再重复访问了。
- 最后返回true。
226. 翻转二叉树 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
1.题目本身是一个递归解,父问题需要向子问题寻找答案,而此时父问题的答案和子问题的答案一致(都是进行翻转),因此是递归解。
2. 既然是递归解,设置问题的出口,当判断的节点为空的时候,那么就是到了叶子结点了,返回空即可。
3. 此时就是不断的进行向左和向右递归,分别得到左右子树的样子。
4. 然后将根节点的左设置为递归出来的右,根节点的右设置为递归出来的左即可。
5. 最后返回。
- 代码:
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
236. 二叉树的最近公共祖先 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 寻找公共祖先,根据题目分析,这个公共节点,要么不存在,要么是根节点,要么存在根节点的左或者右。
- 不存在:给定的p、q根本就没有在树中。
- 根节点:p、q存在树的两侧。
- 存在左或者右:p、q都在根节点的同一侧。
- 再思考题目本身,题目本身同样是一个递归解,它也需要向它的父结点进行汇报,因此,不用额外写dfs函数。
- 设置递归出口:当走到叶子结点的时候,直接返回空。如果根节点是p、q的任意一个,那么返回root。
- 进行递归: 向左和向右求出答案。
- 如果返回的左右答案都是空,那么属于情况1,p、q结点没有在树中。
- 如果返回的节点只有一个为空,那么返回另一侧,因为当有一侧返回为空的话,那么说明p、q不存在在该侧,存在于另一侧。
- 如果返回都有值,那么返回根节点,因为左右同时报告,我这里有节点,所以只可能公共节点是父结点。
- 代码:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return null;
}
if(root == p || root == q){
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null && right == null){
return null;
}
if(left == null){
return right;
}
if(right == null){
return left;
}
return root;
}
}
297. 二叉树的序列化与反序列化 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 采用统一的遍历方式,这里选用先序遍历。
- 如果遇见了叶子结点,那么要记录下叶子结点的情况,即:遇见叶子就返回"null,",这里","的原因是和其他值做区分。
- 然后序列化函数本身是递归的,那么就依次递归左和右。
- 每次返回的时候,要返回根节点的值+","+左子树值+右子树的值。同样,这里的逗号是区分每一次遍历的值。
示例给出的先序遍历:“1,2,null,null,3,4,null,null,5,null,null”。- 反序列化,传入的参数是String,即:遍历得到的序列化内容,然后根据","进行分割。得到一个字符串数组。
- 使用List的实现子类,这里使用"LinkedList",至于为什么不用ArrayList,可以从底层实现考虑,LinkedList底层是链表结构,对于插入和删除效率高于ArrayList。
- 使用一个辅助函数buildTree,将字符串数组传入。
- 在该方法中,作用就是构建树,构建树的过程也是一个不断递归的过程,和序列化的过程整好相反。
- 设置出口:如果第一个值就是"null",那么说明这是一个不存在的节点,即:它的父结点必然是一个叶子结点,但是反序列化是必须要得到和序列化之前一样的结构,所以要先将值删掉,同时返回一个null值。
- 构建根节点:如果第一个节点不是null,那么就是根节点,这是先序遍历的特性,然后移除该节点,同时进行左右递归,把左右子树挂上即可。
- 最后返回构建的树。
- 代码:
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if(root == null){
return "null,";
}
String left = serialize(root.left);
String right = serialize(root.right);
return root.val + "," + left + right;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] temp = data.split(",");
List<String> tempList = new LinkedList<>(Arrays.asList(temp));
return buildTree(tempList);
}
public TreeNode buildTree(List<String> tempList){
if(tempList.get(0).equals("null")){
tempList.remove(0);
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(tempList.get(0)));
tempList.remove(0);
root.left = buildTree(tempList);
root.right = buildTree(tempList);
return root;
}
}
337. 打家劫舍 III - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 这是dfs+dp的题,这里只说dfs的部分。dp部分很好理解。
- 因为是一棵树,那么偷完所有的,就必然会深度优先,这里递归的出口同样是叶子,只不过返回的是一个{0,0},返回数组,索引0表示不偷能得到的最大金额,索引1表示偷能得到的最大金额。
- 之后进行dfs,然后得到一个返回数组,然后判断是偷还是不偷能得到的金额最大。
- 代码:
class Solution {
public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0],res[1]);
}
public int[] dfs(TreeNode root){
if(root == null){
return new int[]{0,0};
}
int[] left = dfs(root.left);
int[] right = dfs(root.right);
int[] dp = new int[2];
dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
dp[1] = root.val + left[0] + right[0];
return dp;
}
}
437. 路径总和 III - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 本题思路就是使用两次dfs,但是两次dfs不是作用在同一个地方。
- 首先题目说了:路径不需要从根节点开始,也不需要在叶子结点结束,只是方向必须向下,那么必然dfs。
- dfs函数,该函数只是找路径,不要返回值,设置一个全局变量res记录就好。
- 函数中,首先对出口进行定义,因为不需要返回值,那么叶子结点的子结点直接return;当不是叶子结点的子结点的时候,先对目标值做减法,然后判断目标值是否为0,为0了,说明找到一条。否则对该节点的左右子树进行dfs。
- 该题目函数本身就是一个dfs,因为不一定要从根节点开始,那么先设置出口,需要返回值,return 0;先从根节点开始,如果当节点没有找到合适的路径,那么就从根节点的左右开始,即:调用函数本身来进行递归查找。
- 代码:
class Solution {
private int res = 0;
public int pathSum(TreeNode root, int targetSum) {
if(root == null){
return 0;
}
dfs(root, targetSum);
pathSum(root.left, targetSum);
pathSum(root.right, targetSum);
return res;
}
public void dfs(TreeNode root, int targetSum){
if(root == null){
return;
}
targetSum -= root.val;
if(targetSum == 0){
res++;
}
dfs(root.left, targetSum);
dfs(root.right, targetSum);
}
}
538. 把二叉搜索树转换为累加树 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 看题目给的示例,明白是从最右端开始变化的,那么进行dfs。
- dfs是否需要返回值?不需要,因为改变值,直接设置一个全局的变量就好,每次对节点值进行累加。
- 进行dfs,一路向右,然后节点值累加,赋给当前结点,然后再dfs左边。
- 树的累加完成。主函数调用即可。
- 代码:
class Solution {
private int sum = 0;
public TreeNode convertBST(TreeNode root) {
dfs(root);
return root;
}
private void dfs(TreeNode root){
if(root == null){
return;
}
dfs(root.right);
sum += root.val;
root.val = sum;
dfs(root.left);
}
}
543. 二叉树的直径 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 先说一下题目的意思:二叉树直径,题目的意思是:树中任意两个结点的最短路径最大值。看了看,最短路径最大值???根路径长度的定义:两节点之间的路径长度 = 他们之间边的数目。那么边的数目 = 结点数- 1,那么这样换算过来就是求路径经过结点数的最大值- 1。
- 如何求经过结点个数?
- 那么也就是说对该节点来说左子树向右遍历最多有L个节点(就是左子树的深度),同理,向右遍历最多有R个节点(就是右子树的深度),那么以该节点为起点的路径经过的节点最大值就是L + R + 1.
- dfs是否需要返回值?需要,因为有深度,其实就是用来记录每个节点的高度。
- 在初始化路径的时候最小为1,那么得到的了结果,答案 = 经过路径的节点数的最大值 - 1。
- 代码:
class Solution {
private int res = 1;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return res - 1;
}
private int dfs(TreeNode root){
if(root == null){
return 0;
}
int left = dfs(root.left);
int right = dfs(root.right);
res = Math.max(res, left + right + 1);
return Math.max(left, right) + 1;
}
}
617. 合并二叉树 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 合并的话,就是左和左对齐相加,右和右对齐相加。
- 该函数本身是递归的,为什么?因为父问题找子问题要答案,子问题向它的子问题要答案,同样返回的都是树的结构,只是需要不断向上做返回。
- 分别做空判断。有一个为空,返回另外一个。
- 然后根节点就是两个树的根节点之和,随后就是分别对齐相加了。
- 返回即可。
- 代码:
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null){
return root2;
}
if(root2 == null){
return root1;
}
TreeNode root = new TreeNode(root1.val + root2.val);
root.left = mergeTrees(root1.left, root2.left);
root.right = mergeTrees(root1.right, root2.right);
return root;
}
}
17. 电话号码的字母组合 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 使用深度优先遍历,进行一个枚举,输出所有可能的结果。
- 使用一个
list
保存最后的结果,numMap
中保存所有数字(除1)的对应字符串。 dfs
的参数分别是结果list
,电话号码numMap
,给定的digits
,下标index
,每次可以组合结果com
- 一旦
index
== 给定的长度,那么就向结果list
中添加 - 如果不等的话,那么就先取出给定的
digits
中的下标位,然后在numMap
中找这个数字对应的字符串,并求的这个字符串的长度。- 然后循环,分别添加进可能的组合
com
中,然后再进行dfs
,此时的index
要+1,再删除此index
位置的元素,因为此时已经走到末尾了,可能某些节点的子结点还没有访问到。所以要删除给定index
位置的元素,让其向其他的方向搜索。
- 然后循环,分别添加进可能的组合
- 一旦
-
代码:
class Solution { public List<String> letterCombinations(String digits) { List<String> res = new ArrayList<>(); if(digits.length() == 0){ return res; } Map<Character,String> numMap = new HashMap<>(); numMap.put('2',"abc"); numMap.put('3', "def"); numMap.put('4', "ghi"); numMap.put('5', "jkl"); numMap.put('6', "mno"); numMap.put('7', "pqrs"); numMap.put('8', "tuv"); numMap.put('9', "wxyz"); dfs(res, numMap, digits, 0, new StringBuffer()); return res; } public void dfs(List<String> res, Map<Character, String> numMap, String digits, int index, StringBuffer com){ if(index == digits.length()){ res.add(com.toString()); } else { char digit = digits.charAt(index); String letters = numMap.get(digit); int letterCount = letters.length(); for(int i = 0; i < letterCount; i++){ com.append(letters.charAt(i)); dfs(res, numMap, digits, index + 1, com); com.deleteCharAt(index); } } } }
114. 二叉树展开为链表 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 根据题目要求可以得知是一个先序遍历的结果。
- 使用dfs的方法,采用先序。
- 将每个节点放入list中。
- 只需要将每个节点的左节点置空,右节点置下一个节点即可。
-
代码
class Solution { List<TreeNode> list = new LinkedList<>(); public void flatten(TreeNode root) { dfs(root); int size = list.size(); TreeNode pre = null, cur = null; for(int i = 1; i < size; i++){ pre = list.get(i - 1); cur = list.get(i); pre.left = null; pre.right = cur; } } public void dfs(TreeNode root){ if(root == null){ return; } list.add(root); dfs(root.left); dfs(root.right); } }
39. 组合总和 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 定义dfs函数,
dfs(int[] candidates, int target, int idx, List<List<Integer>> res, List<Integer> cur)
- candidates:原数组,从该数组进行数的选择
- target:目标值还需要多少数才能凑齐。初始值为target,表示还没有选择任何数;当target=0的时候,代表选择的数凑成了target。
- idx表示现在在candidates数组中第几位
- res是最后结果
- cur是当前结果
- 只要target为0,那么就把当前结果集加入到最后结果集中
- 如果idx等于数组长度(即:扫描到了最后都没有凑齐目标值)或者target小于0,那么也返回。
- 进行枚举,枚举candidates数组的每个数使用的次数。目的就是确保那些数值可以被加入到当前结果集中。
- 条件就是当前数值*当前使用频次数小于等于target,再进行dfs
- 能有返回,那么就将当前数值加入到当前结果集。
- 进行回溯,此时的当前结果集是所有的从顶到底的所有结果,那么就要进行回溯,回溯的时候总是把数组最后一位弹出。
-
题目给的示例
[2,3,6,7] 7 如果不进行回溯的话那么就是 [[7],[7,7,6,7,6,3,7,6,3,7,6,3,2,7,6,3,7,6,3,2,7,6,3]] 是因为在上一个循环的时候,它的作用就是判断当前数能否使用以及使用的次数。所以最后回溯的目的就是判断到底哪些数才能真正加入最后的结果集 注意此时的回溯和17题的回溯不同,本题的回溯是在所有的结果添加完成之后再重新进行一次遍历,这里进行的回溯,而17题是在第一次循环体内就进行的筛除。
-
代码
class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<>(); List<Integer> cur = new ArrayList<>(); dfs(candidates, target, 0, res, cur); return res; } public void dfs(int[] candidates, int target, int idx, List<List<Integer>> res, List<Integer> cur){ if(target == 0){ res.add(new ArrayList<>(cur)); return; } if(idx == candidates.length || target < 0){ return; } for(int i = 0; candidates[idx] * i <= target; i++){ dfs(candidates, target - candidates[idx] * i, idx + 1, res, cur); cur.add(candidates[idx]); } for(int i = 0; candidates[idx] * i <= target; i++){ cur.remove(cur.size() - 1); } } }
46. 全排列 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 使用深度优先策略进行。因为length不超过6
- 全排列的每个元素的长度都是给定数组的长度,那么进行
dfs
的时候,只要当前list的长度和nums数组的长度一致的时候,就可以像结果集中添加了。 - 对数组进行遍历,一旦当前这个list中包含当前数值,跳过,进行下一轮(因为题目要求不包含重复数字);否则,添加进list,然后再进行dfs。
- 同样,一旦走到叶子结点的时候,说明此时以该数值结尾已经走完了,那么需要将此数值移除,然后返回它的父结点,再对其他的方向进行dfs。
- 为什么不使用idx?
- 因为本题是找到所有的排列情况,如果使用了idx,那么在第二个数的时候,就只能找第二个数及之后的,没有涉及到第一个。
-
代码
class Solution { List<List<Integer>> res = new ArrayList<>(); public List<List<Integer>> permute(int[] nums) { List<Integer> list = new ArrayList<>(); dfs(list, nums); return res; } public void dfs(List<Integer> list, int[] nums){ if(list.size() == nums.length){ res.add(new ArrayList<>(list)); return; } for(int i = 0; i < nums.length; i++){ if(list.contains(nums[i])){ continue; } list.add(nums[i]); dfs(list, nums); //删除最后一个节点 list.remove(list.size() - 1); } } }
78. 子集 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
-
使用dfs。
-
退出的条件,因为是自己,所以一旦某一路径上的节点数大于给定数组的长度,那么就返回。
-
没有满足退出条件,那么就向结果集添加当前路径的节点,然后向下选择,每次给路径当前节点,再接着进行dfs,对下一个节点做选择。一旦到达叶子结点的时候,那么就删除当前结点的最后一个,让该路径做其他选择。
-
因为求子集,所以需要说明每次进行的是第几个。所以要有一个下标标致。
-
本题每次在循环的时候,虽然是从idx出发的,但是每次向下进行传递的是i,而不是idx,idx只告诉了本次应该从哪个位置开始,要先把本次的添加进去,然后添加剩下的,也就是从给定的idx到数组长度,那么就是控制一个循环变量。向下dfs也是传递这个变量。
-
代码
class Solution { List<List<Integer>> res = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { dfs(nums,0, nums.length, new ArrayList<>()); return res; } private void dfs(int[] nums, int idx, int n, List<Integer> path){ if(path.size() > n){ return; } res.add(new ArrayList<>(path)); for(int i = idx; i < n; i++){ path.add(nums[i]); dfs(nums, i + 1, n, path); path.remove(path.size() - 1); } } }
79. 单词搜索 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 使用深度优先。
- 什么时候返回?以及返回什么?
- 因为父问题是判断能不能成功,那么子问题的返回结果自然就是能不能成功。
- 每次进行dfs的时候回传入一个idx下标,初始为0,代表扫描到的字符(给定字符串的第idx个位置正好就是扫描到的字符),那么当这个idx等于给定字符串的长度的时候就返回。
- 哪些时候返回不成功呢?只要行、列不符合边界,以及当前扫描到的字符不是第idx位置的字符,就返回false;
- 每次在dfs的时候,要将当前给定的坐标位置的元素设置为’*’,当然先用一个temp保存当前位置的元素,然后就对四个位置进行dfs,最后要将temp还原给指定位置。
- 这样做的目的就是我找到一个入口了之后,就确认了这个字符是其中的一个,扣掉,向其他方向找剩余的字符即可。
- 返回只要四个方向有一个方向可以找到就行。
-
代码
class Solution { public boolean exist(char[][] board, String word) { int row = board.length; int col = board[0].length; for(int i = 0; i < row; i++){ for(int j = 0; j < col; j++){ if(dfs(board, i, j, word, 0)){ return true; } } } return false; } private boolean dfs(char[][] board, int row, int col, String word, int idx){ if(idx == word.length()){ return true; } if(row < 0 || row >= board.length || col < 0 || col >= board[0].length || board[row][col] != word.charAt(idx)){ return false; } char temp = board[row][col]; board[row][col] = '*'; boolean up = dfs(board, row - 1, col, word, idx + 1); boolean down = dfs(board, row + 1, col, word, idx + 1); boolean left = dfs(board, row, col - 1, word, idx + 1); boolean right = dfs(board, row, col + 1, word, idx + 1); boolean found = up || down || left || right; board[row][col] = temp; return found; } }
22. 括号生成 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 假设每生成一个
(
+1,每生成一个)
-1。 - 那么按照这个思路进行dfs即可。
- 注:括号生成一定是一左一右的,那么长度自然就是2n。
-
代码:
class Solution { //每次传入当前位置i,指定长度,得分值,最大下标位置,当前取得的字符串。 private void dfs(int i, int n, int score, int max, StringBuilder path, List<String> res){ //只有当当前最值等于指定的长度,同时得分也正好为0,那么进行添加。 if(i == n){ if(score == 0){ res.add(path.toString()); } }else { //+1是生成左括号,最多的左括号不能超过指定的长度(一左一右的远足) if(score + 1 <= max){ dfs(i + 1, n, score + 1, max, path.append("("), res); //记得回溯 path.deleteCharAt(path.length() - 1); } //-1是生成右括号,同时保证生成的右括号也必须要有(也是一左一右原则) if(score - 1 >= 0){ dfs(i + 1, n, score - 1, max, path.append(")"), res); path.deleteCharAt(path.length() - 1); } } } public List<String> generateParenthesis(int n) { List<String> res = new LinkedList<>(); dfs(0, n * 2, 0, n, new StringBuilder(), res); return res; } }
129. 求根节点到叶节点数字之和 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 使用dfs,传入参数为root和当前值i。
- 如果root为空,那么就已经到了叶子结点,返回0;
- 使用一个临时变量temp = i * 10 + root.val;这样就记录下了从上到下的值。
- 只有当左右子树都为空的时候,那么就返回这个临时变量值。
- 最后返回的是进行dfs左右子树。
-
代码:
class Solution { public int sumNumbers(TreeNode root) { return dfs(root, 0); } public int dfs(TreeNode root, int i){ if(root == null){ return 0; } int temp = i * 10 + root.val; if(root.left == null && root.right == null){ return temp; } return dfs(root.left, temp) + dfs(root.right, temp); } }
110. 平衡二叉树 - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 不仅要判断根节点的左右平衡,也要判断左右子树是否是平衡二叉树。
-
代码:
class Solution { public boolean isBalanced(TreeNode root) { if(root == null){ return true; }else { return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.right) && isBalanced(root.left); } } public int depth(TreeNode node){ if(node == null){ return 0; }else { return Math.max(depth(node.left), depth(node.right)) + 1; } } }
113. 路径总和 II - 力扣(LeetCode) (leetcode-cn.com)
- 思路:
- 使用dfs。
- 当传入的节点为空的时候,返回。
- 每次先减去节点的值,添加到路径节点集合,只有当值为0,并且当前结点为叶子结点了,才能加入结果集合中,并且添加完成结果集合添加之后呢,要从当前路径集合中删除上一次的。然后返回。
- 进行左右递归,但是在递归完成之后呢。也同样删除最后一个。
- 删除的原因:以实例1举例:当左右递归到11节点的时候,那么再进行dfs,此时左边是没有满足且减去之后值为-1,但是此时path还是加进去了,但是正确答案是没有7这个结点的,因此需要减去。
目的:因为在给每次path加节点的时候,没有进行判断,且在每一次有不满足或者满足的时候,需要将上一次的元素进行删除。
- 代码:
class Solution { public List<List<Integer>> pathSum(TreeNode root, int targetSum) { List<List<Integer>> res = new ArrayList<>(); List<Integer> path = new ArrayList<>(); dfs(res, root, targetSum, path); return res; } public void dfs(List<List<Integer>> res, TreeNode root, int targetSum, List<Integer> path){ if(root == null){ return; } targetSum -= root.val; path.add(root.val); //要满足题目条件,整数目标和,到叶子结点。 if(targetSum == 0 && root.left == null && root.right == null){ res.add(new ArrayList<>(path)); //不删除的话,那么下一个满足条件的时候,会把上次的结果添加进来。 path.remove(path.size() - 1); return; } dfs(res, root.left, targetSum, path); dfs(res, root.right, targetSum, path); //同样不删的话,那么只会保存第一次成功的,因为在第一次成功了之后,后面不管总会有满足的,只要进入if语句,那么前面不管是否合法,它都进行了添加。 path.remove(path.size() - 1); } }