从“寻找旋转排序数组中的最小值”谈二分法

二分法查找

常规的二分法查找代码实例如下

复制
public class BinaryResearchII { public static int BR(int[] nums,int val){ int left=0,right=nums.length,mid; while(left+1<right){//只有在只剩下两个元素的时候,才会出现mid值和start值相同,且mid不动的情况,所以此时跳出 mid=left+(right-left)/2;//此步重要,意义同(left+right/2),但是可以防止溢出 if(nums[mid] == val){ right=mid;//因为我们要找的是第一个val,所以right=mid,如果我们要找最后一个val,则用left=mid. } else if(nums[mid]<val){ left=mid; } else if(nums[mid] > val){ right=mid; } } if(nums[left] == val){//要第一个,所以先比较left,若是要最后一个val,则先比较right return left; } if(nums[right] == val){ return right; } return -1; } public static void main(String[] args) { // TODO Auto-generated method stub int []nums={1,2,34,35}; System.out.println(BR(nums,35)); } }

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

在做leetcode时遇到了这一问题,自己对于二分法查找问题的理解一直都不是很清晰,尤其是关于循环跳出判断条件部分,所以借此整理一下。

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

题目

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。

复制
示例 1: 输入: [3,4,5,1,2] 输出: 1
复制
示例 2: 输入: [4,5,6,7,0,1,2] 输出: 0

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array

解答代码

复制
public int findMin(int[] nums) { int start = 0,end = nums.length - 1; int mid; while (end - start > 1){//间距为2时退出 mid = (end - start) / 2 + start;//防溢出 if(nums[mid] > nums[end]){//因为题目允许移动长度为0,与右端比较 start = mid + 1; } else { end = mid; } } return nums[start] < nums[end] ? nums[start] : nums[end];//因为跳出循环条件为end -start > 1,所以要比较下谁大谁小 }

批注

从移动规则可以看出,数组中包含两个递增序列,由于存在左边的递增序列大于右边的递增序列的特性,因此可以用中间点和左右两个端点的比较的方式确定中间(mid)节点是属于那个序列的,而最小值一定位于第二个递增序列,从而一次缩小一半的检索范围,这也正是二分法的思路

自己对于二分法一只比较迷糊的是关于二分法循环结束条件以及start = mid + 1/start = mid选择的问题,其实这二者又是同一个问题,在这我尝试进行一下梳理。

start = mid + 1若导致start超过数组范围那就必然会使得start > end,而我们的循环在start=end时就跳出了,因此整个程序都不会出现数组越界的问题。

end - start > 1的判定又避免了当判定范围只剩下两个元素时mid使用start = mid(上一解答代码中start = mid + 1或者start = mid都对)时mid不移动的问题,只需最后函数返回值时比较下两个值大小返回小的即可。

还有一点就是当循环条件设为end>start时,函数return end or start?答案是返回谁都可以,因为循环结束条件为start == end

此外,我们还需要注意到不移动的情况,因此,我们选择与右端点进行比较,从而在不移动的时候也不会错误判断所处前后序列(与右端点比较在不移动的情况下默认处于后序列,与左端点比较默认处于前序列)。
综上,循环结束条件为end - start > 1,
错误代码实例——死循环

复制
public int findMin(int[] nums) { int start = 0,end = nums.length - 1; int mid; while (end - start > 0){ mid = (end - start) / 2 + start; if(nums[mid] > nums[end]){ start = mid;//此处结合判定条件end - start > 0存在mid不移动问题,应改为start = mid + 1 } else { end = mid; } } return nums[start];//因为循环是当start = end时跳出,所以返回start或者end都是一样的 }

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

题目

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
注意数组中可能存在重复的元素。

复制
示例 1: 输入: [1,3,5] 输出: 1
复制
输入: [2,2,2,0,1] 输出: 0

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii

解答代码

复制
public int findMin_repetition(int[] nums) { int start = 0,end = nums.length - 1; int mid; while (start < end && end - start > 1){ mid = (end - start) / 2 + start; if(nums[mid] > nums[end]){ start = mid + 1; } else if(nums[mid] < nums[end]){ end = mid; } else { end--;//如遇到相等情况,则左移,去除一个重复项,但即使有重复项,大于小于情况下的前后序列规律还是一样的。 } } return Math.min(nums[start],nums[end]); }

批注

本题的总体思路同上题,作为上题的延伸,再有了重复项之后,要考虑的就是如遇到重复项,由于即使有重复项,大于小于情况下的前后序列规律还是一样的,因此如遇到相等情况,则左移右端点,去除当前这一个重复项即可。

结语

本文主要是通过这两道题梳理了一直以来比较迷糊的二分法循环跳出条件,每次循环start/end的赋值和函数返回值这三个问题,当然这三个问题的答案也是互相关联的,总结如下表。

循环判断条件 查找范围赋值 函数返回值
start < end start = mid + 1 return num[start]/num[end]
start + 1 < end start = mid/start = mid + 1 return min(num[start],num[end])
posted @   wunsiang  阅读(177)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示