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

每日算法-250405

34. 在排序数组中查找元素的第一个和最后一个位置

题目

LeetCode Problem 34

思路

本题的核心思路是二分查找

解题过程

  1. 问题分析:在一个升序排列的数组中查找一个目标值 target 的起始和结束位置。这是一个典型的二分查找应用场景。
  2. 核心转换:题目要求找到 target 的第一个位置和最后一个位置。这可以转换为两个子问题:
    • 找到第一个 大于等于 target 的元素的下标(记为 left_bound)。
    • 找到第一个 大于 target 的元素的下标,然后将这个下标减 1,就得到 target 的最后一个位置(记为 right_bound)。这等价于找到第一个 大于等于 target + 1 的元素的下标,然后减 1。
  3. 实现lower_bound函数:我们可以实现一个通用的二分查找函数 lower_bound(nums, k),用于查找数组 nums 中第一个大于等于 k 的元素的下标。
    • 初始化指针 left = 0, right = nums.length - 1
    • 循环条件 while (left <= right)
    • 计算中间位置 mid = left + (right - left) / 2
    • 如果 nums[mid] < k,说明目标值 k(或第一个大于等于 k 的元素)一定在 mid 的右侧,更新 left = mid + 1
    • 如果 nums[mid] >= k,说明 mid 可能是第一个大于等于 k 的元素,或者目标在 mid 的左侧。因此,我们需要继续在左半部分(包括 mid 本身)查找,更新 right = mid - 1
    • 循环结束后,left 指针指向的位置就是第一个大于等于 k 的元素的下标。如果数组中所有元素都小于 kleft 将会等于 nums.length
  4. 求解
    • 调用 lower_bound(nums, target) 得到 left_index
    • 检查 left_index:如果 left_index 等于数组长度 nums.length 或者 nums[left_index] 不等于 target,说明数组中不存在 target,直接返回 [-1, -1]
    • 调用 lower_bound(nums, target + 1) 得到 right_index_plus_one
    • target 的最后一个位置是 right_index_plus_one - 1
    • 返回 [left_index, right_index_plus_one - 1]

复杂度

  • 时间复杂度: O(log n) - 两次二分查找。
  • 空间复杂度: O(1) - 只使用了常数级别的额外空间。

Code

class Solution {
    public int[] searchRange(int[] nums, int target) {
        // 查找第一个大于等于 target 的位置
        int leftIdx = lower_bound(nums, target);

        // 检查 leftIdx 是否越界 或 nums[leftIdx] != target
        // 如果是,说明 target 不存在
        if (leftIdx == nums.length || nums[leftIdx] != target) {
            return new int[] {-1, -1};
        }

        // 查找第一个大于等于 target + 1 的位置
        // 这个位置的前一个位置就是 target 的最后一个位置
        int rightIdx = lower_bound(nums, target + 1) - 1;

        return new int[] {leftIdx, rightIdx};
    }

    // 查找数组中第一个大于等于 k 的元素的下标
    private int lower_bound(int[] nums, int k) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < k) {
                // [mid+1, right]
                left = mid + 1;
            } else { // nums[mid] >= k
                // [left, mid-1]
                right = mid - 1;
            }
        }
        // 循环结束后,left 就是第一个 >= k 的元素的下标
        return left;
    }
}

35. 搜索插入位置

题目

LeetCode Problem 35

思路

同样使用二分查找

解题过程

  1. 问题分析:在一个 无重复元素 的有序数组中,查找目标值 target。如果找到,返回其下标;如果找不到,返回它应该插入的位置的下标,以保持数组有序。
  2. 核心思想:这个问题本质上就是查找数组中第一个 大于等于 target 的元素的下标。
    • 如果数组中存在 target,那么第一个大于等于 target 的元素就是 target 本身,其下标即为所求。
    • 如果数组中不存在 target,那么第一个大于等于 target 的元素的位置,就是 target 应该插入的位置。
  3. 实现:可以直接复用上一题中的 lower_bound 查找逻辑。
    • 初始化 left = 0, right = nums.length - 1
    • 循环 while (left <= right)
    • 计算 mid
    • 如果 nums[mid] < target,目标在右侧,left = mid + 1
    • 如果 nums[mid] >= target,目标在 mid 或其左侧,right = mid - 1
    • 循环结束后,left 就是第一个大于等于 target 的元素的下标。
  4. 边界情况处理
    • 如果数组中所有元素都小于 target,循环过程中 left 会一直右移,最终 left 变为 nums.length,这正好是 target 应该插入的位置。
    • 如果数组中所有元素都大于 target,循环过程中 right 会一直左移,最终 left 保持为 0,这也是 target 应该插入的位置。
    • 因此,该二分查找的返回值 left 直接就是答案。

复杂度

  • 时间复杂度: O(log n) - 一次二分查找。
  • 空间复杂度: O(1) - 只使用了常数级别的额外空间。

Code

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1; // 继续在右区间 [mid+1, right] 查找
            } else { // nums[mid] >= target
                right = mid - 1; // 继续在左区间 [left, mid-1] 查找
            }
        }
        // 循环结束后,left 指向第一个大于等于 target 的元素下标
        // 或者,如果 target 大于所有元素,left 指向 nums.length
        return left;
    }
}

92. 反转链表 II (复习)

题目

LeetCode Problem 92

复习心得

今天是第二次做这道题。核心思路仍然是找到 left 位置的前一个节点 prev,然后使用头插法,在 leftright 区间内,依次将 cur 后面的节点 curNext 移动到 prev 的后面(也就是反转区间的头部)。

while 循环里,关键在于理解节点连接的变化:

  1. 保存 cur 的下一个节点:ListNode curNext = cur.next;
  2. cur 跳过 curNext,指向 curNext 的下一个节点:cur.next = curNext.next;
  3. curNext 插入到反转区间的头部,也就是 prev 的后面:curNext.next = prev.next;
  4. prev 指向新的头部 curNextprev.next = curNext;

今天在写的时候,容易混淆的是步骤 3 和 4。一开始容易错误地写成 curNext.next = cur,这是不对的,因为 cur 是在移动的,只有在循环开始前 prev.next 才指向反转区间的第一个节点。正确的做法是始终将 curNext 插入到 prev.next 所指向的位置。

详细题解可以参考之前的笔记:每日算法-250328

Code

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 使用虚拟头节点简化边界处理(left=1 的情况)
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        // 1. 找到 left 位置的前一个节点 prev
        ListNode prev = dummy;
        for (int i = 1; i < left; i++) {
            prev = prev.next;
        }

        // prev.next 就是反转区间的第一个节点,记为 cur
        ListNode cur = prev.next;

        // 2. 执行头插法反转 left 到 right 区间的节点
        // 总共需要执行 right - left 次头插操作
        for (int i = left; i < right; i++) {
            // 获取 cur 的下一个节点,它将是下一个要移动到头部的节点
            ListNode nodeToMove = cur.next;
            // cur 跳过 nodeToMove
            cur.next = nodeToMove.next;
            // 将 nodeToMove 插入到 prev 的后面
            nodeToMove.next = prev.next;
            prev.next = nodeToMove;
        }

        return dummy.next;
    }
}
http://www.dtcms.com/a/113623.html

相关文章:

  • 4. 面向对象程序设计
  • 分布式事务解决方案全解析:从经典模式到现代实践
  • 每天五分钟深度学习框架pytorch:搭建LSTM完成手写字体识别任务?
  • 深入探索 Linux Top 命令:15 个实用示例
  • python中的sort使用
  • 在 macOS 上安装和配置 Aria2 的详细步骤
  • 【数学建模】(时间序列模型)ARIMA时间序列模型
  • tomcat的web三大组件Sciidea搭建web/maven的tomcat项目
  • grep命令: 过滤
  • 基于STM32与应变片的协作机械臂力反馈控制系统设计与实现----2.2 机械臂控制系统硬件架构设计
  • 自托管本地图像压缩器Mazanoke
  • (三)链式工作流构建——打造智能对话的强大引擎
  • 5天速成ai agent智能体camel-ai之第1天:camel-ai安装和智能体交流消息讲解(附源码,零基础可学习运行)
  • linux专题3-----linux上链接远程mysql
  • 深入理解Python元组:从基础到高级应用
  • xss攻击
  • NDK开发:开发环境
  • 2025-04-05 吴恩达机器学习4——逻辑回归(1):基础入门
  • 华为高斯(GaussDB)数据库中 Range、List、Hash三种分区方式 的完整SQL示例及增删改查操作,并附上总结对比表格
  • Linux内核引导内存分配器原理
  • 金仓数据库KCM认证考试介绍【2025年4月更新】
  • PgVectore的使用
  • REASONING THOUGHT和REASONING分别是什么意思,有什么区别
  • C语言:字符串
  • Baklib企业CMS的核心要素是什么?
  • 贪心算法之最小生成树问题
  • Sentinel实战(五)、系统保护规则、限流后统一处理及sentinel持久化配置
  • 多GPU训练
  • C++_类和对象(上)
  • 【简单数论】(模运算,快速幂,乘法逆元,同余,exgcd,gcd,欧拉函数,质数,欧拉筛,埃式筛,调和级数枚举,约数,组合数)