LeetCode 热题100之二分

关于二分,之前也写过一篇,参考二分Acwing

1.搜索插入位置

在这里插入图片描述思路分析:典型的 二分查找算法,用于在一个已排序的数组中查找目标值的位置。如果找到了目标值,返回其索引;如果没有找到,则返回目标值应该插入的位置。

  • 初始化左右边界
  • 二分查找
    • 每次计算mid,通过 mid = left + (right - left) / 2 来避免可能的溢出;
    • 如果目标值 target 与 nums[mid] 相等,则返回 mid
    • 如果 nums[mid] 大于 target,则将右边界 right 移动到 mid - 1,继续在左半边查找;
    • 如果 nums[mid] 小于 target,则将左边界 left 移动到 mid + 1,继续在右半边查找。
  • 返回插入位置:如果没有找到目标值,left 会指向目标值应该插入的位置。此时,left 就是目标值应该插入的索引。

具体实现代码(详解版):

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 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;  // 返回左边界位置,即目标值应插入的位置
    }
};

  • 时间复杂度:O(log n),每次查找都会将搜索空间减小一半,最多执行log n次;
  • 空间复杂度:O(1)

2.搜索二维矩阵

在这里插入图片描述
思路分析:由于每一行是升序排列的,而且每列也是升序排列的,我们可以通过二分查找直接在二维矩阵中进行查找,而不需要将矩阵展平为一维数组。

  • 单一的二分查找:我们将二维矩阵视为一个有序的 1D 数组,矩阵的元素总数是 m * n。关键点是将每个中间索引 mid 映射到二维矩阵中的位置,mid / n 对应行,mid % n 对应列。;
  • 索引映射:通过 mid / n 得到对应的行索引,通过 mid % n 得到列索引。这种映射方式相当于将矩阵展平,但不需要额外的空间。

具体实现代码(详解版):

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if (matrix.empty() || matrix[0].empty()) return false;

        int m = matrix.size();
        int n = matrix[0].size();
        
        int left = 0, right = m * n - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2;
            //就这一句不同,yyds
            int midValue = matrix[mid / n][mid % n]; // 将 1D 索引转换为 2D 索引

            if (midValue == target) {
                return true;
            } else if (midValue < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        
        return false;
    }
};

  • 时间复杂度:O(log(m * n))
  • 空间复杂度:O(1)

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

在这里插入图片描述
思路分析:这个问题可以分为两个子问题:查找目标值的起始位置,查找目标值的结束位置;

  • 1.查找起始位置

    • 设定两个指针,分别指向数组的左右端点;
    • 在每次比较时,我们计算中间位置 mid,然后根据数组的值来调整左右边界;
      • 如果 nums[mid] 大于或等于 target,我们就缩小右边界 r = mid,因为目标值可能在当前的 mid 或左边部分。
      • 如果 nums[mid] 小于 target,说明目标值在右边,因此移动左边界 l = mid + 1。
    • 当 l 和 r 收敛时,我们检查 nums[l] 是否等于 target,如果是,l 就是目标值的 起始位置。
  • 2.查找结束位置

    • 查找 结束位置 的方法与查找起始位置非常相似,但这次我们需要找到 最后一个出现的目标值。因此,在二分查找过程中,当目标值出现时,我们继续往右查找,直到找到最后一个目标值。
    • 使用两个指针 l2 和 r2,开始时设定为整个数组的范围。
    • 计算中间位置 mid,并根据 nums[mid] 和 target 的关系来调整左右边界:
      • 如果 nums[mid] 小于或等于 target,我们可能找到了目标值的最后位置,因此继续向右查找,调整 l2 = mid。
      • 如果 nums[mid] 大于 target,则目标值只能在 mid 左边,因此调整右边界 r2 = mid - 1。
    • 当 l2 和 r2 收敛时,l2 就是目标值的 结束位置。

具体实现代码(详解版)

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> res = {-1, -1};
        if (nums.empty()) {
            return res;
        }

        // 查找目标值的起始位置
        int l = 0, r = nums.size() - 1;
        while (l < r) {
            int mid = l + (r - l) / 2; // 防止溢出
            if (nums[mid] >= target) {
                r = mid; // 目标值可能在左半部分或是 mid 本身
            } else {
                l = mid + 1;
            }
        }
        
        // 确保 nums[l] 是目标值
        if (nums[l] != target) {
            return res; // 如果没有找到目标值,直接返回 {-1, -1}
        }
        
        int start = l;
        
        // 查找目标值的结束位置
        int l2 = 0, r2 = nums.size() - 1;
        while (l2 < r2) {
            int mid = l2 + (r2 - l2 + 1) / 2; // 防止溢出
            if (nums[mid] <= target) {
                l2 = mid; // 目标值可能在右半部分或是 mid 本身
            } else {
                r2 = mid - 1;
            }
        }
        
        int end = l2;
        
        // 返回目标值的起始位置和结束位置
        return {start, end};
    }
};

  • 时间复杂度:O(log n);
  • 空间复杂度:O(1)

4.搜搜旋转排序数组

在这里插入图片描述
思路分析:旋转后的数组会分为两个升序的部分:一部分可能是从旋转点到数组的末尾,另一部分是从数组的开头到旋转点。我们可以利用二分查找,但需要判断中间元素的位置是处于旋转后的哪个部分。通过这个判断,我们能够缩小搜索范围。

  • 初始化两个指针 left 和 right,分别指向数组的头和尾。;
  • 计算中间位置 mid = left + (right - left) / 2
  • 判断 nums[mid] 是否等于目标值 target,如果是,直接返回 mid。
  • 判断 nums[mid] 是否等于目标值 target,如果是,直接返回 mid。
    • 如果左半部分升序:nums[left] <= nums[mid],此时:
      • 如果 nums[left] <= target < nums[mid],目标值在左半部分,更新 right = mid - 1;
      • 否则,目标值在右半部分,更新 left = mid + 1。
    • 如果右半部分升序:nums[mid] <= nums[right],此时:
      • 如果 nums[mid] < target <= nums[right],目标值在右半部分,更新 left = mid + 1;
      • 否则,目标值在左半部分,更新 right = mid - 1。
  • 如果 left 超过 right,说明目标值不存在于数组中,返回 -1。

具体实现代码(详解版):

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2;  // 防止溢出
            
            if (nums[mid] == target) {
                return mid;  // 找到目标值,返回索引
            }
            
            // 判断左半部分是否有序
            if (nums[left] <= nums[mid]) {
                // 如果目标值在左半部分
                if (nums[left] <= target && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else {
                // 右半部分有序
                // 如果目标值在右半部分
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        
        return -1;  // 找不到目标值
    }
};

5.寻找寻转数组中的最小值

在这里插入图片描述
思路分析:旋转的关键在于数组的最小值。最小值一定在旋转点处,旋转后的数组就像是两个升序数组拼接在一起,最小值一定是较小的部分的第一个元素。通过二分查找来高效地找到最小值的位置:

  • 如果 nums[mid] >= nums[right],说明旋转点在右半部分或是 mid 本身可能是旋转点的前一个位置。此时最小值必然在右半部分,我们将 left = mid + 1。
  • 否则,说明最小值在左半部分或 mid 本身就是最小值,我们将 right = mid。
  • 最终,left == right,nums[left]就是最小值。

具体实现代码(详解版):

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;

        // 二分查找
        while (left < right) {
            int mid = left + (right - left) / 2;

            // 如果 mid 元素大于右边的元素,最小值一定在右边
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            } else {
                // 如果 mid 元素小于等于右边的元素,最小值可能在左边
                right = mid;
            }
        }

        // 当 left == right 时,left 就是最小值的位置
        return nums[left];
    }
};

6.寻找两个正序数组中的中位数

在这里插入图片描述

真实难题!
思路分析:由于我们需要在两个有序数组中找到中位数,可以考虑通过二分查找的方式,在一个数组中找到合适的分割点,并利用这个分割点将另一个数组分成合适的部分。

  • 中位数的定义:如果合并两个数组后的总元素数是奇数,那么中位数就是中间那个元素;如果合并两个数组后的总元素数是偶数,那么中位数是中间两个元素的平均值。
  • 通过二分查找进行分割
    • 我们将数组 nums1 和 nums2 分成两部分,使得:
      • 左边部分包含的是小于等于右边部分的元素。
      • 两个数组的左部分和右部分的元素个数总是相等(或者左部分多一个)。
    • 我们希望通过二分查找来找到数组 nums1 中的分割点 i,从而通过 i 可以推算出数组 nums2 中的分割点 j。
    • 条件检查:对于每一对 i 和 j,我们检查是否满足:
      • nums1[i-1] <= nums2[j](nums1 左半部分最大值小于等于 nums2 右半部分最小值)
      • nums2[j-1] <= nums1[i](nums2 左半部分最大值小于等于 nums1 右半部分最小值)
      • 如果满足条件,计算并返回中位数。
    • 如果不满足上述条件,调整 i,继续二分查找。

具体实现代码(详解版):

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.size() > nums2.size()) {
            // 确保 nums1 是较小的数组
            return findMedianSortedArrays(nums2, nums1);
        }
        
        int m = nums1.size();
        int n = nums2.size();
        
        int left = 0, right = m;
        while (left <= right) {
            int i = left + (right - left) / 2;  // nums1 的分割点
            int j = (m + n + 1) / 2 - i;       // nums2 的分割点
            
            // 处理 nums1[i-1] 和 nums2[j-1] 可能越界的情况
            int nums1LeftMax = (i == 0) ? INT_MIN : nums1[i - 1];
            int nums1RightMin = (i == m) ? INT_MAX : nums1[i];
            int nums2LeftMax = (j == 0) ? INT_MIN : nums2[j - 1];
            int nums2RightMin = (j == n) ? INT_MAX : nums2[j];
            
            // 检查是否找到了合适的分割点
            if (nums1LeftMax <= nums2RightMin && nums2LeftMax <= nums1RightMin) {
                // 如果总数为奇数,返回左半部分最大值
                if ((m + n) % 2 == 1) {
                    return max(nums1LeftMax, nums2LeftMax);
                }
                // 如果总数为偶数,返回两者中间的平均值
                return (max(nums1LeftMax, nums2LeftMax) + min(nums1RightMin, nums2RightMin)) / 2.0;
            } 
            else if (nums1LeftMax > nums2RightMin) {
                // nums1[i-1] 太大,缩小 i
                right = i - 1;
            } else {
                // nums2[j-1] 太大,增大 i
                left = i + 1;
            }
        }
        
        return 0.0;
    }
};

  • 时间复杂度:O(log(min(m,n))
  • 空间复杂度:O(1)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

viewerjs实现以图片中心点进行缩放

最近有个需求&#xff0c;使用到了viewerjs对一个图片进行可缩放预览&#xff0c;但是存在一个问题&#xff0c;通过滚轮缩放图片时会导致图片移动到视窗外面。 翻了一下GitHub上的源码&#xff0c;viewerjs滚轮&#xff08;触摸板双指&#xff09;缩放功能是监听了wheel事件&a…

OpenAI大事记;GPT到ChatGPT参数量进化

目录 OpenAI大事记 GPT到ChatGPT参数量进化 OpenAI大事记 GPT到ChatGPT参数量进化 ChatGPT是从初代 GPT逐渐演变而来的。在进化的过程中,GPT系列模型的参数数量呈指数级增长,从初代GPT的1.17亿个参数,到GPT-2的15 亿个参数,再到 GPT-3的1750 亿个参数。模型越来越大,训练…

通过包控制->获取包重新获取之后,需求类型列表不对

龙勤思(2017年11月27日)&#xff1a; 这个类型列表&#xff0c;我在把需求包提交到svn&#xff0c;再新建一个eap&#xff0c;通过包控制->获取包重新获取之后&#xff0c;就变成默认的如下列表了。我从你的原始的eap导出参考数据&#xff0c;再导入到新建的eap&#xff0c…

HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac

寻找模拟器 背景&#xff1a; 运行的是h5&#xff0c;模拟器是网易MuMu。 首先检查一下是否配置dab环境&#xff0c;adb version 配置一下hbuilderX的adb&#xff1a; 将命令输出的路径配置到hbuilderx里面去&#xff0c;然后重启下HbuilderX。 开始安装基座…一直安装不…

C++builder中的人工智能(15):C++高斯误差线性单元(GELU)

在这篇文章中&#xff0c;我们将探索高斯误差线性单元&#xff08;GELU&#xff1a;Gaussian Error Linear Unit&#xff09;是什么&#xff0c;它是如何在人工神经网络&#xff08;ANN&#xff09;中工作的&#xff0c;以及GELU可以应用于哪些AI技术。通过学习C中的高斯误差线…

Android 下内联汇编,Android Studio 汇编开发

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 内联汇编 Android 内联汇编非常适用于 ARM 架构的性能优化和底层操作&#xff0c;通常用于加密、解密、特定指令优化等领域。 1. 基础语法 内联汇编在 C/C …

【go从零单排】泛型(Generics)、链表

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 在Go语言中&#xff0c;泛型&#xff08;Generics&#xff09;允许你编写可以处理…

vue项目删除无用的依赖

1.安装依赖检查工具 npm i depcheck2.查看无用的依赖 depcheck3.手动删除pageage.json中的无用的依赖&#xff08;如果有sass和sass-loader不要删&#xff0c;会引起项目报错&#xff09; 4.全部删除完成之后&#xff0c;删除package-lock.json文件&#xff0c;删除node_mod…

「Qt Widget中文示例指南」如何创建一个窗口标志?(一)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 窗口标志要么是类型…

移植 AWTK 到 纯血鸿蒙 (HarmonyOS NEXT) 系统 (9) - 编译现有的AWTK应用程序

AWTK 应用程序开发完成后&#xff0c;在配置文件中添加 harmonyos 的选项&#xff0c;通过create_project.py脚本即可生成 DevEco Studio的工程。 安装开发环境 DevEco Studio HarmonyOS 的开发工具。 Python 运行环境。 git 源码管理工具。 下载 awtk 和 awtk-harmonyos…

C++ 参数传递 笔记

目录 1、输入参数的传递方式-选择传值还是传引用&#xff1f; 处理用户信息 处理坐标 处理配置 处理ID 2、对于需要修改的参数,使用非const引用传递 有趣的例外&#xff1a;警惕表象的迷惑 需要警惕的陷阱 “糟糕”的update方法&#xff1a; “完美”的set_name与set…

【Eclipse系列】eclipse安装与常规配置(含插件)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、下载与安装 二、常规设置 1.1.设置工作空间(workspace) 1.2.设置字体和字体大小 ​编辑 1.3.设置编码 1.4.去除验证(validation) 1.5.去除单词验证(spelli…

内网对抗-信息收集篇SPN扫描DC定位角色区域定性服务探针安全防护凭据获取

知识点&#xff1a; 1、信息收集篇-网络架构-出网&角色&服务&成员 2、信息收集篇-安全防护-杀毒&防火墙&流量监控 3、信息收集篇-密码凭据-系统&工具&网站&网络域渗透的信息收集&#xff1a; 在攻防演练中&#xff0c;当完成边界突破后进入内…

C语言 | Leetcode C语言题解之第540题有序数组中的单一元素

题目&#xff1a; 题解&#xff1a; int singleNonDuplicate(int* nums, int numsSize) {int low 0, high numsSize - 1;while (low < high) {int mid (high - low) / 2 low;mid - mid & 1;if (nums[mid] nums[mid 1]) {low mid 2;} else {high mid;}}return …

【开发】关于Java中String与Integer的小小知识点(使用等号对比引用对象)

一个很简单的小知识点 我们都知道&#xff0c;如果使用对比包装类型或对象&#xff0c;那么比较的都是两者之间的地址&#xff08;指针或句柄&#xff09;&#xff0c;而非对象本身&#xff0c;那么且看下方的代码。 public class A {public static void main(String[] args)…

纯前端实现在线预览excel文件(插件: LuckyExcel、Luckysheet)

概述 在实际开发中&#xff0c;遇到需要在线预览各种文件的需求&#xff0c;最近遇到在线预览excel文件的需求&#xff0c;在此记录一下&#xff01;本文主要功能实现&#xff0c;用于插件 LuckyExcel &#xff0c;Luckysheet&#xff01;废话不多说&#xff0c;上代码&#xf…

洛谷每日一题——B2143 进制转换、P1003 [NOIP2011 提高组] 铺地毯

B2143 进制转换 题目描述 进制转换 - 洛谷 运行代码 #include<stdio.h> int main(){int a,b,i0,j,num[20];char k[]{0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F};scanf("%d",&a);scanf("%d",&b);do{i;num[i]a%b;aa/b;}while(a!0);printf("&qu…

Elasticsearch的自定义查询方法到底是啥?

Elasticsearch主要的目的就是查询&#xff0c;默认提供的查询方法是查询全部&#xff0c;不满足我们的需求&#xff0c;可以定义查询方法 自定义查询方法 单条件查询 我们查询的需求&#xff1a;从title中查询所有包含"鼠标"这个分词的商品数据 SELECT * FROM it…

环境配置与搭建

安装pytorch 官网连链接&#xff1a;https://pytorch.org/ 特殊包名 cv2 pip install opencv-python sklearn pip install scikit-learnPIL pip install Pillow使用jupyter notebook pip install jupyter安装显卡驱动 Windows Linux 视频教程&#xff1a; 【ubuntu2…

jmeter常用配置元件介绍总结之函数助手

系列文章目录 1.windows、linux安装jmeter及设置中文显示 2.jmeter常用配置元件介绍总结之安装插件 3.jmeter常用配置元件介绍总结之取样器 jmeter常用配置元件介绍总结之函数助手 1.进入函数助手对话框2.常用函数的使用介绍2.1.RandomFromMultipleVars函数2.2.Random函数2.3.R…