【题解】—— LeetCode一周小结6


【题解】—— 每日一道题目栏


上接:【题解】—— LeetCode一周小结5

5.跳跃游戏 VI

题目链接:1696. 跳跃游戏 VI

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。

一开始你在下标 0 处。每一步,你最多可以往前跳 k 步,但你不能跳出数组的边界。也就是说,你可以从下标 i 跳到 [i + 1, min(n - 1, i + k)] 包含 两个端点的任意位置。

你的目标是到达数组最后一个位置(下标为 n - 1 ),你的 得分 为经过的所有数字之和。

请你返回你能得到的 最大得分 。

示例 1:

输入:nums = [1,-1,-2,4,-7,3], k = 2

输出:7

解释:你可以选择子序列 [1,-1,4,3] (上面加粗的数字),和为 7 。

示例 2:

输入:nums = [10,-5,-2,4,0,3], k = 3

输出:17

解释:你可以选择子序列 [10,4,3] (上面加粗数字),和为 17 。

示例 3:

输入:nums = [1,-5,-20,4,-1,3,-6,-3], k = 2

输出:0

提示:

1 <= nums.length, k <= 105
-104 <= nums[i] <= 104

题解:
方法:动态规划 + 优先队列优化
        动态规划的思想,具体来说是使用单调递减队列来优化动态规划过程。以下是算法的主要思路:

  1. 我们使用一个优先队列(PriorityQueue)来存储二元组 (f[j], j),其中 f[j] 表示到达位置 j 的最大得分,j 表示位置的索引。
  2. 初始时,将第一个元素 (nums[0], 0) 加入队列,并初始化 ansnums[0]
  3. 遍历数组 nums,从位置 1 开始,对于每个位置 i,执行以下步骤:
    • 移除队列中索引超过限制 k 的元素,确保队列中的元素满足限制条件。
    • 计算当前位置的得分 ans,更新为队列中最大得分加上当前位置的得分 nums[i]
    • 将当前位置 (ans, i) 加入队列。
  4. 返回最终的 ans,即到达最后一个位置的最大得分。

        一个递减的得分队列可以确保队列中的元素始终按照得分递减的顺序排列。这样做的好处是在动态规划的过程中,我们只需关注在当前位置之前的 k 个位置中得分最大的情况,而不需要考虑更远的位置,从而减少了计算的复杂度。

import java.util.PriorityQueue;class Solution {public int maxResult(int[] nums, int k) {int n = nums.length;// 优先队列,元素为数组 [f[j], j]PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> b[0] - a[0]);pq.offer(new int[]{nums[0], 0});int ans = nums[0];for (int i = 1; i < n; ++i) {// 如果堆顶元素的索引与当前索引之差大于k,则移除堆顶元素while (i - pq.peek()[1] > k) {pq.poll();}ans = pq.peek()[0] + nums[i];pq.offer(new int[]{ans, i});}return ans;}
}

方法:动态规划 + 单调队列优化
        使用动态规划和单调队列的结合,以下是主要思路:

  1. 动态规划: 定义 dp[i] 表示到达位置 i 的最大得分。初始时,dp[0] = nums[0]
  2. 单调队列: 使用双端队列(deque)维护一个递减的队列,队列中保存的是二元组 (dp[j], j)。队列的头部始终保持最大的 dp[j]
  3. 遍历数组: 从位置 1 开始遍历数组 nums,对于每个位置 i,执行以下步骤:
    • 移除队列中索引超过限制 k 的元素,确保队列中的元素满足限制条件。
    • 计算当前位置的得分 dp[i],更新为队列中最大得分加上当前位置的得分 nums[i]
    • 将当前位置 (dp[i], i) 加入队列。
    • 移除队列中不满足单调性的元素,确保队列中的元素按照得分递减的顺序排列。
  4. 返回最后一个位置的最大得分 dp[n-1]

        这种做法的关键点在于使用单调队列维护一个在滑动窗口内的最大得分,并通过动态规划的思想递推计算每个位置的最大得分。这样可以有效减少不必要的计算,提高算法的效率。

import java.util.ArrayDeque;
import java.util.Deque;class Solution {public int maxResult(int[] nums, int k) {int n = nums.length;// 单调队列,元素为数组 [f[j], j]Deque<int[]> q = new ArrayDeque<>();q.offerLast(new int[]{nums[0], 0});int ans = nums[0];for (int i = 1; i < n; ++i) {// 移除队首元素,直到队首的 j 满足限制while (i - q.peekFirst()[1] > k) {q.pollFirst();}ans = q.peekFirst()[0] + nums[i];// 移除队尾元素,直到队尾的 j 满足单调性while (!q.isEmpty() && ans >= q.peekLast()[0]) {q.pollLast();}q.offerLast(new int[]{ans, i});}return ans;}
}

6.魔塔游戏

题目链接:LCP 30. 魔塔游戏

小扣当前位于魔塔游戏第一层,共有 N 个房间,编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums,其中正数表示道具补血数值,即血量增加对应数值;负数表示怪物造成伤害值,即血量减少对应数值;0 表示房间对血量无影响。

小扣初始血量为 1,且无上限。假定小扣原计划按房间编号升序访问所有房间补血/打怪,为保证血量始终为正值,小扣需对房间访问顺序进行调整,每次仅能将一个怪物房间(负数的房间)调整至访问顺序末尾。请返回小扣最少需要调整几次,才能顺利访问所有房间。若调整顺序也无法访问完全部房间,请返回 -1。

示例 1:

输入:nums = [100,100,100,-250,-60,-140,-50,-50,100,150]

输出:1

解释:初始血量为 1。至少需要将 nums[3] 调整至访问顺序末尾以满足要求。

示例 2:

输入:nums = [-200,-300,400,0]

输出:-1

解释:调整访问顺序也无法完成全部房间的访问。

提示:

1 <= nums.length <= 105

-105 <= nums[i] <= 105

题解:
方法:贪心+优先队列
        通过模拟小扣在每个房间的行走过程,使用优先队列来保存怪物房间的伤害,确保在小扣的血量小于等于0时调整访问顺序。以下是思路:

  1. 计算总血量: 首先计算小扣在所有回合结束后的总血量 sum,遍历 nums 数组,将每个元素累加到 sum 中。

  2. 检查总血量是否为正: 如果总血量 sum 小于等于0,表示小扣无法在所有回合结束后保持正血量,直接返回 -1。

  3. 开始模拟过程: 初始化小扣的血量 blood 为1,初始化一个优先队列 pq 用来保存怪物房间的伤害。同时初始化 last 记录调整的次数。

  4. 遍历每个房间: 使用 for 循环遍历 nums 数组,对于每个房间进行以下操作:

    • 如果当前房间为怪物房间(nums[i] < 0),将其伤害值加入优先队列 pq 中。
    • 如果加入当前怪物房间的伤害后,小扣的血量 blood + nums[i] 小于等于0,说明小扣在这回合结束后将会死亡,需要将之前扣除最多的血量的怪物房间移到末尾。记录移动的次数 last++,同时从队列中弹出最大伤害的怪物,将其伤害值加回小扣的血量中。
    • 更新小扣的血量,将当前房间的血量变化加到小扣的血量中。
  5. 返回结果: 返回调整的次数 last

import java.util.PriorityQueue;class Solution {public int magicTower(int[] nums) {int sum = 1;// 计算初始血量for (int i = 0; i < nums.length; i++) {sum += nums[i];}// 如果所有回合后的血量为非正数,返回 -1if (sum <= 0) {return -1;}// 开始模拟long blood = 1; // 初始血量PriorityQueue<Integer> pq = new PriorityQueue<>(); // 优先队列用于保存怪物房间的伤害int last = 0; // 记录调整的次数for (int i = 0; i < nums.length; i++) {if (nums[i] < 0) {pq.offer(nums[i]); // 将怪物房间的伤害加入队列// 如果加入当前怪物房间的伤害后,小扣的血量小于等于0if (blood + nums[i] <= 0) {last++; // 记录移动的次数blood -= pq.poll(); // 加回之前扣除最多的血量}}blood += nums[i]; // 更新小扣的血量}return last; // 返回调整的次数}
}

7.二叉树的堂兄弟节点 II

题目链接:2641. 二叉树的堂兄弟节点 II

给你一棵二叉树的根 root ,请你将每个节点的值替换成该节点的所有 堂兄弟节点值的和 。

如果两个节点在树中有相同的深度且它们的父节点不同,那么它们互为 堂兄弟

请你返回修改值之后,树的根 root 。

注意,一个节点的深度指的是从树根节点到这个节点经过的边数。

示例 1:

请添加图片描述

输入:root = [5,4,9,1,10,null,7]

输出:[0,0,0,7,7,null,11]

解释:上图展示了初始的二叉树和修改每个节点的值之后的二叉树。

  • 值为 5 的节点没有堂兄弟,所以值修改为 0 。
  • 值为 4 的节点没有堂兄弟,所以值修改为 0 。
  • 值为 9 的节点没有堂兄弟,所以值修改为 0 。
  • 值为 1 的节点有一个堂兄弟,值为 7 ,所以值修改为 7 。
  • 值为 10 的节点有一个堂兄弟,值为 7 ,所以值修改为 7 。
  • 值为 7 的节点有两个堂兄弟,值分别为 1 和 10 ,所以值修改为 11 。

示例 2:

请添加图片描述

输入:root = [3,1,2]

输出:[0,0,0]

解释:上图展示了初始的二叉树和修改每个节点的值之后的二叉树。

  • 值为 3 的节点没有堂兄弟,所以值修改为 0 。
  • 值为 1 的节点没有堂兄弟,所以值修改为 0 。
  • 值为 2 的节点没有堂兄弟,所以值修改为 0 。

提示:

树中节点数目的范围是 [1, 105] 。

1 <= Node.val <= 104

题解:
方法:BFS(广度优先搜索)遍历二叉树
        
使用BFS(广度优先搜索)进行了两次遍历二叉树:

  1. 初始化根节点值为0: 将根节点的值设置为0。

  2. 第一次 BFS 遍历: 使用 BFS 遍历二叉树,计算出每一层的节点值之和 nextLevelSum,同时将当前层的所有节点加入队列 q 中。

  3. 第二次 BFS 遍历: 再次遍历每个节点,计算其子节点值之和,并用 nextLevelSum 减去子节点值之和来更新子节点的值。

  4. 重复以上步骤直至队列为空: 重复进行第一次和第二次 BFS 遍历,直到队列为空,表示已经遍历完所有层级的节点。

  5. 返回根节点: 返回经过替换处理后的根节点。

/*** 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.ArrayList;
import java.util.List;class Solution {public TreeNode replaceValueInTree(TreeNode root) {root.val = 0;List<TreeNode> q = List.of(root);while (!q.isEmpty()) {List<TreeNode> tmp = q;q = new ArrayList<>();// 计算下一层的节点值之和int nextLevelSum = 0;for (TreeNode node : tmp) {if (node.left != null) {q.add(node.left);nextLevelSum += node.left.val;}if (node.right != null) {q.add(node.right);nextLevelSum += node.right.val;}}// 再次遍历,更新下一层的节点值for (TreeNode node : tmp) {// 计算当前节点子节点的值之和int childrenSum = (node.left != null ? node.left.val : 0) +(node.right != null ? node.right.val : 0);// 更新子节点的值为下一层节点值之和减去子节点值之和if (node.left != null) node.left.val = nextLevelSum - childrenSum;if (node.right != null) node.right.val = nextLevelSum - childrenSum;}}return root;}
}

8.二叉树的堂兄弟节点

题目链接:993. 二叉树的堂兄弟节点

在二叉树中,根节点位于深度 0 处,每个深度为 k 的节点的子节点位于深度 k+1 处。

如果二叉树的两个节点深度相同,但 父节点不同 ,则它们是一对堂兄弟节点。

我们给出了具有唯一值的二叉树的根节点 root ,以及树中两个不同节点的值 x 和 y 。

只有与值 x 和 y 对应的节点是堂兄弟节点时,才返回 true 。否则,返回 false。

示例 1:

请添加图片描述

输入:root = [1,2,3,4], x = 4, y = 3

输出:false

示例 2:
请添加图片描述

输入:root = [1,2,3,null,4,null,5], x = 5, y = 4

输出:true

示例 3:

请添加图片描述

输入:root = [1,2,3,null,4], x = 2, y = 3

输出:false

提示:

二叉树的节点数介于 2 到 100 之间。

每个节点的值都是唯一的、范围为 1 到 100 的整数。

题解:
方法:DFS
        
希望得到某个节点的父节点&所在深度,:

/*** 查找 t 的「父节点值」&「所在深度」* @param root 当前搜索到的节点* @param fa root 的父节点* @param depth 当前深度* @param t 搜索目标值* @return [fa.val, depth]*/
int[] dfs(TreeNode root, TreeNode fa, int depth, int t);

之后按照遍历的逻辑处理即可。

需要注意的时,我们需要区分出搜索不到和**搜索对象为 root(没有 fa 父节点)**两种情况。

我们约定使用 −1 代指没有找到目标值 t,使用 0 代表找到了目标值 t,但其不存在父节点。

/*** 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 {public boolean isCousins(TreeNode root, int x, int y) {int[] xi = dfs(root, null, 0, x);int[] yi = dfs(root, null, 0, y);return xi[1] == yi[1] && xi[0] != yi[0];}int[] dfs(TreeNode root, TreeNode fa, int depth, int t) {if (root == null) return new int[]{-1, -1}; // 使用 -1 代表为搜索不到 tif (root.val == t) {return new int[]{fa != null ? fa.val : 1, depth}; // 使用 1 代表搜索值 t 为 root}int[] l = dfs(root.left, root, depth + 1, t);if (l[0] != -1) return l;return dfs(root.right, root, depth + 1, t);}
}

方法:BFS

class Solution {public boolean isCousins(TreeNode root, int x, int y) {int[] xi = bfs(root, x);int[] yi = bfs(root, y);return xi[1] == yi[1] && xi[0] != yi[0];}int[] bfs(TreeNode root, int t) {Deque<Object[]> d = new ArrayDeque<>(); // 存储值为 [cur, fa, depth]d.addLast(new Object[]{root, null, 0});while (!d.isEmpty()) {int size = d.size();while (size-- > 0) {Object[] poll = d.pollFirst();TreeNode cur = (TreeNode)poll[0], fa = (TreeNode)poll[1];int depth = (Integer)poll[2];if (cur.val == t) return new int[]{fa != null ? fa.val : 0, depth};if (cur.left != null) d.addLast(new Object[]{cur.left, cur, depth + 1});if (cur.right != null) d.addLast(new Object[]{cur.right, cur, depth + 1});}}return new int[]{-1, -1};}
}

9.二叉树的最近公共祖先

题目链接:236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:

在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1

输出:3

解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例 2:

在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4

输出:5

解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例 3:

输入:root = [1,2], p = 1, q = 2

输出:1

提示:

树中节点数目在范围 [2, 105] 内。

-109 <= Node.val <= 109

所有 Node.val 互不相同 。

p != q

p 和 q 均存在于给定的二叉树中。

题解:
方法:DFS
        查找二叉树中指定节点 p 和 q 的最近公共祖先节点。通过递归遍历左右子树,找到 p 和 q 的最近公共祖先节点。
这个递归解法通过深度优先搜索(DFS)来查找二叉树中节点 p 和节点 q 的最近公共祖先。

递归解析:

  1. 递归终止条件: 在递归的过程中,首先检查当前节点是否为空,或者当前节点是否为节点 p 或节点 q。如果是的话,直接返回当前节点,因为当前节点就是最近公共祖先。

  2. 递归向左子树查找: 若当前节点不是节点 p 或节点 q,则递归调用函数 lowestCommonAncestor 在左子树中查找节点 p 和节点 q 的最近公共祖先。递归调用的结果存储在变量 left 中。

  3. 递归向右子树查找: 同样地,递归调用函数 lowestCommonAncestor 在右子树中查找节点 p 和节点 q 的最近公共祖先,结果存储在变量 right 中。

  4. 判断最近公共祖先: 最后,根据递归调用的结果 leftright,判断以下情况:

    • 如果 leftright 都不为空,说明节点 p 和节点 q 分别位于当前节点的左右子树中,因此当前节点就是最近公共祖先,直接返回当前节点。
    • 如果 left 为空而 right 不为空,说明节点 p 和节点 q 同时位于右子树中,且右子树中有它们的最近公共祖先,因此返回 right
    • 如果 left 不为空而 right 为空,说明节点 p 和节点 q 同时位于左子树中,且左子树中有它们的最近公共祖先,因此返回 left
    • 如果 leftright 都为空,说明左右子树中都不包含节点 p 和节点 q,返回 null
  5. 返回结果: 最终返回最近公共祖先节点。

这个递归解法的时间复杂度为 O(N),其中 N 是二叉树中节点的数量,因为每个节点最多被访问一次。

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/class Solution {/*** 找到二叉树中指定节点 p 和 q 的最近公共祖先节点。* @param root 根节点* @param p    节点 p* @param q    节点 q* @return     最近公共祖先节点*/public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {// 如果根节点为空,或者根节点为 p 或 q,直接返回根节点if (root == null || root == p || root == q) {return root;}// 递归在左子树中寻找 p 和 q 的最近公共祖先节点TreeNode left = lowestCommonAncestor(root.left, p, q);// 递归在右子树中寻找 p 和 q 的最近公共祖先节点TreeNode right = lowestCommonAncestor(root.right, p, q);// 如果左子树中未找到 p 和 q 的公共祖先,则返回右子树的结果if (left == null) {return right;}// 如果右子树中未找到 p 和 q 的公共祖先,则返回左子树的结果if (right == null) {return left;}// 如果左右子树都找到了 p 和 q 的公共祖先,则当前节点为最近公共祖先节点return root;}
}

4种情况的展开写法如下:

class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null || 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; // 4.如果 `left` 和 `right` 都为空if(left == null) return right; // 2.如果 `left` 为空而 `right` 不为空if(right == null) return left; // 3.如果 `left` 不为空而 `right` 为空return root; // 1. if(left != null and right != null)如果 `left` 和 `right` 都不为空}
}

10. 二叉树的中序遍历

题目链接:94. 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

示例 1:
在这里插入图片描述

输入:root = [1,null,2,3]

输出:[1,3,2]

示例 2:

输入:root = []

输出:[]

示例 3:

输入:root = [1]

输出:[1]

提示:

树中节点数目在范围 [0, 100] 内

-100 <= Node.val <= 100

题解:
方法一:递归遍历
        先递归左子树,再访问根节点,接着递归右子树。

/*** 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.*;class Solution {private List<Integer> ans = new ArrayList<>(); // 用于存放中序遍历结果的列表/*** 执行中序遍历,返回遍历结果列表。* @param root 二叉树的根节点* @return 中序遍历结果列表*/public List<Integer> inorderTraversal(TreeNode root) {dfs(root); // 调用深度优先搜索函数进行中序遍历return ans; // 返回中序遍历结果列表}/*** 深度优先搜索函数,递归遍历二叉树,并将节点值存入结果列表。* @param root 当前处理的节点*/private void dfs(TreeNode root) {if (root == null) { // 递归终止条件:当前节点为空return;}dfs(root.left); // 递归遍历左子树ans.add(root.val); // 将当前节点值加入结果列表dfs(root.right); // 递归遍历右子树}
}

时间复杂度 O(n),空间复杂度 O(n)。其中 n 是二叉树的节点数,空间复杂度主要取决于递归调用的栈空间。


方法二:栈实现非递归遍历

二叉树的中序遍历,使用了迭代的方式,并且使用了辅助栈来模拟递归过程。

非递归的思路如下:

  1. 定义一个栈 stk
  2. 将树的左节点依次入栈
  3. 左节点为空时,弹出栈顶元素并处理
  4. 重复 2~3 的操作
/*** 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.*;class Solution {/*** 执行中序遍历,返回遍历结果列表。* @param root 二叉树的根节点* @return 中序遍历结果列表*/public List<Integer> inorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>(); // 用于存放中序遍历结果的列表Deque<TreeNode> stk = new ArrayDeque<>(); // 辅助栈,用于遍历过程中保存节点// 遍历二叉树while (root != null || !stk.isEmpty()) {if (root != null) {stk.push(root); // 将当前节点入栈root = root.left; // 移动到左子节点} else {root = stk.pop(); // 栈顶节点出栈ans.add(root.val); // 访问栈顶节点的值,因为是中序遍历,所以此时是左子树的最左侧节点root = root.right; // 移动到右子节点}}return ans; // 返回中序遍历结果列表}
}

方法三:Morris 实现中序遍历
Morris 遍历无需使用栈,空间复杂度为 O(1)。核心思想是:

遍历二叉树节点,

  1. 若当前节点 root 的左子树为空,将当前节点值添加至结果列表 ans 中,并将当前节点更新为 root.right
  2. 若当前节点 root 的左子树不为空,找到左子树的最右节点 prev(也即是 root 节点在中序遍历下的前驱节点):
  • 若前驱节点 prev 的右子树为空,将前驱节点的右子树指向当前节点 root,并将当前节点更新为 root.left。
  • 若前驱节点 prev 的右子树不为空,将当前节点值添加至结果列表 ans 中,然后将前驱节点右子树指向空(即解除 prev 与 root 的指向关系),并将当前节点更新为 root.right。
  1. 循环以上步骤,直至二叉树节点为空,遍历结束。
/*** 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.*;class Solution {/*** 执行中序遍历,返回遍历结果列表。* @param root 二叉树的根节点* @return 中序遍历结果列表*/public List<Integer> inorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>(); // 用于存放中序遍历结果的列表// 当根节点不为空时,执行遍历while (root != null) {if (root.left == null) { // 如果当前节点的左子节点为空ans.add(root.val); // 将当前节点的值加入结果列表root = root.right; // 移动到当前节点的右子节点} else { // 如果当前节点的左子节点不为空TreeNode prev = root.left; // 找到当前节点的左子树的最右下角节点while (prev.right != null && prev.right != root) {prev = prev.right;}if (prev.right == null) { // 如果最右下角节点的右子节点为空prev.right = root; // 将最右下角节点的右子节点指向当前节点root = root.left; // 移动到当前节点的左子节点} else { // 如果最右下角节点的右子节点不为空ans.add(root.val); // 将当前节点的值加入结果列表prev.right = null; // 恢复最右下角节点的右子节点为nullroot = root.right; // 移动到当前节点的右子节点}}}return ans; // 返回中序遍历结果列表}
}

11. 二叉树的前序遍历

题目链接:144. 二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

在这里插入图片描述

输入:root = [1,null,2,3]

输出:[1,2,3]

示例 2:

输入:root = []

输出:[]

示例 3:

输入:root = [1]

输出:[1]

示例 4:

在这里插入图片描述

输入:root = [1,2]

输出:[1,2]

示例 5:

在这里插入图片描述

输入:root = [1,null,2]

输出:[1,2]

提示:

树中节点数目在范围 [0, 100] 内

-100 <= Node.val <= 100

进阶:递归算法很简单,你可以通过迭代算法完成吗?

题解:
方法一:递归遍历
        我们先访问根节点,然后递归左子树和右子树。

/*** 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.*;class Solution {private List<Integer> ans = new ArrayList<>(); // 用于存放前序遍历结果的列表/*** 执行前序遍历,返回遍历结果列表。* @param root 二叉树的根节点* @return 前序遍历结果列表*/public List<Integer> preorderTraversal(TreeNode root) {dfs(root); // 调用深度优先搜索函数进行前序遍历return ans; // 返回前序遍历结果列表}/*** 深度优先搜索函数,递归遍历二叉树,并将节点值存入结果列表。* @param root 当前处理的节点*/private void dfs(TreeNode root) {if (root == null) { // 递归终止条件:当前节点为空return;}ans.add(root.val); // 将当前节点值加入结果列表dfs(root.left); // 递归遍历左子树dfs(root.right); // 递归遍历右子树}
}

方法二:栈实现非递归遍历
        使用了迭代的方式,并且利用了辅助栈来模拟递归过程,实现了二叉树的前序遍历。

栈实现非递归遍历的思路如下:

  1. 定义一个栈 stk,先将根节点压入栈
  2. 若栈不为空,每次从栈中弹出一个节点
  3. 处理该节点
  4. 先把节点右孩子压入栈,接着把节点左孩子压入栈(如果有孩子节点)
  5. 重复 2-4
  6. 返回结果
/*** 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.*;class Solution {/*** 执行前序遍历,返回遍历结果列表。* @param root 二叉树的根节点* @return 前序遍历结果列表*/public List<Integer> preorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>(); // 用于存放前序遍历结果的列表if (root == null) { // 如果根节点为空,直接返回空列表return ans;}Deque<TreeNode> stk = new ArrayDeque<>(); // 辅助栈,用于遍历过程中保存节点// 将根节点入栈stk.push(root);// 开始遍历,直到栈为空while (!stk.isEmpty()) {TreeNode node = stk.pop(); // 弹出栈顶节点ans.add(node.val); // 将当前节点值加入结果列表if (node.right != null) { // 如果当前节点有右子节点,将右子节点入栈stk.push(node.right);}if (node.left != null) { // 如果当前节点有左子节点,将左子节点入栈stk.push(node.left);}}return ans; // 返回前序遍历结果列表}
}

方法三:Morris 实现前序遍历
        利用 Morris Traversal 的思想实现了二叉树的前序遍历,其时间复杂度为 O(n),其中 n 是二叉树中的节点数。Morris Traversal 算法的优点是空间复杂度为 O(1),不需要额外的栈空间。核心思想是:

遍历二叉树节点,

  1. 若当前节点 root 的左子树为空,将当前节点值添加至结果列表 ansansans 中,并将当前节点更新为 root.right
  2. 若当前节点 root 的左子树不为空,找到左子树的最右节点 pre(也即是 root 节点在中序遍历下的前驱节点):
  • 若前驱节点 pre 的右子树为空,将当前节点值添加至结果列表 ansansans 中,然后将前驱节点的右子树指向当前节点 root,并将当前节点更新为 root.left。
  • 若前驱节点 pre 的右子树不为空,将前驱节点右子树指向空(即解除 pre 与 root 的指向关系),并将当前节点更新为 root.right。
  1. 循环以上步骤,直至二叉树节点为空,遍历结束。
/*** 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.ArrayList;
import java.util.List;class Solution {/*** 执行前序遍历,返回遍历结果列表。* @param root 二叉树的根节点* @return 前序遍历结果列表*/public List<Integer> preorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>(); // 用于存放前序遍历结果的列表while (root != null) {if (root.left == null) { // 如果当前节点的左子节点为空ans.add(root.val); // 将当前节点的值加入结果列表root = root.right; // 移动到当前节点的右子节点} else { // 如果当前节点的左子节点不为空TreeNode prev = root.left; // 找到当前节点的左子树的最右下角节点while (prev.right != null && prev.right != root) {prev = prev.right; // 寻找当前节点的左子树的最右下角节点}if (prev.right == null) { // 如果最右下角节点的右子节点为空ans.add(root.val); // 将当前节点的值加入结果列表prev.right = root; // 将最右下角节点的右子节点指向当前节点,建立线索root = root.left; // 移动到当前节点的左子节点} else { // 如果最右下角节点的右子节点不为空prev.right = null; // 恢复最右下角节点的右子节点为空,断开线索root = root.right; // 移动到当前节点的右子节点}}}return ans; // 返回前序遍历结果列表}
}

下接:【题解】—— LeetCode一周小结7


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/684401.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯电子类单片机提升三——NE555

目录 单片机资源数据包_2023 一、NE555和定时器工作模式 1.NE555的介绍 2.定时器的计数模式 二、NE555频率读取代码的实现 1.定时器0初始化 2.通过读取TH0和TL0来读取频率 3.通过中断读取频率 三、完整代码演示 通过读取TH0和TL0来读取频率 main.c 通过中断读取频…

qml中解决Page控件头部元素Margin不生效的问题

0、想要的效果 1、问题描述 经测试&#xff1a;Page的头部无法完美的进行左右边距设置&#xff0c;leftMargin可以&#xff0c;rightMargin不可以。。。。 Page {// ...header: Frame {id: headerheight: 70// 必须首先锚定位&#xff0c;然后设置边距才生效padding: 0anchor…

QlikSense: 通过 Insight Advisor 创建可视化

通过 Insight Advisor 创建可视化 探索你的数据&#xff0c;并通过 Insight Advisor 分析类型 和 Insight Advisor 搜索创建可视化。Insight Advisor 使用 Qlik cognitive engine 和应用程序的逻辑模型为您创建可视化。单击工作表中的 Insight Advisor 以使用 Insight Advisor…

构建智慧交通平台:架构设计与实现

随着城市交通的不断发展和智能化技术的迅速进步&#xff0c;智慧交通平台作为提升城市交通管理效率和水平的重要手段备受关注。本文将探讨如何设计和实现智慧交通平台的系统架构&#xff0c;以应对日益增长的城市交通需求&#xff0c;并提高交通管理的智能化水平。 ### 1. 智慧…

【电路笔记】-LR串联电路

LR串联电路 文章目录 LR串联电路1、概述2、示例1所有线圈、电感器、扼流圈和变压器都会在其周围产生磁场,由电感与电阻串联组成,形成 LR 串联电路。 1、概述 在本节有关电感器的第一个文章中,我们简要介绍了电感器的时间常数,指出流过电感器的电流不会瞬时变化,而是会以恒…

Covalent Network(CQT)与卡尔加里大学建立合作,共同推动区块链技术创新

Covalent Network&#xff08;CQT&#xff09;作为领先的 Web3 数据索引器和提供者&#xff0c;宣布已经与卡尔加里大学达成了具备开创性意义的合作&#xff0c;此次合作标志着推动区块链数据研究和可访问性的重要里程碑。卡尔加里大学是首个以验证者的身份加入 Covalent Netwo…

如何在30天内使用python制作一个卡牌游戏

如何在30天内使用python制作一个卡牌游戏 第1-5天&#xff1a;规划和设计第6-10天&#xff1a;搭建游戏框架第11-20天&#xff1a;核心游戏机制开发第21-25天&#xff1a;游戏界面和用户体验第26-30天&#xff1a;测试和发布附加建议游戏类型游戏规则设计界面设计技术选型第6-…

【Qt】环境安装与初识

目录 一、Qt背景介绍 二、搭建Qt开发环境 三、新建工程 四、Qt中的命名规范 五、Qt Creator中的快捷键 六、QWidget基础项目文件详解 6.1 .pro文件解析 6.2 widget.h文件解析 6.3 widget.cpp文件解析 6.4 widget.ui文件解析 6.5 main.cpp文件解析 七、对象树 八、…

垃圾分类|城市垃圾分类管理系统|基于Springboot的城市垃圾分类管理系统设计与实现(源码+数据库+文档)

城市垃圾分类管理系统目录 目录 基于Springboot的城市垃圾分类管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、垃圾列表 2、公告信息管理 3、公告类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 …

【王道数据结构】【chapter5树与二叉树】【P159t15】

设计一个算法将二叉树的叶结点从左到右的顺序连成一个单链表&#xff0c;表头指针为head。二叉树按二叉链表方式存储&#xff0c;链接时用叶结点的右指针来存放单链表指针。 #include <iostream> #include <stack> #include <queue> typedef struct treenode…

【AIGC】Stable Diffusion 的提示词入门

一、正向提示词和反向提示词 Stable Diffusion 中的提示词通常用于指导用户对生成的图像进行控制。这些提示词可以分为正向提示词&#xff08;Positive Prompts&#xff09;和反向提示词&#xff08;Negative Prompts&#xff09;两类&#xff0c;它们分别影响图像生成过程中的…

《春山》中的贝叶斯统计——白敬亭衣服合理概率及决策比重。

目录 1. 全身黑衣服合理概率2. 真的是导演组允许&#xff1f;3. 粉丝的证据是否站得住&#xff1f;4.总结 感谢up主链接: 【理工春山学】只谈事实 从统计角度深度剖析春山学&#xff0c;她使用贝叶斯统计合理分析了在舞台中白敬亭、双魏、导演组出错的概率。接下来我采用一个新…

吐血整理!操作系统【处理机调度】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 1 基本概念1.1 总览1.2 什么是调度1.2.1 调度1.2.2 处理机调度 1.3 调度的三个层次1.3.1 高级调度1.3.2 中级调度&#xff08;内存调度&#xff09;1.3.3 低级调度&#xf…

【leetcode994】腐烂的橘子(BFS)

文章目录 一、题目二、思路三、代码 一、题目 二、思路 首先将所有烂橘子入队&#xff0c;然后常规BFS遍历&#xff0c;注意while的截止条件除了队列为空&#xff0c;新鲜橘子数量大于0&#xff08;没新鲜橘子也没必要继续遍历&#xff0c;保证时间计算的正确性&#xff09;&a…

哈希切分

目录 一 二 三 2.单个子文件太大怎么办&#xff1f;&#xff08;分两种情况讨论&#xff09; 一 这样的题目典型就是KV模型的问题&#xff0c;即通过key IP找对应的value 出现次数&#xff0c;对于KV模型的问题首先想到的就是用map来统计次数&#xff0c;但是100G大小的文件…

Docker 第十四章 : Docker 三剑客之 Machine

第十四章 : Docker 三剑客之 Machine 本章知识点: Docker Machine 是 Docker 三剑客之一,它是一个工具,允许用户在本地或远程机器上创建 Docker 主机。它简化了 Docker 环境的设置,特别是在不同的操作系统和云平台上。通过 Docker Machine,用户可以轻松地在虚拟机或物理…

基于四叉树的图像分割算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ........................................................... Imgs(dx 1 : dx R1, dy 1 …

Spring中的事务实现、失效场景即AOP的相关概念理解

spring实现事务&#xff08;声明式事务&#xff09;的本质就是aop完成的,它会对方法的前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或回滚事务。aop就是面向切面编程&#xff0c;在spring中将那些与业务无关&#xff0c;但却对多个对象产生影响的…

【硬核】javascript轻松实现自动化批量取消某音用户关注功能

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起学习和进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&a…

高程 | 继承与派生(c++)

文章目录 &#x1f4da;继承的概念和语法&#x1f4da;派生类生成过程&#x1f4da;继承权限和继承方式&#x1f407;公有继承&#x1f407;私有继承&#x1f407;保护继承 &#x1f4da;类型转换规则&#x1f4da;派生类构造函数和析构函数&#x1f4da;继承中的静态成员特性&…