旋转排序数组系列题详解

文章目录

  • 旋转排序数组系列题详解
    • 一、问题描述:旋转数组的最小数字
    • 二、分析:二分查找
    • 三、代码
    • 四、问题描述:寻找旋转排序数组中的最小值
    • 五、分析:二分搜索
    • 六、代码
    • 七、问题描述:寻找旋转排序数组中的最小值 II
    • 八、分析:二分查找
    • 九、代码
    • 十、问题描述:搜索旋转排序数组
    • 十一、分析:二分搜索
    • 十二、代码
    • 十三、问题描述:搜索旋转排序数组II
    • 十四、代码

一、问题描述:旋转数组的最小数字

二、分析:二分查找

一个包含重复元素的升序数组在经过旋转之后,可以得到下面可视化的折线图:

  • 其中横轴表示数组元素的下标,纵轴表示数组元素的值。图中标出了最小值的位置,是我们需要旋转的目标。

  • 我们考虑数组中的最后一个元素 x在最小值右侧的元素,它们的值一定都小于等于 x;而在最小值左侧的元素,它们的值一定都大于等于 x。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。

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

  • 第一种情况是numbers[pivot]<numbers[high]

如下图所示,这说明numbers[pivot] 是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。

  • 第二种情况是numbers[pivot]>numbers[high]

如下图所示,这说明 numbers[pivot] 是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。

  • 第三种情况是numbers[pivot]==numbers[high]。

如下图所示,由于重复元素的存在,我们并不能确定 numbers[pivot] 究竟在最小值的左侧还是右侧,因此我们不能莽撞地忽略某一部分的元素。

我们唯一可以知道的是,由于它们的值相同,所以无论numbers[high] 是不是最小值,都有一个它的「替代品numbers[pivot],因此我们可以忽略二分查找区间的右端点。

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

三、代码

class Solution {public:int minArray(vector<int>& numbers) {int low = 0;int high = numbers.size() - 1;while (low < high) {int pivot = low + (high - low) / 2;if (numbers[pivot] < numbers[high]) {high = pivot;}else if (numbers[pivot] > numbers[high]) {low = pivot + 1;}else {high -= 1;}}return numbers[low];}
};

四、问题描述:寻找旋转排序数组中的最小值

五、分析:二分搜索

一种暴力的解法是搜索整个数组,找到其中的最小元素,这样的时间复杂度是 O(N)其中 N 是给定数组的大小。

  • 一个非常棒的解决该问题的办法是使用二分搜索。在二分搜索中,我们找到区间的中间点并根据某些条件决定去区间左半部分还是右半部分搜索。

  • 由于给定的数组是有序的,我们就可以使用二分搜索。然而,数组被旋转了,所以简单的使用二分搜索并不可行。

  • 在这个问题中,我们使用一种改进的二分搜索,判断条件与标准的二分搜索有些不同。

  • 我们希望找到旋转排序数组的最小值,如果数组没有被旋转呢?如何检验这一点呢?

  • 如果数组没有被旋转,是升序排列,就满足 last element > first element。

  • 上图例子中 7>2 。说明数组仍然是有序的,没有被旋转。

  • 上面的例子中 3 < 4,因此数组旋转过了。这是因为原先的数组为 [2, 3, 4, 5, 6, 7],通过旋转较小的元素 [2, 3] 移到了后面,也就是 [4, 5, 6, 7, 2, 3]。因此旋转数组中第一个元素 [4] 变得比最后一个元素大

  • 这意味着在数组中你会发现一个变化的点,这个点会帮助我们解决这个问题,我们称其为变化点。

  • 在这个改进版本的二分搜索算法中,我们需要找到这个点。下面是关于变化点的特点:

  • 所有变化点左侧元素 > 数组第一个元素

  • 所有变化点右侧元素 < 数组第一个元素

  • 算法

  • 找到数组的中间元素 mid。

  • 如果中间元素 > 数组第一个元素,我们需要在 mid 右边搜索变化点。

  • 如果中间元素 < 数组第一个元素,我们需要在 mid 做边搜索变化点。

  • 上面的例子中,中间元素 6 比第一个元素 4 大,因此在中间点右侧继续搜索。

  • 当我们找到变化点时停止搜索,当以下条件满足任意一个即可:

  • nums[mid] > nums[mid + 1],因此 mid+1 是最小值

  • nums[mid - 1] > nums[mid],因此 mid 是最小值

  • 在上面的例子中,标记左右区间端点。中间元素为 2,之后的元素是 7 满足 7 > 2 也就是 nums[mid - 1] > nums[mid]。因此找到变化点也就是最小元素为 2。

六、代码

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

七、问题描述:寻找旋转排序数组中的最小值 II

八、分析:二分查找

  • 一个包含重复元素的升序数组在经过旋转之后,可以得到下面可视化的折线图:

  • 其中横轴表示数组元素的下标,纵轴表示数组元素的值。图中标出了最小值的位置,是我们需要旋转的目标。

  • 我们考虑数组中的最后一个元素 x:在最小值右侧的元素,它们的值一定都小于等于 x;而在最小值左侧的元素,它们的值一定都大于等于 x。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。

  • 在二分查找的每一步中,左边界为low,右边界为high,区间的中点为pivot,最小值就在该区间内。

  • 我们将中轴元素nums[pivot] 与右边界元素nums[high] 进行比较,可能会有以下的三种情况:

  • 第一种情况是nums[pivot]<nums[high]。

如下图所示,这说明nums[pivot] 是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。

  • 第二种情况是nums[pivot]>nums[high]。

如下图所示,这说明 nums[pivot] 是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。

  • 第三种情况是 nums[pivot]==nums[high]。

如下图所示,由于重复元素的存在,我们并不能确定nums[pivot] 究竟在最小值的左侧还是右侧,因此我们不能莽撞地忽略某一部分的元素。

我们唯一可以知道的是,由于它们的值相同,所以无论 nums[high] 是不是最小值,都有一个它的「替代品」nums[pivot],因此我们可以忽略二分查找区间的右端点。

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

九、代码

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

十、问题描述:搜索旋转排序数组

十一、分析:二分搜索

题目要求算法时间复杂度必须是 O(logn)O(logn)O(logn) 的级别,这提示我们可以使用二分搜索的方法。

  • 但是数组本身不是有序的,进行旋转后只保证了数组的局部是有序的,这还能进行二分搜索吗?答案是可以的。
  • 可以发现的是,我们将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。
  • 拿示例来看,我们从 6 这个位置分开以后数组变成了 [4, 5, 6] 和 [7, 0, 1, 2] 两个部分,其中左边 [4, 5, 6]这个部分的数组是有序的,其他也是如此。
  • 这启示我们可以在常规二分搜索的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r]
  • 看哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分搜索的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:
  • 如果 [l, mid - 1] 是有序数组,且 target 的大小满足 [nums[l],nums[mid]),则我们应该将搜索范围缩小至 [l, mid - 1],否则在 [mid + 1, r] 中寻找。
  • 如果 [mid, r] 是有序数组,且 target 的大小满足(nums[mid+1],nums[r]],则我们应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找。

  • 需要注意的是,二分的写法有很多种,所以在判断 target 大小与有序部分的关系的时候可能会出现细节上的差别。

十二、代码

class Solution {public:int search(vector<int>& nums, int target) {int n = (int)nums.size();if (!n) return -1;if (n == 1) return nums[0] == target ? 0 : -1;//左右边界int l = 0, r = n - 1;while (l <= r) {//中间的值int mid = (l + r) >> 1;//相等直接返回if (nums[mid] == target) return mid;//代表【l,mid】是有序的if (nums[0] <= nums[mid]) {//如果目标值target满足【nums[0],nums[mid])代表在//左半区间查找if (nums[0] <= target && target < nums[mid]) {r = mid - 1;} //反之右半区间查找else {l = mid + 1;}} //代表(mid,r】是有序的else {//在右半区间查找if (nums[mid] < target && target <= nums[n - 1]) {l = mid + 1;} //在左半区间查找else {r = mid - 1;}}}return -1;}
};

十三、问题描述:搜索旋转排序数组II

十四、代码

class Solution {public:bool search(vector<int>& nums, int target) {int left = 0,right = nums.size() - 1;while(left <= right){while(left != right && nums[left] == nums[right]) right--; //无重复值的解法中添加这行int mid = (left + right) / 2;if(nums[mid] == target) return true;else if(nums[mid] > target){if(nums[mid] > nums[right] && target < nums[left])   left = mid + 1;else right = mid - 1;}else{if(nums[mid] < nums[left] && target > nums[right]) right=mid-1;else left = mid + 1;}}return false;}
};

旋转排序数组系列题详解相关推荐

  1. Rotated Sorted Array旋转排序数组相关题

    1.什么是Rotated Sorted Array 在leetcode相关题目中,对Rotated Sorted Array相关的定义为: 整数数组 nums 按升序排列,数组中的值互不相同 . 在传 ...

  2. 贪心算法及Jump Game系列题详解

    本博文所有的代码均可在 https://github.com/Hongze-Wang/LeetCode_Java https://github.com/Hongze-Wang/LeetCode_Pyt ...

  3. LeetCode (二分小专题)33搜索旋转排序数组34在排序数组中查找元素的第一个和最后一个位置35搜索插入位置

    前言 国庆前最后一次打卡,国庆后继续开启,公众号bigsai回复进群欢迎加入打卡,如有帮助记得点赞收藏. 近期打卡记录: LeetCode 32最长有效括号(困难) (本周) LeetCode 30串 ...

  4. 满满干货:二分查找/排序 编程题详解

    铁汁们~今天给大家分享一篇有关二分查找/排序 编程题详解(牛客网),满满干货,来吧,开造⛳️ 先给大家说些小知识点: 1.指针变量名[整数]=*(指针变量名+整数): 2.知识点:双指针 双指针指的是 ...

  5. LeetCode高频题33. 搜索旋转排序数组

    LeetCode高频题33. 搜索旋转排序数组 提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目 互联网大厂们在公司养了一大批A ...

  6. 《LeetCode力扣练习》第33题 搜索旋转排序数组 Java

    <LeetCode力扣练习>第33题 搜索旋转排序数组 Java 一.资源 题目: 整数数组 nums 按升序排列,数组中的值 互不相同 . 在传递给函数之前,nums 在预先未知的某个下 ...

  7. [Leedcode][JAVA][第33题][搜索旋转排序数组]

    [问题描述][33. 搜索旋转排序数组] [中等] 假设按照升序排序的数组在预先未知的某个点上进行了旋转.( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ). ...

  8. ​LeetCode刷题实战81:搜索旋转排序数组 II

    算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试.所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 ! 今天和大家 ...

  9. 剑指offer(1-10题)详解

    文章目录 01二维数组的查找 02替换空格 03从尾到头打印链表 04重建二叉树★ 05 用两个栈实现队列 06旋转数组的最小数字 07 斐波那契数列 08 跳台阶 09 变态跳台阶★ 10 矩阵覆盖 ...

最新文章

  1. vs2008 C++ 没有找到MSVCR90D.dll 问题
  2. Java 8 的 JVM 有多快?Fork-Join 性能基准测试
  3. 学习Cassandra资料的一些整理
  4. 干货三:CountDownTimer倒计时工具类
  5. 关于OPC自动化接口编程(OPCDAAuto.dll)几点注意问题
  6. maven package自己主动部署包
  7. c/s 自动升级(WebService)
  8. NYOJ15-括号匹配(二)-区间DP
  9. BZOJ4562: [Haoi2016]食物链
  10. java http服务_springboot官方例子中文翻译--RESTful服务启用CORS支持
  11. SitePoint播客#160:坐在树上的Adobe和HTML
  12. 博科光纤交换机默认密码更改
  13. Android aab打包报错(持续更新中~),android插件开发过时
  14. 工装夹具检具治具机械设计机构solidworks模型3d课程图纸sw
  15. Codeforces Round 649 (Rated for Div. 2)D. Ehab s Last Corollary详细题解(图论+简单环)
  16. 【关于memset和0x3f3f3f3f】
  17. Apple Watch 关闭显示正在听的音乐
  18. php webmail,构建简单的Webmail系统
  19. Web前端HTML5CSS3初学者零基础入门——第三天
  20. 于氏:简单监控全网服务

热门文章

  1. 200804C阶段一变量生存期和结构体
  2. 什么是Session分布式共享
  3. 复现强网杯python is the best language 2
  4. 命令行开启windows下的iis信息服务,开启及配置http端口,开启及配置ftp端口
  5. 不要“个人英雄主义”,物联网安全共同体更稳固
  6. Android加载大图、多图解决方案
  7. php开启与关闭错误提示
  8. raw_input 与 input的区别
  9. tourist取模模板
  10. CodeForces - 1220D Alex and Julian(思维+数论)