《二分法之旋转有序数组》

   二分查找实际上就是根据有序得条件进行边界收缩从而到 O(logn) 复杂度的搜索算法。二分法说简单也并不简单,Knuth 大佬(KMP 算法发明者)说过:Although the basic idea of binary search is comparatively straightforward,the details can be surprisingly tricky…,思路很简单,但是细节是魔鬼。所以对于旋转数组搜索问题更能够体现你对二分法的魔鬼细节了解多少。本文给出二分法的三种模板,寻中,寻左,寻右,以及对应LeetCode上五道题目的解法。本文给出的解法统一了:l <= r,先判断左区间是否为升序。

Key Words:二分法、旋转数组

Beijing, 2021.01

作者:RaySue

Agile Pioneer  

文章目录

  • 标准二分法的三种形式
    • 寻中
    • 寻左
    • 寻右
  • 旋转排序数组
    • 旋转排序数组二分搜索 target
    • 旋转排序数组 搜索最小值
    • 参考

标准二分法的三种形式

有序数组中查找一个特定元素 target 的过程如下:

  • 若 target == nums[mid],直接返回

  • 若 target < nums[mid],则 target 位于左侧区间 [left,mid) 中。令 right = mid - 1,在左侧区间查找

  • 若 target > nums[mid],则 target 位于右侧区间 (mid,right] 中。令 left = mid + 1,在右侧区间查找

  如果寻找的有序数组中有重复元素,可能有多个 target ,那么二分法应该取哪个值呢?通过对二分法的简单调整就可以实现这个功能,下面的代码为了方便理解,所以在寻左和寻右的时候就没有简写,实际用的时候可以将其中的两个分支合并即可。

C++ 代码模板

寻中

搜索所有target的中间元素位置

int l = 0, r = nums.size() - 1, mid;
while(l <= r)
{mid = l + (r - l >> 1);if (nums[mid] < target) {l = mid + 1;} else if (nums[mid] > target){r = mid - 1;} else {return mid;}
}
return -1;

寻左

寻找所有target位于最左边的元素位置,注意要判断结果的左边界是否右溢出

int l = 0, r = nums.size() - 1, mid;
while(l <= r)
{mid = l + (r - l >> 1);if (nums[mid] < target) {l = mid + 1;} else if (nums[mid] > target){r = mid - 1;} else {r = mid - 1;}
}
if (l >= nums.size() || nums[l] != target)return -1;

寻右

寻找所有target位于最右边的元素位置,注意要判断结果的右边界是否左溢出

int l = 0, r = nums.size() - 1, mid;
while(l <= r)
{mid = l + (r - l >> 1);if (nums[mid] < target) {l = mid + 1;} else if (nums[mid] > target){r = mid - 1;} else {l = mid + 1;}
}
if (r < 0 || nums[r] != target)return -1;

旋转排序数组

   前面回顾了二分法一般情况的模板,我们的主角旋转排序数组登场了,这里提到的旋转排序数组就是对一个排序数组,进行了一次旋转即在某个位置进行了断开,然后把后部分放到前面,如下:

  • 旋转前:[1,2,3,4,5,6]

  • 旋转后:[4,5,1,2,3,4]

  • 标准的排序数组使用二分法搜索target示意图,绿色是左边界,红色是右边界,橙色是mid,星星是 target:

  • 旋转排序数组使用二分法搜索target示意图,左右两部分都是升序的,且左半部分大于右半部分:

旋转排序数组二分查找有四种:

  • 搜索特定值

    • 不包含重复元素的旋转排序数组
    • 包含重复元素的旋转排序数组
  • 搜索最小值

    • 不包含重复元素的旋转排序数组
    • 包含重复元素的旋转排序数组

旋转排序数组二分搜索 target

旋转排序数组二分搜索 target 的大致思路:

  • 若 target == nums[mid],直接返回

  • 若 nums[left] <= nums[mid],说明左侧区间 [left,mid]「连续递增」。此时:若 nums[left] <= target < nums[mid],说明 target 位于左侧。令 right = mid-1,在左侧区间查找,否则,令 left = mid+1,在右侧区间查找

  • 否则,说明右侧区间 [mid,right]「连续递增」。此时:若 nums[mid] < target <= nums[right],说明 target 位于右侧区间。令 left = mid+1,在右侧区间查找,否则,令 right = mid-1,在左侧区间查找

注意:区间收缩时不包含 mid,也就是说,实际收缩后的区间是 [left,mid) 或者 (mid,right],因为mid在最初已经判断过了,每次你更新左右边界是否用 mid 加减 1的时候只需考虑一下 mid 是否被判断过,不要漏掉情况。

  • 不包含重复元素的旋转排序数组 搜索特定值
int search(vector<int> &nums, int target)
{int l = 0, r = nums.size() - 1, mid;while(l <= r){mid = l + (r - l >> 1);if (nums[mid] == target) return mid;else if (nums[l] <= nums[mid]){// 这里一定是 <= 否则就会漏掉情况if (nums[l] <= target && target < nums[mid]){r = mid - 1;} else {l = mid + 1;}} else{//                    这里也一定是 <= 否则漏情况if (nums[mid] < target && target <= nums[r]){l = mid + 1;} else {r = mid - 1;}}}return -1;
}
  • 包含重复元素的旋转排序数组 搜索特定值

   对于包含重复的情况,我们就不能考虑[left,mid] 这个区间了,因为当 nums[left] == nums[mid] 的时候,我们是不知道要收缩左边界还是右边界的,这时候就退化为了线性查找,这在有重复元素nums[left] <= nums[mid] 拆开为 nums[left] < nums[mid] 即 nums[left] == nums[mid] 分开处理就行了。

bool search(vector<int> &nums, int target)
{int l = 0, r = nums.size() - 1, mid;while(l <= r){mid = l + (r - l >> 1);if (nums[mid] == target) return true;else if (nums[l] < nums[mid]){if (nums[l] <= target && target < nums[mid]){r = mid - 1;    } else {l = mid + 1;}} else if (nums[l] > nums[mid]){if (nums[mid] < target && target <= nums[r]){l = mid + 1;} else {r = mid - 1;}} else {// 这里提供线性查找的三种方式// 方式一nums[l] == nums[mid] ? l++: r--;// 方式二 判断 [l,mid] 区间//for (int i = l; i <= mid; ++i)//{//    if (nums[i] == target) return true;//}//l = mid + 1;// 方式三 判断 [mid,r] 区间//for (int i = mid; i <= r; ++i)//{//    if (nums[i] == target) return true;//}//r = mid - 1;// 方式四 直接判断 [l, r] 区间}}return false;
}

旋转排序数组 搜索最小值

  • 不包含重复元素的旋转排序数组 搜索最小值

旋转排序数组二分搜索 最小值 的大致思路:

  • 如果 nums[l] <= nums[r],说明nums的[l, r]区间是单调递增的,返回nums[l]即可,考虑如果给定的nums压根就没旋转,那么结果肯定是对的。

  • 如果 nums[l] <= nums[mid],说明nums的[l, r]区间是单调递增的,所以最小值肯定在 mid 右边,而且mid必然不是最小值,所以 l = mid + 1;

  • 如果 nums[l] > nums[mid],说明 mid 在右半部分,即[mid, r]区间是单调递增的,所以最小值肯定在 mid 或 mid 左边,注意,此时 mid 可能就是最小值,所以 r = mid;

int findMin(vector<int> &nums)
{int l = 0, r = nums.size() - 1, mid;while(l <= r){mid = l + (r - l >> 1);if (nums[l] <= nums[r]) return nums[l];else if (nums[l] <= nums[mid]){l = mid + 1;} else {r = mid;}}return -1;
}
  • 包含重复元素的旋转排序数组 搜索最小值

   对于包含重复的情况,我们就无法通过nums[l] <= nums[r]就认为nums[l]就是最小值, 举个例子[2,2,1,2,2,2],显然此时的 nums[l] 就不是最小值。我的做法是在各个分支都计算更新一下最小值,比较好理解。

int findMin(vector<int> &nums)
{if (nums.empty()) return -1;if (nums.size() == 1) return nums.back();int minVal = nums[0];int l = 0, r = nums.size() - 1, mid;while (l <= r){mid = l + (r - l >> 1);if (nums[l] < nums[mid]){minVal = min(minVal, nums[l]);l = mid + 1;} else if (nums[l] > nums[mid]){minVal = min(minVal, nums[mid]);r = mid - 1;} else{minVal = min(minVal, nums[mid]);nums[l] == nums[mid]? l++ : r--;}}return minVal;
}

参考

[1] https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/yi-wen-jie-jue-4-dao-sou-suo-xuan-zhuan-pai-xu-s-2/

[2] https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/er-fen-cha-zhao-xiang-jie

PS: 读完本文你可以立马去LeetCode解决一下五个问题:

  1. 33.搜索旋转排序数组
  2. 81.搜索旋转排序数组II
  3. 153.寻找旋转排序数组中的最小值
  4. 153.寻找旋转排序数组中的最小值II
  5. 剑指 Offer 11.旋转数组的最小数字

二分法之旋转有序数组相关推荐

  1. LeetCode 153. Find Minimum in Rotated Sorted Array (在旋转有序数组中找到最小值)

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  2. 旋转有序数组的二分查找

    http://hi.baidu.com/nicker2010/item/4d4f71145532a234b83180a7 总结规律,简化模型 题目都不难,重要的是很敏锐的发现问题的规律. 旋转有序数组 ...

  3. leetcode刷题:求旋转有序数组的最小值

    题目: 分析:由题中所给的描述,不管数组如何旋转,我们的整个数组,其实总是真正有序的适用于二分查找法. 代码如下: #include <stdio.h> int findMin(int* ...

  4. LeetCode(81): 搜索旋转排序数组 II

    Medium! 题目描述: 假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] ). 编写一个函数来判断给 ...

  5. 判断给定的两个数是否是亲和数_动画演示LeetCode算法题:004-寻找两个有序数组的中位数...

    题目: 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2. 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n)). 你可以假设 nums1 和 nu ...

  6. 【LeetCode】陌陌面试-有序数组于其一个元素翻转后,判断一个数是否存在数组中,时间复杂度O(logn)

    1.有序数组,判断一个数是否存在于数组中,时间复杂度O(logn) 解题思路: 二分法,在有序数组中,提高时间复杂度的一个方法. 代码: def demo(nums, target):left,rig ...

  7. C语言二分法在一个有序数组查找数的算法(附完整源码)

    C语言二分法在一个有序数组查找数 二分法在一个有序数组查找数 C语言分治法来计算pow(x,y)完整源码(定义,实现,main函数测试) 二分法在一个有序数组查找数 给定一个排序数组和一个数字. 用户 ...

  8. 二分法:两个有序数组长度为N,找到第N、N+1大的数

    题目 两个有序数组长度为N,找到第N.N+1大的数 思路1:双指针,O(N)复杂度 简述思路: 如果当前A指针指向的数组A的内容小于B指针指向的数组B的内容,那么A指针往右移动,然后nums(当前已经 ...

  9. LeetCode--81. 搜索旋转排序数组Ⅱ(遍历法,二分法)

    搜索旋转排序数组Ⅱ(C, Python) 1. 题目描述 2. 题目分析 3. C语言实现 3.1 遍历法 3.2 二分法 4. Python语言实现 1. 题目描述 难度:中等 2. 题目分析 这道 ...

最新文章

  1. Spring Boot 集成测试
  2. IntelliJ IDEA入门教程之一
  3. Strom的trident单词计数代码
  4. 盘点66个Pandas函数,轻松搞定“数据清洗”!
  5. 如何使用SAP HANA Studio的PlanViz分析CDS view性能问题
  6. React开发(114):不建议用setstate回调
  7. hadoop--MapReduce框架原理
  8. Joe一款个人博客typecho主题(扩展版)
  9. 【Unity Shaders】Lighting Models —— 灯型号Lit Sphere
  10. 02-Http请求与响应全解
  11. Android Studio支持Java1.8的解决方案
  12. adb shell操作文件
  13. Java调用MATLAB作图是的ERROR--MWEException
  14. MQTT如何快速助你产品化
  15. 工具 | 超实用工具listary和snipaste
  16. (亲测解决)每次打开excel文件都会出现两个窗口,一个是空白的sheet1,另一个是自己的文档
  17. Linux的基础文件操作1
  18. Ubuntu16.04装拼音/Sogou遇到的问题(卸载/重装 Fcitx / Fcitx Configuration、卸载/重装Sogou)
  19. 升降压斩波电路matlab,升降压斩波电路matlab仿真
  20. 日语口语1.13 ジェトロの山本さんから部長のことをお伺いまして

热门文章

  1. Python XML解析器– ElementTree
  2. couchdb 安装_如何在Ubuntu上安装CouchDB –分步指南
  3. java 泛型示例_Java泛型示例教程–泛型方法,类,接口
  4. jquery 遍历无限极树_jQuery parent()和children()树遍历函数示例
  5. java mongodb_MongoDB Java Servlet Web应用程序示例教程
  6. Android ButterKnife示例
  7. MIT 6.828 main.c文件分析
  8. [计算机网络] - 从英雄联盟,看数据包何去何从?
  9. stringstream 字符串的格式化数据提取
  10. 传爱立信两大股东欲弹劾CEO卫翰思 股价应声反弹