【LeetCode Hot100 二分查找】搜索插入位置、搜索二维矩阵、搜索旋转排序数组、寻找两个正序数组的中位数

news2025/1/4 19:19:24

二分查找

    • 搜索插入位置
    • 搜索二维矩阵
    • 在排序数组中查找元素的第一个和最后一个位置
    • 寻找旋转排序数组中的最小值
    • 搜索旋转排序数组
    • 寻找两个正序数组的中位数(hard)

搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1

代码:
闭区间解法

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) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

搜索二维矩阵

给你一个满足下述两条属性的 m x n 整数矩阵:

  • 每行中的整数从左到右按非严格递增顺序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。
在这里插入图片描述
思路
把该二维矩阵设想成一个一维的有序数组,那么在该一维数组的第 i i i 个位置的数可以用二维矩阵 ( m 行 n 列) 表示,即在 i / n i/n i/n 行, i % n i\%n i%n 列.

代码
在上一题的基础上修改代码:

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

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

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

思路
用两次二分查找分别找左边界和有边界
第一次二分法找左边界,第二次二分法找右边界

代码
先初始化左边界有边界为 -1

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        int leftBoard = -1, rightBoard = -1;
        // 寻找左边界
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                leftBoard = mid; 
                right = mid - 1;  // 找到之后 在 mid 的左边区间继续找,直到找到最左边的边界
            } else if(nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        left = 0;
        right = nums.length - 1;
        // 寻找右边界
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                rightBoard = mid;
                left = mid + 1;
            } else if(nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return new int[]{leftBoard, rightBoard};
    }
}

寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。

思路
设 x=nums[mid] 是现在二分取到的数。
我们需要判断 x 和数组最小值的位置关系,谁在左边,谁在右边?
把 x 与最后一个数 nums[n−1] 比大小:

  • 如果 x>nums[n−1],那么可以推出以下结论:
    • nums 一定被分成左右两个递增段;
    • 第一段的所有元素均大于第二段的所有元素;
    • x 在第一段。
    • 最小值在第二段。
    • 所以 x 一定在最小值的左边。
  • 如果 x≤nums[n−1],那么 x 一定在第二段。(或者 nums 就是递增数组,此时只有一段。)
    • x 要么是最小值,要么在最小值右边。

所以,只需要比较 x 和 nums[n−1] 的大小关系,就间接地知道了 x 和数组最小值的位置关系,从而不断地缩小数组最小值所在位置的范围,二分找到数组最小值。

代码

class Solution {
    public int findMin(int[] nums) {
        int n = nums.length;
        int left = 0, right = n - 2;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[n - 1]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return nums[left];
    }
}

搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

思路
使用上一题的思路,先找到该旋转排序数组的最小值的位置,然后根据 target 和 旋转的位置(旋转排序数组的最后一个数)的大小进行比较,判断是在左边查找还是在右边查找。

代码

class Solution {
    public int search(int[] nums, int target) {
        int min = findMin(nums);  // 先找到最小值的下标
        int n = nums.length;
        if (target == nums[n -1]) {
            return n - 1;   // 相等的话直接返回
        } else if (target > nums[n-1]) {
            return searchTarget(nums, target, 0, min - 1);  // 在左边查找
        } else { 
            return searchTarget(nums, target, min, n - 2);  // 在右边查找
        }
    }
	// 查找最小值下标
    public int findMin(int[] nums) {
        int n = nums.length;
        int left = 0, right = n - 2;
        while( left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[n - 1]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
	// 二分查找
    public int searchTarget(int[] nums, int target, int left, int right) {
        int n = nums.length;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1; 
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
}

寻找两个正序数组的中位数(hard)

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

思路
我们将通过 二分查找 来解决这个问题,具体步骤如下:

  1. 确保 nums1 是较短的数组
  • 由于我们要在较短的数组上进行二分查找,为了减少计算复杂度,我们首先确保 nums1 的长度小于或等于 nums2。如果 nums1 的长度大于 nums2,我们交换这两个数组。
  • 这样做的目的是保证二分查找的次数最小化,最大化效率。
  1. 分割数组
  • 我们的目标是将两个数组分割成左右两部分,使得合并后的两个部分的元素个数相同,或者左边多一个元素(如果总长度是奇数)。具体来说,假设 nums1 的长度是 n,nums2 的长度是 m,则:
    • 左边部分的元素个数应为 (n + m + 1) / 2,这个值可以保证左边部分最多比右边部分多一个元素(当总长度是奇数时)。
    • 右边部分的元素个数为 n + m - (n + m + 1) / 2。
  1. 二分查找
  • 在 nums1 上执行二分查找,假设当前查找的分割位置是 partition1,那么 nums1 的左边部分就是 nums1[0]…nums1[partition1-1],右边部分是 nums1[partition1]…nums1[n-1]。
  • 同样地,nums2 的分割位置 partition2 可以通过以下公式计算:
    p a r t i t i o n 2 = ( n + m + 1 ) / 2 − p a r t i t i o n 1 partition2= (n+m+1)/2 −partition1 partition2=(n+m+1)/2partition1
    这个公式确保了左边部分的总元素个数为 (n + m + 1) / 2。
  1. 确保分割符合条件
  • 为了保证左边部分的所有元素都小于或等于右边部分的所有元素,我们需要检查:
    • nums1[partition1 - 1] <= nums2[partition2](nums1 左边的最大值小于 nums2 右边的最小值)。
    • nums2[partition2 - 1] <= nums1[partition1](nums2 左边的最大值小于 nums1 右边的最小值)。
      如果这些条件成立,说明我们找到了合适的分割位置。
  1. 计算中位数
  • 如果两个数组的总长度是奇数,中位数就是左边部分的最大值,max(nums1[partition1 - 1], nums2[partition2 - 1])。
  • 如果两个数组的总长度是偶数,中位数是左边部分的最大值和右边部分的最小值的平均值:
    m e d i a n = ( m a x ( n u m s 1 [ p a r t i t i o n 1 − 1 ] , n u m s 2 [ p a r t i t i o n 2 − 1 ] ) + m i n ( n u m s 1 [ p a r t i t i o n 1 ] , n u m s 2 [ p a r t i t i o n 2 ] ) ) / 2 median = (max(nums1[partition1−1],nums2[partition2−1])+min(nums1[partition1],nums2[partition2])) / 2 median=(max(nums1[partition11],nums2[partition21])+min(nums1[partition1],nums2[partition2]))/2
  1. 二分查找的调整
  • 如果不满足条件,意味着 partition1 需要调整:
    • 如果 nums1[partition1 - 1] > nums2[partition2],则需要将 partition1 向左移,即减小 partition1。
    • 如果 nums2[partition2 - 1] > nums1[partition1],则需要将 partition1 向右移,即增大 partition1。
  1. 边界条件
  • 对于数组的边界,我们使用 Integer.MIN_VALUE 和 Integer.MAX_VALUE 来处理数组分割的边界情况。例如,如果 partition1 为 0,意味着 nums1 左边部分没有元素,我们将 maxLeft1 设置为 Integer.MIN_VALUE;如果 partition1 为 n,意味着 nums1 右边部分没有元素,我们将 minRight1 设置为 Integer.MAX_VALUE。

代码

public class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 保证 nums1 是较短的数组
        if (nums1.length > nums2.length) {
            int[] temp = nums1;
            nums1 = nums2;
            nums2 = temp;
        }

        int n = nums1.length;
        int m = nums2.length;

        // 在 nums1 上进行二分查找
        int left = 0, right = n;

        while (left <= right) {
            int partition1 = (left + right) / 2;
            int partition2 = (n + m + 1) / 2 - partition1;

            // 获取 nums1 和 nums2 中的元素
            int maxLeft1 = (partition1 == 0) ? Integer.MIN_VALUE : nums1[partition1 - 1];
            int minRight1 = (partition1 == n) ? Integer.MAX_VALUE : nums1[partition1];

            int maxLeft2 = (partition2 == 0) ? Integer.MIN_VALUE : nums2[partition2 - 1];
            int minRight2 = (partition2 == m) ? Integer.MAX_VALUE : nums2[partition2];

            // 检查是否找到合适的分割位置
            if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
                // 如果数组长度是奇数
                if ((n + m) % 2 == 1) {
                    return Math.max(maxLeft1, maxLeft2);
                } else {
                    // 如果数组长度是偶数
                    return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2.0;
                }
            } else if (maxLeft1 > minRight2) {
                // 如果 maxLeft1 太大,调整左边界
                right = partition1 - 1;
            } else {
                // 如果 maxLeft2 太大,调整右边界
                left = partition1 + 1;
            }
        }

        return 0.0;
    }
}

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

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

相关文章

nature reviews genetics | 需要更多的针对不同种族的癌症基因组图谱研究,促进精准治疗和维护治疗公平权益

–https://doi.org/10.1038/s41576-024-00796-w Genomic landscape of cancer in racially and ethnically diverse populations 研究团队和单位 Ulrike Peters–Public Health Sciences Division, Fred Hutchinson Cancer Center Claire E. Thomas–Public Health Scienc…

吾杯网络安全技能大赛——Misc方向WP

吾杯网络安全技能大赛——Misc方向WP Sign 题目介绍: 浅浅签个到吧 解题过程&#xff1a; 57754375707B64663335376434372D333163622D343261382D616130632D3634333036333464646634617D 直接使用赛博橱子秒了 flag为 WuCup{df357d47-31cb-42a8-aa0c-6430634ddf4a} 原神启动…

MySQL中distinct和group by去重的区别

MySQL中distinct和group by去重的区别 在MySQL中&#xff0c;我们经常需要对查询结果进行去重&#xff0c;而DISTINCT和GROUP BY是实现这一功能的两种常见方法。虽然它们在很多情况下可以互换使用&#xff0c;但它们之间还是存在一些差异的。接下来&#xff0c;我们将通过创建测…

LinuxC高级day5

作业: 1.思维导图 2.定义一个find函数&#xff0c;查找ubuntu和root的gid并使用变量接收结果 3.定义一个数组&#xff0c;写一个函数完成对数组的冒泡排序 4.使用break求1-100中的质数(质数:只能被1和他本身整除&#xff0c;如:357)

coredns报错plugin/forward: no nameservers found

coredns报错plugin/forward: no nameservers found并且pod无法启动 出现该报错原因 是coredns获取不到宿主机配置的dns地址 查看宿主机是否有dns地址 resolvectl status 我这里是配置正确后&#xff0c;如果没配置过以下是不会显示出dns地址的 给宿主机增加静态dns地址之后将…

2025加密风云:行业变革与未来趋势全景透视

引言 2024年是加密行业发展历程中的重要一年&#xff0c;诸多事件和趋势为未来的发展奠定了基础。随着全球政策环境的变化、技术的不断进步以及市场参与者的多样化&#xff0c;加密行业在2025年将迎来新的转型与挑战。这篇文章将从政策、技术、市场、应用以及社会影响等多个角…

logback之自定义pattern使用的转换器

目录 &#xff08;1&#xff09;场景介绍 &#xff08;2&#xff09;定义转换器BizCallerConverter &#xff08;3&#xff09;logback配置conversionRule &#xff08;4&#xff09;测试效果 前文《logback之pattern详解以及源码分析》已经介绍了pattern&#xff0c;以及…

mac m2 安装 docker

文章目录 安装1.下载安装包2.在downloads中打开3.在启动台打开打开终端验证 修改国内镜像地址小结 安装 1.下载安装包 到官网下载适配的安装包&#xff1a;https://www.docker.com/products/docker-desktop/ 2.在downloads中打开 拖过去 3.在启动台打开 选择推荐设置 …

发那科 PMC 学习与总结

1、CNC 与 PMC CNC&#xff08;Computerized Numerical Control&#xff1a;计算机控制的数控装置&#xff09;和PLC&#xff08;Programmable Logic Controller&#xff1a;可编程顺序逻辑控制器&#xff09;的各项处理由几部分构成。CNC 中系统的控制软件已安装完毕&#xf…

【机器学习应用】决策树

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;机器学习应用入门到进阶 &#x1f30f;代码仓库&#xff1a;GitHub平台 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经…

「Mac畅玩鸿蒙与硬件52」UI互动应用篇29 - 模拟火车票查询系统

本篇教程将实现一个模拟火车票查询系统&#xff0c;通过输入条件筛选车次信息&#xff0c;并展示动态筛选结果&#xff0c;学习事件处理、状态管理和界面展示的综合开发技巧。 关键词 条件筛选动态数据展示状态管理UI交互查询系统 一、功能说明 模拟火车票查询系统包含以下功…

JVM对象内存分配

1 栈上分配 栈空间随着方法执行完毕而回收通过栈上分配对象内存空间的方式&#xff0c;减少对堆空间的使用&#xff0c;从而减少gc的压力&#xff0c;提升程序性能 逃逸分析&#xff1a;分析对象的作用域&#xff0c;判断对象所需内存是否可以在栈上分配当对象没有被外部方法或…

Science Advances:一种多功能软变形和触觉显示器

01 内容概览现有技术缺点无法主动将变形的表面重新配置为各种 3D 形状。设备庞大复杂&#xff1a;机械装置依赖线性致动器和刚性结构&#xff0c;阻碍其小型化和实用性。功能单一&#xff1a;现有软材料驱动方案通常只能变形&#xff0c;缺乏多模态触觉反馈&#xff0c;难以满足…

如何从文档创建 RAG 评估数据集

我们在 Hugging Face Hub 上自动生成的 RAG 评估数据集&#xff08;来自欧盟的PDF 输入文件&#xff0c;根据CC BY 4.0许可&#xff09;。图片由作者提供 在本文中&#xff0c;我将向您展示如何创建自己的 RAG 数据集&#xff0c;该数据集包含任何语言的文档的上下文、问题和答…

flux中的缓存

1. cache&#xff0c;onBackpressureBuffer。都是缓存。cache可以将hot流的数据缓存起来。onBackpressureBuffer也是缓存&#xff0c;但是当下游消费者的处理速度比上游生产者慢时&#xff0c;上游生产的数据会被暂时存储在缓冲区中&#xff0c;防止丢失。 2. Flux.range 默认…

CES Asia 2025:科技盛宴引领未来,BESTAR声学创新备受瞩目

随着科技行业的蓬勃发展&#xff0c;亚洲消费电子技术领域的年度盛会——CES Asia 2025&#xff08;赛逸展&#xff09;即将盛大举行。作为全球消费电子行业的风向标展会&#xff0c;CES Asia 2025吸引了众多顶尖科技企业参与&#xff0c;将围绕智能家居、智能出行、虚拟现实、…

HTML-CSS-常见标签与样式

目录 一. 央视新闻排版1.1 标题1.2 正文1.3 案例1.3.1 顶部导航栏1.3.2 flex布局1.3.3 表单标签1.3.4 表单项标签1.3.5 表格 1.3 课程总结 \quad 一. 央视新闻排版 \quad \quad 1.1 标题 \quad ALTp就是用AI快速生成 标题一共有6级 \quad 1.2 正文 \quad 定义视频 定义图片 样…

c++领域展开第九幕——类和对象(下篇收尾 友元函数、内部类、拷贝对象时的编译器优化)超详细!!!!

文章目录 前言一、友元函数二、内部类三、匿名对象四、对象拷贝时的编译器优化总结 前言 上篇博客我们学习了类和对象后期的一些内容——初始化列表、类型转换、static成员 今天我们来对类和对象进行最后的收尾 开始发车啦 一、友元函数 • 友元提供了一种突破类访问限定符封装…

LevelDB 源码阅读:利用 Clang 的静态线程安全分析

LevelDB 中有一些宏比较有意思&#xff0c;平时自己写代码的时候&#xff0c;还基本没用过。这些宏在 thread_annotations.h 中定义&#xff0c;可以在编译时使用 Clang 编译器的线程安全分析工具&#xff0c;来检测潜在的线程安全问题。 比如下面这些宏&#xff0c;到底有什么…

什么是实体完整性约束?

实体完整性约束是数据库设计中的一个核心概念&#xff0c;它确保了关系数据库中每个实体的唯一性和可标识性。以下是对实体完整性约束的详细解释&#xff1a; 一、定义 实体完整性约束是指关系的主关键字&#xff08;Primary Key&#xff09;不能重复&#xff0c;也不能取“空值…