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

在这里插入图片描述

优质博文IT-BLOG-CN

一、题目

已知一个长度为n的数组,预先按照升序排列,经由1n次 旋转 后,得到输入数组。例如,原数组nums = [0,1,2,4,5,6,7]在变化后可能得到:
【1】若旋转4次,则可以得到[4,5,6,7,0,1,2]
【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次得到输入数组。

示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为[11,13,15,17],旋转4次得到输入数组。

n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums中的所有整数 互不相同
nums原来是一个升序排序的数组,并进行了1n次旋转

二、代码

二分查找

我们考虑数组中的最后一个元素xxx:在最小值右侧的元素(不包括最后一个元素本身),它们的值一定都严格小于xxx;而在最小值左侧的元素,它们的值一定都严格大于xxx。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。

在二分查找的每一步中,左边界为low,右边界为high,区间的中点为pivot,最小值就在该区间内。我们将中轴元素nums[pivot]与右边界元素nums[high]进行比较,可能会有以下的三种情况:

第一种情况是nums[pivot]<nums[high]。这说明nums[pivot]是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。

第二种情况是nums[pivot]>nums[high]。这说明nums[pivot]是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。

由于数组不包含重复元素,并且只要当前的区间长度不为1pivot就不会与high重合;而如果当前的区间长度为1,这说明我们已经可以结束二分查找了。因此不会存在nums[pivot]=nums[high]的情况。

当二分查找结束时,我们就得到了最小值所在的位置。

class Solution {
    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (nums[pivot] < nums[high]) {
                high = pivot;
            } else {
                low = pivot + 1;
            }
        }
        return nums[low];
    }
}

时间复杂度: 时间复杂度为O(log⁡n),其中n是数组nums的长度。在二分查找的过程中,每一步会忽略一半的区间,因此时间复杂度为O(log⁡n)

空间复杂度: O(1)

主要思路

单调递增的序列:

        *
      *
    *
  *
*

做了旋转:

  *
*
        *
      *
    *

用二分法查找,需要始终将目标值(这里是最小值)套住,并不断收缩左边界或右边界。

左、中、右三个位置的值相比较,有以下几种情况:

    左值 < 中值, 中值 < 右值 :没有旋转,最小值在最左边,可以收缩右边界

            右

         中

     左

左值 > 中值, 中值 < 右值 :有旋转,最小值在左半边,可以收缩右边界

     左       

             右

         中

左值 < 中值, 中值 > 右值 :有旋转,最小值在右半边,可以收缩左边界

         中  

     左 

             右

左值 > 中值, 中值 > 右值 :单调递减,不可能出现

     左

        中

            右

分析前面三种可能的情况,会发现情况12是一类,情况3是另一类。
【1】如果中值 < 右值,则最小值在左半边,可以收缩右边界。
【2】如果中值 > 右值,则最小值在右半边,可以收缩左边界。
【3】通过比较中值与右值,可以确定最小值的位置范围,从而决定边界收缩的方向。

而情况1与情况3都是左值 < 中值,但是最小值位置范围却不同,这说明,如果只比较左值与中值,不能确定最小值的位置范围。所以我们需要通过比较中值与右值来确定最小值的位置范围,进而确定边界收缩的方向。

接着分析解法里的一些问题:首先是while循环里的细节问题。这里的循环不变式是left < right, 并且要保证左闭右开区间里面始终套住最小值。

中间位置的计算:mid = left + (right - left) / 2这里整数除法是向下取整的地板除,mid更靠近left,再结合while循环的条件left < right,可以知道left <= mid,mid < right,即在while循环内,mid始终小于right。因此在while循环内,nums[mid]要么大于要么小于nums[right],不会等于。这样else {right = mid;}这句判断可以改为更精确的else if (nums[mid] < nums[right]) {right = mid;}

再分析一下while循环退出的条件。

如果输入数组只有一个数,左右边界位置重合,left == right,不会进入while循环,直接输出。

如果输入数组多于一个数,循环到最后,会只剩两个数,nums[left] == nums[mid],以及nums[right],这里的位置left == mid == right - 1

如果nums[left] == nums[mid] > nums[right],则左边大、右边小,需要执行left = mid + 1,使得left == right,左右边界位置重合,循环结束,nums[left]与nums[right]都保存了最小值。

如果nums[left] == nums[mid] < nums[right],则左边小、右边大,会执行right = mid,使得left == right,左右边界位置重合,循环结束,nums[left]nums[mid]nums[right]都保存了最小值。

细化了的代码:

class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;                /* 左闭右闭区间,如果用右开区间则不方便判断右值 */ 
        while (left < right) {                      /* 循环不变式,如果left == right,则循环结束 */
            int mid = left + (right - left) / 2;    /* 地板除,mid更靠近left */
            if (nums[mid] > nums[right]) {          /* 中值 > 右值,最小值在右半边,收缩左边界 */ 
                left = mid + 1;                     /* 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid */ 
            } else if (nums[mid] < nums[right]) {   /* 明确中值 < 右值,最小值在左半边,收缩右边界 */ 
                right = mid;                        /* 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处 */ 
            }
        }
        return nums[left];    /* 循环结束,left == right,最小值输出nums[left]或nums[right]均可 */     
    }
};

再讨论一个问题: 为什么左右不对称?为什么比较midright而不比较midleft?能不能通过比较midleft来解决问题?

左右不对称的原因是: 这是循环前升序排列的数,左边的数小,右边的数大,而且我们要找的是最小值,肯定是偏向左找,所以左右不对称了。

为什么比较midright而不比较midleft
具体原因前面已经分析过了,简单讲就是因为我们找最小值,要偏向左找,目标值右边的情况会比较简单,容易区分,所以比较midright而不比较midleft

那么能不能通过比较midleft来解决问题?
能,转换思路,不直接找最小值,而是先找最大值,最大值偏右,可以通过比较midleft来找到最大值,最大值向右移动一位就是最小值了(需要考虑最大值在最右边的情况,右移一位后对数组长度取余)。

以下是先找最大值的代码,可以与前面找最小值的比较:

class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1; 
        while (left < right) {
            int mid = left + (right - left + 1) / 2;   /* 先加一再除,mid更靠近右边的right */
            if (nums[left] < nums[mid]) {
                left = mid;                            /* 向右移动左边界 */
            } else if (nums[left] > nums[mid]) {
                right = mid - 1;                       /* 向左移动右边界 */
            }
        }
        return nums[(right + 1) % nums.length];    /* 最大值向右移动一位就是最小值了(需要考虑最大值在最右边的情况,右移一位后对数组长度取余) */
    }
};

使用left < rightwhile循环条件可以很方便推广到数组中有重复元素的情况,只需要在nums[mid] == nums[right]时挪动右边界就行:

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

初始条件是左闭右闭区间,right = nums.size() - 1,那么能否将while循环的条件也选为左闭右闭区间left <= right

可以的,代码如下:

class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1; 
        while (left <= right) {                         // 循环的条件选为左闭右闭区间left <= right
            int mid = left + (right - left) / 2;
            if (nums[mid] >= nums[right]) {             // 注意是当中值大于等于右值时,
                left = mid + 1;                         // 将左边界移动到中值的右边
            } else {                                    // 当中值小于右值时
                right = mid;                            // 将右边界移动到中值处
            }
        }
        return nums[right];                             // 最小值返回nums[right]
    }
};

这道题还有其它解法: 始终将nums[mid]与最右边界的数进行比较,相当于在每次裁剪区间之后始终将最右边的数附在新数组的最右边。

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

或者在处理了第一种情况之后,始终将nums[mid]与最左边界的数nums[0]进行比较,即相当于在每次裁剪区间之后始终将最左边的数附在新数组的最左边,再不断处理情况2及情况3

class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        if nums[left] < nums[right]:
            return nums[left]
        while left < right:
            mid = (left + right) >> 1
            if nums[0] > nums[mid]:
                right = mid
            else:
                left = mid + 1
        return nums[left]

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

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

相关文章

perf的安装与迁移

前言 perf是性能优化很重要的工具之一&#xff0c;本篇博客就来看一下perf的安装以及遇到的问题。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关注本博主并订阅本专栏&#xff0c;一起讨论一起学…

Unity性能优化篇(四) GPU Instancing

使用GPU Instancing可以在一个Draw Call中同时渲染多个相同或类似的物体&#xff0c;从而减少CPU和GPU的开销。 官方文档&#xff1a;https://docs.unity3d.com/Manual/GPUInstancing.html 启用GPU Instancing&#xff0c;我们可以选中一个材质&#xff0c;然后在Inspector窗口…

【你也能从零基础学会网站开发】Web建站之HTML+CSS入门篇 传统布局和Web标准布局的区别

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 传统布局与…

c++复习

基础 内存分区 栈&#xff1a; 存放函数的局部变量、函数参数、返回地址等&#xff0c;由编译器自动分配和释放。 堆&#xff1a; 动态申请的内存空间&#xff0c;就是由 malloc 分配的内存块&#xff0c;由程序员控制它的分配和释放&#xff0c;如果程序执行结束还没有释放…

【MySQL 系列】MySQL 起步篇

MySQL 是一个开放源代码的、免费的关系型数据库管理系统。在 Web 开发领域&#xff0c;MySQL 是最流行、使用最广泛的关系数据库。MySql 分为社区版和商业版&#xff0c;社区版完全免费&#xff0c;并且几乎能满足全部的使用场景。由于 MySQL 是开源的&#xff0c;我们还可以根…

[HackMyVM]Quick 2

kali:192.168.56.104 主机发现 arp-scan -l # arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:d2:e0:49, IPv4: 192.168.56.104 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.56.1 0a:00:27:00:00:05 (Un…

Chromium内核浏览器编译记(四)Linux版本CEF编译

转载请注明出处&#xff1a;https://blog.csdn.net/kong_gu_you_lan/article/details/136508294 本文出自 容华谢后的博客 0.写在前面 本篇文章是用来记录编译Linux版本CEF的步骤和踩过的坑&#xff0c;以防止后续再用到的时候忘记&#xff0c;同时也希望能够帮助到遇到同样问…

Uboot的start.s源码分析

/* * armboot - Startup Code for ARM920 CPU-core * * Copyright (c) 2001 Marius Gr鰃er <magsysgo.de> * Copyright (c) 2002 Alex Z黳ke <azusysgo.de> * Copyright (c) 2002 Gary Jennejohn <gjdenx.de> * * See file CREDITS for list of people w…

微软首批 AI PC 产品将于3月21日发布;腾讯、字节再战 AI 社交产品

微软首批 AI PC 产品将于 3 月 21 日发布 据 Windows Central 报道&#xff0c;微软将于 3 月 21 日发布 Surface Pro 10 和 Surface Laptop 6&#xff0c;这有望成为微软首批 AI PC 产品&#xff0c;性能与效率或可媲美苹果 iPad Pro 和 MacBook Pro。 新品将搭载基于英特尔酷…

OpenHarmony教程指南—Ability的启动模式

介绍 本示例展示了在一个Stage模型中&#xff0c;实现standard、singleton、specified多种模式场景。 本实例参考开发指南 。 本实例需要使用aa工具 查看应用Ability 模式信息。 效果预览 使用说明 1、standard模式&#xff1a; 1&#xff09;进入首页&#xff0c;点击番茄…

arm系统构建的基础知识

目录 一、环境变量 二、归档和压缩 (一) 常用命令 (二) 常用参数 三、磁盘分区和挂载 四、网络管理 一、环境变量 显示环境变量 —— echo设置临时环境变量 —— exportecho $PATH —— 显示当前PATH环境变量 在当前目录下&#xff0c;编写一个hello.c 编译并运行。 图…

HTML静态网页成品作业(HTML+CSS)——花主题介绍网页设计制作(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

免费实时天气预报api接口

5分钟更新一次,包含基本天气信息、24小时逐小时天气、实时气象预警列表、湿度、能见度、气压、日出日落、9大生活指数、pm2.5、pm10、o3、no2、so2、是否需要带口罩、外出适宜、开窗适宜、是否需要打开净化器等,可按地名、城市编号、IP查询。 新增:优化预警字段, 返回实时预…

16、电源管理入门之驱动Runtime PM管理

目录 1. 框架介绍 1.1 为什么需要Runtime PM Framework? 1.2 系统框架图 2. Drivers 3. Runtime PM core 4. power domain framework 5. runtime pm的sysfs 6参考: Runtime PM管理也就是设备驱动里面的电源管理,即设备驱动结构体里面的struct dev_pm_ops,只控制设…

Spring Boot 配置热部署

前言 对于 Spring Boot 项目之中, 在刚开始学习的时候, 每当代码进行变动的时候, 想要生效那就必须要手动重启. 为什么要重启呢 ? 原因在于写的代码是依靠运行之后的 class 文件运行的, 当我们的代码更新以后, 如果不去手动重启, 那么就无法生成新的 class 文件, 执行的就是旧…

影响哈默纳科Harmonic减速机使用寿命的5大因素

哈默纳科HarmonicDrive减速机以其轻量、小型、传动效率高、减速范围广、精度高等特点&#xff0c;被广泛应用于各种传动系统中。然而&#xff0c;尽管哈默纳科Harmonic减速机具有诸多优势&#xff0c;但其使用寿命仍可能受到多种因素的影响。 首先&#xff0c;环境因素对哈默纳…

grid布局所有元素在同一行显示且等分列

目录 一、问题 二、实现方式 三、总结 tiips:如嫌繁琐&#xff0c;直接移步总结即可&#xff01; 一、问题 1.grid布局可以通过 grid-template-columns来指定列的宽度。且可以通过repeat来指定重复的次数。但是现在的需求是&#xff1a;grid布局中元素的数量不确定&#…

学生信息管理APP

设计内容简介 本次设计使用Android Studio实现一个学生信息管理系统,系统功能结构如下图所示: 详细设计 数据库设计SQLite,是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低。…

排序算法:插入排序和希尔排序

一、插入排序 1.基本原理 插入排序&#xff08;英语&#xff1a;Insertion Sort&#xff09;是一种简单直观的排序算法。它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上…

27.基于springboot + vue实现的前后端分离-网上租赁交易系统(项目 + 论文)

项目介绍 本课题是根据用户的需要以及网络的优势建立的一个基于Spring Boot的网上租贸系统&#xff0c;来满足用户网络商品租赁的需求。本网上租贸系统应用Java技术&#xff0c;MYSQL数据库存储数据&#xff0c;基于Spring Boot框架开发。在网站的整个开发过程中&#xff0c;首…