当前位置: 首页 > news >正文

每日算法-250413

记录一下今天解决的几道 LeetCode 算法题。


2476. 二叉搜索树最近节点查询

题目

LeetCode 2476 Problem Description

思路

主要思路是将二叉搜索树(BST)通过中序遍历转换为一个升序排列的数组,然后对每个查询值,利用二分查找在这个有序数组中找到最接近的小于等于和大于等于它的节点值。

解题过程

  1. 中序遍历:首先,定义一个 traversal 方法,使用中序遍历(左子树 -> 根节点 -> 右子树)将 BST 的所有节点值存入一个 List<Integer>。因为中序遍历 BST 会得到一个升序序列。
  2. 转换为数组:将得到的 List<Integer> 转换为一个 int[] 数组 nums。选择先用 List 再转 Array 是因为在遍历开始时无法确定节点的总数,List 可以动态扩容。
  3. 处理查询:遍历 queries 列表中的每一个查询值 x
  4. 二分查找:为每个 x 调用一个 check 方法(二分查找)。这个 check 方法旨在找到 nums 数组中第一个 大于等于 x 的元素的下标 index
  5. 确定最小值 (min)
    • 小于等于 x 的最大值 (min_i) 对应的元素应该在 index 的左侧。
    • 如果 nums[index] 正好等于 x,那么 min_i 就是 nums[index],其下标为 index
    • 如果 nums[index] 大于 x(或者 index 等于数组长度 n,表示所有元素都小于 x),那么小于等于 x 的最大值应该在 index - 1 的位置。
    • 所以,我们先判断 index == n 或者 nums[index] != x 的情况,如果是,说明目标 x 不在数组中或者 nums[index] 是大于 x 的最小值,此时我们需要找 index - 1 作为 min_i 的候选下标。
    • 处理边界:如果最终确定的 min_i 的下标小于 0(比如 x 比数组中所有元素都小,index 为 0,然后 index-- 变为 -1),则 min_i 不存在,设为 -1。否则 min_inums[index] (经过调整后的 index)。
  6. 确定最大值 (max)
    • 大于等于 x 的最小值 (max_i) 对应的元素下标就是二分查找直接返回的 index
    • 处理边界:如果 index 等于数组长度 n(表示 x 大于数组中所有元素),则 max_i 不存在,设为 -1。否则 max_inums[index]
  7. 存储结果:将找到的 min_imax_i 存入一个临时的 List<Integer>,然后添加到最终的结果列表 ret 中。
  8. 返回结果:返回 ret

复杂度

  • 时间复杂度: O(N + Q log N)
    • O(N) 用于中序遍历构建有序数组,其中 N 是树中的节点数。
    • O(Q log N) 用于处理 Q 个查询,每个查询进行一次二分查找。
    • 如果简化考虑 Q 和 N 可能的大小关系,有时也写作 O(N log N) 或 O(Q log N),但 O(N + Q log N) 更精确。
  • 空间复杂度: O(N)
    • 需要 O(N) 的空间存储中序遍历的结果数组。
    • 递归栈空间在最坏情况下(链状树)也可能达到 O(N),平均情况下是 O(log N)。

Code

/**
 * 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 List<List<Integer>> closestNodes(TreeNode root, List<Integer> queries) {
        List<List<Integer>> ret = new ArrayList<>();

        List<Integer> arr = new ArrayList<>();
        traversal(root, arr);
        int n = arr.size();
        int[] nums = new int[n];
        for (int i = 0; i < n; i++) {
            nums[i] = arr.get(i);
        }

        int min = 0, max = 0;
        for (Integer x : queries) {
            List<Integer> tmp = new ArrayList<>();
            int index = check(nums, x);
            max = index == n ? -1 : nums[index];
            if (index == n || max != x) {
                index--;
            }
            min = index < 0 ? -1 : nums[index];
            tmp.add(min);
            tmp.add(max);
            ret.add(tmp);
        }

        return ret;
    }

    private void traversal(TreeNode root, List<Integer> arr) {
        if (root == null) {
            return;
        }
        traversal(root.left, arr);
        arr.add(root.val);
        traversal(root.right, arr);
    }

    private int check(int[] arr, int t) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] < t) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
}

74. 搜索二维矩阵

题目

LeetCode 74 Problem Description

思路

利用题目给出的两个特性(行内递增、下一行首元素大于上一行尾元素),可以将整个二维矩阵视为一个一维有序数组,从而使用二分查找。或者更直观地,先进行一次二分查找确定目标值 target 可能在哪一行,再对该行进行一次二分查找。

解题过程 (两次二分查找)

  1. 确定行
    • 对矩阵的行进行二分查找(基于每行的第一个元素或最后一个元素)。
    • 使用 leftright 指针表示当前搜索的行范围,初始为 0matrix.length - 1
    • 计算中间行 mid
    • 比较 targetmatrix[mid][0](行首元素)和 matrix[mid][m-1](行尾元素,m 为列数)。
    • 如果 target < matrix[mid][0],说明 target 如果存在,必定在 mid 行之前,令 right = mid - 1
    • 如果 target > matrix[mid][m-1],说明 target 如果存在,必定在 mid 行之后,令 left = mid + 1
    • 如果 matrix[mid][0] <= target <= matrix[mid][m-1],说明 target 可能在 mid 行,跳出循环或直接对该行进行下一步查找。
  2. 确定列
    • 如果上一步确定了目标可能所在的行 targetRow,则对 matrix[targetRow] 这个一维数组进行标准的二分查找。
    • 定义一个 check 方法(或直接内联实现),在 matrix[targetRow] 中查找 target
    • 使用 leftright 指针表示列范围,初始为 0m - 1
    • 计算 mid 列。
    • 比较 arr[mid]t (target)。
    • 如果 arr[mid] < t,令 left = mid + 1
    • 如果 arr[mid] >= t,令 right = mid - 1。(这里使用 >= 是为了找到第一个可能等于 t 的位置,或者 t 的插入位置)
    • 循环结束后,left 指向第一个大于等于 t 的位置。检查 left 是否在数组范围内且 arr[left] 是否等于 t
  3. 返回结果:如果在行查找中没有找到合适的行,或者在列查找中没有找到 target,返回 false。否则返回 true

复杂度

  • 时间复杂度: O(log M + log N)
    • 第一次二分查找确定行,复杂度为 O(log M),其中 M 是行数。
    • 第二次二分查找在确定行内查找列,复杂度为 O(log N),其中 N 是列数。
  • 空间复杂度: O(1)
    • 只使用了常数级别的额外空间。

Code

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int left = 0, right = matrix.length - 1, m = matrix[0].length;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (matrix[mid][0] > target) {
                right = mid - 1;
            } else if (matrix[mid][m - 1] < target) {
                left = mid + 1;
            } else {
                return check(matrix[mid], target);
            }
        }
        return false;
    }

    private boolean check(int[] arr, int t) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] < t) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return arr[left] == t;
    }
}

240. 搜索二维矩阵 II

题目

LeetCode 240 Problem Description

思路

这道题的矩阵特性与上一题不同:每行递增,每列递增,但下一行的首元素不一定大于上一行的尾元素。因此不能直接视为一维数组进行二分。

可以采用一种称为 Z 型查找 的方法。利用矩阵的行列单调性,从矩阵的一个角(例如右上角左下角)开始搜索。

解题过程 (以右上角为例)

  1. 起始位置:将指针 (x, y) 初始化为矩阵的右上角,即 x = 0 (第一行),y = m - 1 (最后一列),其中 n 是行数,m 是列数。
  2. 比较与移动
    • 获取当前位置的元素 num = matrix[x][y]
    • 比较 targetnum
      • 如果 target == num,说明找到了目标值,返回 true
      • 如果 target < num,说明 target 不可能在当前列 y 的下方(因为列是递增的),所以需要向左移动,减小列索引 y--
      • 如果 target > num,说明 target 不可能在当前行 x 的左方(因为行是递增的),所以需要向下移动,增加行索引 x++
  3. 终止条件
    • 重复步骤 2,直到找到 target 或指针移出矩阵边界。
    • 如果指针移出边界仍未找到 target,则说明矩阵中不存在该值,返回 false

复杂度

  • 时间复杂度: O(M + N)
    • 指针 x 最多向下移动 M 次,指针 y 最多向左移动 N 次。每次移动都是 O(1)。总的移动次数不超过 M + N。
  • 空间复杂度: O(1)
    • 只使用了常数级别的额外空间存储指针 xy

Code

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int n = matrix.length, m = matrix[0].length;
        int x = 0, y = m - 1;
        while ((x >= 0 && x <= n - 1) && (y >= 0 && y <= m - 1)) {
            int num = matrix[x][y];
            if (target < num) {
                y--;
            } else if (target > num) {
                x++;
            } else {
                return true;
            }
        }
        return false;
    }
}

相关文章:

  • deskflow使用教程:一个可以让两台电脑鼠标键盘截图剪贴板共同使用的开源项目
  • 【开发工具】科研开发中的主流AI工具整理及如何使用GPT润色英文论文
  • 【苹果cms 1】本地影视资源站搭建
  • [文献阅读] chinese-roberta Pre-Training With Whole Word Masking for Chinese BERT
  • 真实企业级K8S故障案例:ETCD集群断电恢复与数据保障实践
  • QML ListView 与 C++ 模型交互
  • 微信小程序实战案例 - 餐馆点餐系统 阶段 0 - 环境就绪
  • 玩转Docker | 使用Docker部署MicroBin粘贴板
  • Java新手村第二站:泛型、集合与IO流初探
  • k8s的配置文件总结
  • Go学习路线指南
  • springboot框架集成websocket依赖实现物联网设备、前端网页实时通信!
  • MySQL——MVCC(多版本并发控制)
  • 免费在线文档工具,在线PDF添加空白页,免费在任意位置插入空白页,多样化的文件处理
  • 【AI论文】MM-IFEngine:迈向多模态指令遵循
  • Magnet Pro Macbook窗口分屏管理软件【提高效率工具】
  • 从零训练LLM-1.训练BPE
  • 猫咪如厕检测与分类识别系统系列【五】信息存储数据库改进+添加猫咪页面制作+猫咪躯体匹配算法架构更新
  • SQL 解析 with as
  • C++ 入门六:多态 —— 同一接口的多种实现之道
  • 阿里CEO:将以饱和式投入打法,聚焦几大核心战役
  • 近4小时会谈、3项联合声明、20多份双边合作文本,中俄元首今年首次面对面会晤成果颇丰
  • 比尔·盖茨:未来20年通过盖茨基金会捐出几乎全部财富,2045年底基金会停止运营
  • 司法部:持续规范行政执法行为,加快制定行政执法监督条例
  • 建筑瞭望|融入自然的新泳池,治愈了中央公园的历史旧伤
  • 云南省司法厅党委书记、厅长茶忠旺主动投案,正接受审查调查