文章目录
力扣hot100-二叉树
概要
二叉树
(Binary Tree)是一种树形数据结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树在算法和计算机科学中具有广泛的应用,特别是在表达式解析、搜索算法、排序算法、优先级队列、堆和其他数据结构中。
二叉树的基本概念
- 节点 (Node):二叉树中的每个元素。
- 根节点 (Root Node):二叉树的顶端节点。
- 叶节点 (Leaf Node):没有子节点的节点。
- 子节点 (Child Node):某节点的直接后继。
- 父节点 (Parent Node):某节点的直接前驱。
- 高度 (Height):从根节点到叶节点的最长路径上的节点数。
- 深度 (Depth):从根节点到某节点的路径上的节点数。
- 层次 (Level):从根节点开始,第 1 层为根节点,其子节点为第 2 层,以此类推。
常见的二叉树类型
- 完全二叉树 (Complete Binary Tree):除了最后一层,其他每一层的节点都达到最大数,最后一层的节点全部集中在最左边。
- 满二叉树 (Full Binary Tree):每个节点要么是叶子节点,要么有两个子节点。
- 二叉搜索树 (Binary Search Tree, BST):对于树中的每个节点,其左子树的所有节点值小于该节点值,右子树的所有节点值大于该节点值。
- 平衡二叉树 (Balanced Binary Tree):左右子树的高度差不超过 1。
常用的二叉树遍历
- 前序遍历 (Pre-order Traversal):根节点 -> 左子树 -> 右子树。
- 中序遍历 (In-order Traversal):左子树 -> 根节点 -> 右子树。
- 后序遍历 (Post-order Traversal):左子树 -> 右子树 -> 根节点。
- 层次遍历 (Level-order Traversal):按层次从上到下、从左到右遍历。
二叉树的常用技巧
-
递归:
- 许多二叉树问题可以通过递归来解决,因为二叉树的结构天然适合递归思想。
- 例如,求二叉树的高度可以通过递归求左右子树的高度,然后取最大值加一。
-
迭代:
- 使用栈或队列来模拟递归过程,实现非递归的遍历方法。
- 例如,中序遍历可以通过显式栈来实现。
-
回溯:
- 回溯法常用于在树中寻找路径或解决路径问题。
- 例如,在路径和为某一值的情况下,回溯法可以在遍历的过程中动态构建路径并回退。
-
动态规划:
- 在处理一些优化问题时,可以在二叉树上应用动态规划,通过存储子问题的结果来减少重复计算。
- 例如,在二叉树上查找最长路径等问题中。
-
分治法:
- 将问题分解为若干子问题分别解决,然后合并子问题的结果。
- 例如,合并两棵二叉树、构造平衡二叉树等。
题目:二叉树的中序遍历
原题链接:二叉树的中序遍历
方法1–递归遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
inorder(root, list);
return list;
}
public void inorder(TreeNode root, List<Integer> list) {
if (root == null) return;
inorder(root.left, list);
list.add(root.val);
inorder(root.right, list);
}
}
方法2–使用栈
栈(Stack):利用栈来模拟递归的行为。栈在遍历左子树时保存节点,确保能够回到父节点,并遍历右子树。
对于二叉树的中序遍历,访问节点的顺序是:左子树 -> 根节点 -> 右子树
。代码的关键在于使用了一个栈
来模拟递归调用的过程。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
LinkedList<TreeNode> stack = new LinkedList<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
list.add(root.val);
root = root.right;
}
return list;
}
}
题目: 二叉树的最大深度
原题链接: 二叉树的最大深度
题解
用递归!
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
题目:翻转二叉树
原题链接:翻转二叉树
方法1–递归
从下到上
public TreeNode invertTree(TreeNode root) {
// 如果当前节点为空,直接返回空
if (root == null) {
return null;
}
// 递归翻转左右子树
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
// 交换左右子树
root.left = right;
root.right = left;
// 返回当前节点
return root;
}
从上到下
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
方法2–非递归
不用递归,用迭代的方法,通常使用栈或队列来模拟递归的调用栈
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode current = queue.poll();
// 交换左右子节点
TreeNode temp = current.left;
current.left = current.right;
current.right = temp;
// 将子节点加入队列以处理它们的子节点
if (current.left != null) queue.offer(current.left);
if (current.right != null) queue.offer(current.right);
}
return root;
}
题目: 对称二叉树
原题链接: 对称二叉树
题解
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
return isMirror(root.left, root.right);
}
private boolean isMirror(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null) {
return false;//不对称
}
return (left.val == right.val)
&& isMirror(left.left, right.right)
&& isMirror(left.right, right.left);
}
- 左子树的左子节点 left.left 与右子树的右子节点 right.right 要镜像对称。
- 左子树的右子节点 left.right 与右子树的左子节点 right.left 要镜像对称。
left.val == right.val:2 == 2,满足。
isMirror(left.left, right.right):即 3 与 3,满足。
isMirror(left.right, right.left):即 4 与 4,满足。
1
/ \
2 2
/ \ / \
3 4 4 3
题目:二叉树的直径
原题链接:二叉树的直径
题解
方法:分解问题
最大深度的计算:
- 二叉树的直径可以通过计算每个节点的左右子树的最大深度来获得。
- 对于每个节点,计算其左子树和右子树的深度。
- 节点的直径等于左子树深度加右子树深度。
public class T543 {
private int res = 0;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return res;
}
private int depth(TreeNode node) {
if (node == null) {
return 0;
}
int left = depth(node.left);
int right = depth(node.right);
// 计算经过当前节点的直径
res = Math.max(res, left + right);
// 返回当前节点的深度
return Math.max(left, right) + 1;
}
}
···
其实这道题和`二叉树的最大深度`有很大关系的,我们看看`二叉树的最大深度`代码
```java
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;
}
在上面源码中的//
那里添加动态更新最大路径就是本道题的答案。
二叉树的直径
这个题,针对一个节点,我需要把左子树,右子树深度都计算出来,然后动态更新直径,所以在后序位置
更新直径。
题目:二叉树的层序遍历
原题链接:二叉树的层序遍历
题解
方法:借助队列
这个数据结构
public static List<List<Integer>> levelOrder(TreeNode root) {
ArrayList<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int size = queue.size();
ArrayList<Integer> temp = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
temp.add(node.val);
}
res.add(temp);
}
return res;
}
在while循环的每次迭代开始时,queue存储的是当前层的所有节点。
题目:将有序数组转换为二又搜索树
原题链接:将有序数组转换为二又搜索树
题解
将有序数组转换为二叉搜索树的关键在于利用数组的中间元素作为树的根节点
,然后递归地对左右子数组执行相同的操作,构建左右子树
public static TreeNode sortedArrayToBST(int[] nums) {
return convertToBST(nums, 0, nums.length - 1);
}
private static TreeNode convertToBST(int[] nums, int left, int right) {
if (left > right) return null;
int mid = (left + right) / 2;
TreeNode node = new TreeNode(nums[mid]);
node.left = convertToBST(nums, left, mid - 1);
node.right = convertToBST(nums, mid + 1, right);
return node;
}
题目:二叉搜索树中第 K 小的元素
原题链接:二叉搜索树中第 K 小的元素
题解
二叉搜索树
的中序遍历
结果是一个升序排列的序列,因此可以通过中序遍历找到第 k 小的元素。
private int res = 0;
private int count = 0;
private int kk = 0;
public int kthSmallest(TreeNode root, int k) {
kk = k;
inorderTraversal(root);
return res;
}
// 中序遍历方法
private void inorderTraversal(TreeNode root) {
if (root == null) return;
// 1. 递归遍历左子树
inorderTraversal(root.left);
// 2. 处理当前节点
count += 1;
// 如果当前节点是第 k 个节点,保存结果并返回
if (count == kk) {
res = root.val;
return;
}
// 3. 递归遍历右子树
inorderTraversal(root.right);
}
力扣hot100-矩阵
题目:矩阵置零
原题链接:矩阵置零
题解
方法:通过先标记需要置为 0
的位置,再进行修改,避免了在遍历矩阵时直接更改元素,避免了覆盖掉尚未检查的元素。(先标记再置零
)
row[i] || col[j]
的意思就是:如果 第 i
行 或 第 j
列 中有任何一个被标记为需要置为 0,那么当前元素 matrix[i][j] 就应该被置为 0。
public void setZeroes(int[][] matrix) {
boolean[] row = new boolean[matrix.length];
boolean[] col = new boolean[matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (matrix[i][j] == 0) {
row[i] = col[j] = true;
}
}
}
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (row[i] || col[j]) {
matrix[i][j] = 0;
}
}
}
}
下面这个逻辑更清晰:
public void setZeroes(int[][] matrix) {
boolean[][] visited = new boolean[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == 0) {
// 行变为0
rowToZero(matrix, i, visited);
// 列变为0
cloToZero(matrix, j, visited);
}
}
}
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (visited[i][j]) {
matrix[i][j] = 0;
}
}
}
}
private void rowToZero(int[][] matrix, int i, boolean[][] visited) {
for (int j = 0; j < matrix[i].length; j++) {
visited[i][j] = true;
}
}
private void cloToZero(int[][] matrix, int j, boolean[][] visited) {
for (int i = 0; i < matrix.length; i++) {
visited[i][j] = true;
}
}
题目:螺旋矩阵
原题链接:螺旋矩阵
题解
思路解析:
-
定义边界:我们需要通过 4 个变量来维护矩阵的边界:
top
:表示当前螺旋矩阵的上边界,初始化为 0。bottom
:表示当前螺旋矩阵的下边界,初始化为矩阵的最后一行索引。left
:表示当前螺旋矩阵的左边界,初始化为 0。right
:表示当前螺旋矩阵的右边界,初始化为矩阵的最后一列索引。
-
顺时针遍历:按照螺旋顺序进行遍历:
- 从左到右:遍历 top 边界上的一行元素,然后
top++
(即上边界下移)。 - 从上到下:遍历 right 边界上的一列元素,然后
right--
(即右边界左移)。 - 从右到左:遍历 bottom 边界上的一行元素,然后
bottom--
(即下边界上移)。 - 从下到上:遍历 left 边界上的一列元素,然后
left++
(即左边界右移)。
- 从左到右:遍历 top 边界上的一行元素,然后
-
结束条件:遍历结束的条件是
top > bottom
或left > right
,即上下边界或左右边界错位。
每遍历一行或者一列,记得收缩相应的空间。(已经遍历的就不在遍历了)
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>();
int top = 0;
int right = matrix[0].length - 1;
int bottom = matrix.length - 1;
int left = 0;
while (left <= right && top <= bottom) {
// 左到右
// 边界是left right,i在范围里面移动。 保持行不变
for (int i = left; i <= right; i++) {
res.add(matrix[top][i]);
}
top++;
// 上到下
for (int i = top; i <= bottom; i++) {
res.add(matrix[i][right]);
}
right--;
// 右到左
if (top <= bottom) {
for (int i = right; i >= left; i--) {
res.add(matrix[bottom][i]);
}
bottom--;
}
// 下到上
if (left <= right) {
for (int i = bottom; i >= top; i--) {
res.add(matrix[i][left]);
}
left++;
}
}
return res;
}
题目:旋转图像
原题链接:旋转图像
题解
矩阵转置
:将矩阵的行列互换得到的新矩阵称为转置矩阵。
1 2 3 1 4 7 7 4 1
4 5 6 ==转置==》 2 5 8 ==行反转==> 8 5 2
7 8 9 3 6 9 9 6 3
public void rotate(int[][] matrix) {
int n = matrix.length;
// 1. 对角线翻转 矩阵转置
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 2. 每一行反转
for (int i = 0; i < n; i++) {
int left = 0, right = n - 1;
while (left < right) {
int temp = matrix[i][left];
matrix[i][left] = matrix[i][right];
matrix[i][right] = temp;
left++;
right--;
}
}
}
力扣hot100-技巧
题目:只出现一次的数字
原题链接:只出现一次的数字
题解
在Java中,异或运算(XOR)是一种位运算符,用符号 ^ 表示。它用于对两个二进制数的每一位进行比较,规则如下:
-
如果两个对应位的值相同,结果为 0。
-
如果两个对应位的值不同,结果为 1。
特殊:任何数和 0 做异或运算,结果仍然是原来的数。(最低位假设是1,与0异或结果还是1。最低位假设是0,与0异或结果还是0)
public int singleNumber(int[] nums) {
int res = 0;
for (int num : nums) {
res = res ^ num;
}
return res;
}
题目:多数元素
原题链接:多数元素
题解
利用 HashMap,因为看返回值为 int ,说明只有一个数符合答案。使用 HashMap,可以通过提前终止来减少不必要的遍历。
public int majorityElement(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
int n = nums.length / 2;
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
if (map.get(num) > n) {
return num;
}
}
return -1;
}
题目:多数元素 II
原题链接:多数元素 II
题解
方法也是利用 HashMap ,和题目:多数元素
类似,只不过可能存在多个结果,需要用一个数据把结果收集起来。
public List<Integer> majorityElement(int[] nums) {
List<Integer> res = new ArrayList<>();
// 键是num 值num出现次数
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
int n = nums.length / 3;
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getValue() > n) {
res.add(entry.getKey());
}
}
return res;
}
❤觉得有用的可以留个关注~~❤