力扣hot100--2

力扣hot100-二叉树

概要

二叉树(Binary Tree)是一种树形数据结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树在算法和计算机科学中具有广泛的应用,特别是在表达式解析、搜索算法、排序算法、优先级队列、堆和其他数据结构中。

二叉树的基本概念

  1. 节点 (Node):二叉树中的每个元素。
  2. 根节点 (Root Node):二叉树的顶端节点。
  3. 叶节点 (Leaf Node):没有子节点的节点。
  4. 子节点 (Child Node):某节点的直接后继。
  5. 父节点 (Parent Node):某节点的直接前驱。
  6. 高度 (Height):从根节点到叶节点的最长路径上的节点数。
  7. 深度 (Depth):从根节点到某节点的路径上的节点数。
  8. 层次 (Level):从根节点开始,第 1 层为根节点,其子节点为第 2 层,以此类推。

常见的二叉树类型

  1. 完全二叉树 (Complete Binary Tree):除了最后一层,其他每一层的节点都达到最大数,最后一层的节点全部集中在最左边。
  2. 满二叉树 (Full Binary Tree):每个节点要么是叶子节点,要么有两个子节点。
  3. 二叉搜索树 (Binary Search Tree, BST):对于树中的每个节点,其左子树的所有节点值小于该节点值,右子树的所有节点值大于该节点值。
  4. 平衡二叉树 (Balanced Binary Tree):左右子树的高度差不超过 1。

常用的二叉树遍历

  1. 前序遍历 (Pre-order Traversal):根节点 -> 左子树 -> 右子树。
  2. 中序遍历 (In-order Traversal):左子树 -> 根节点 -> 右子树。
  3. 后序遍历 (Post-order Traversal):左子树 -> 右子树 -> 根节点。
  4. 层次遍历 (Level-order Traversal):按层次从上到下、从左到右遍历。

二叉树的常用技巧

  1. 递归

    • 许多二叉树问题可以通过递归来解决,因为二叉树的结构天然适合递归思想。
    • 例如,求二叉树的高度可以通过递归求左右子树的高度,然后取最大值加一。
  2. 迭代

    • 使用栈或队列来模拟递归过程,实现非递归的遍历方法。
    • 例如,中序遍历可以通过显式栈来实现。
  3. 回溯

    • 回溯法常用于在树中寻找路径或解决路径问题。
    • 例如,在路径和为某一值的情况下,回溯法可以在遍历的过程中动态构建路径并回退。
  4. 动态规划

    • 在处理一些优化问题时,可以在二叉树上应用动态规划,通过存储子问题的结果来减少重复计算。
    • 例如,在二叉树上查找最长路径等问题中。
  5. 分治法

    • 将问题分解为若干子问题分别解决,然后合并子问题的结果。
    • 例如,合并两棵二叉树、构造平衡二叉树等。

题目:二叉树的中序遍历

原题链接:二叉树的中序遍历
在这里插入图片描述

方法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;
        }
    }

题目:螺旋矩阵

原题链接:螺旋矩阵
在这里插入图片描述

题解

思路解析:

  1. 定义边界:我们需要通过 4 个变量来维护矩阵的边界:

    • top:表示当前螺旋矩阵的上边界,初始化为 0。
    • bottom:表示当前螺旋矩阵的下边界,初始化为矩阵的最后一行索引。
    • left:表示当前螺旋矩阵的左边界,初始化为 0。
    • right:表示当前螺旋矩阵的右边界,初始化为矩阵的最后一列索引。
  2. 顺时针遍历:按照螺旋顺序进行遍历:

    • 从左到右:遍历 top 边界上的一行元素,然后 top++(即上边界下移)。
    • 从上到下:遍历 right 边界上的一列元素,然后 right--(即右边界左移)。
    • 从右到左:遍历 bottom 边界上的一行元素,然后 bottom--(即下边界上移)。
    • 从下到上:遍历 left 边界上的一列元素,然后 left++(即左边界右移)。
  3. 结束条件:遍历结束的条件是 top > bottomleft > 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;
    }

❤觉得有用的可以留个关注~~❤

### 力扣热门100列表 力扣LeetCode)上的热门目通常是指那些被广泛讨论、高频面试或者具有较高难度的目。这些目涵盖了数据结构和算法的核心知识点,适合用来提升编程能力和解决实际问的能力。 以下是基于社区反馈整理的部分 **LeetCode Hot 100 Problems List**: #### 数组与字符串 1. Two Sum (两数之和)[^1] 2. Longest Substring Without Repeating Characters (无重复字符的最长子串)[^2] 3. Median of Two Sorted Arrays (两个有序数组的中位数)[^1] 4. Container With Most Water (盛最多水的容器)[^2] #### 链表 5. Reverse Linked List (反转链表) 6. Merge Two Sorted Lists (合并两个有序链表) 7. Remove Nth Node From End of List (删除倒数第N个节点) 8. Linked List Cycle II (环形链表II) #### 堆栈与队列 9. Valid Parentheses (有效的括号) 10. Min Stack (最小栈) 11. Sliding Window Maximum (滑动窗口最大值)[^2] #### 树与二叉树 12. Binary Tree Inorder Traversal (二叉树的中序遍历) 13. Validate Binary Search Tree (验证二叉搜索树) 14. Same Tree (相同的树) 15. Serialize and Deserialize Binary Tree (序列化与反序列化二叉树) #### 图论 16. Number of Islands (岛屿数量) 17. Course Schedule (课程表) 18. Clone Graph (克隆图) #### 排序与搜索 19. Find First and Last Position of Element in Sorted Array (在排序数组中查找元素的第一个和最后一个位置) 20. Search a 2D Matrix (二维矩阵搜索) 21. K Closest Points to Origin (最接近原点的K个点) #### 动态规划 22. Climbing Stairs (爬楼梯) 23. House Robber (打家劫舍)[^1] 24. Coin Change (零钱兑换) 25. Unique Paths (不同路径) #### 贪心算法 26. Jump Game (跳跃游戏)[^1] 27. Non-overlapping Intervals (无重叠区间) 28. Best Time to Buy and Sell Stock (买卖股票的最佳时机)[^1] #### 字符串匹配与处理 29. Implement strStr() (实现strStr()) 30. Longest Consecutive Sequence (最长连续序列) 31. Group Anagrams (分组异位词) --- ### 示例代码片段 以下是一个关于动态规划的经典例子——`Climbing Stairs` 的 Python 实现: ```python class Solution: def climbStairs(self, n: int) -> int: if n == 1 or n == 2: return n dp = [0] * (n + 1) dp[1], dp[2] = 1, 2 for i in range(3, n + 1): dp[i] = dp[i - 1] + dp[i - 2] return dp[n] ``` 上述代码通过动态规划的方式解决了 `Climbing Stairs` 问,时间复杂度为 \(O(n)\),空间复杂度同样为 \(O(n)\)[^1]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值