二分查找解决旋转数组

Posted 算法丹青

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找解决旋转数组相关的知识,希望对你有一定的参考价值。



二分查找用到很多大于小于号, 每次都得判断是否需要加等于, 着实费劲..



题目很容易理解, 也可以快速想出思路:


因为数组旋转前是有序的, 所以旋转后会保持一定的有序性, 可以对有序部分进行二分查找或缩小查找查找范围. 


举个例子:

如果原数组为: 2  5  6  7  0  1 . 数组最左端的值为2, 第一轮num[mid]值为6, 下标从0到 mid 所对应的值有序, 

如果num[start]<=target<num[mid], 就去mid前半部分继续找, 否则去后半部分找.


如果原数组为: 2  5  6  0  1  2 . 数组最左端的值为2, 第一轮num[mid]值为0, 下标从 mid 到 last-1 所对应的值有序, 

如果num[mid]<target<=num[last-1], 就去mid前半部分继续找, 否则去后半部分找.


如果是 1  1  1  1  0  1  1 . num[start] == num[mid], 

那直接让 start+1, 去掉这个干扰项, 可以保证不影响旋转数组原来的有序性质. 


代码1:

public: bool search(vector<int>& nums, int target) { int size = nums.size(); int first = 0;  int last = size;
while(first < last){ int mid = first + (last - first)/2; if(nums[mid] == target){ return true;            }             if(nums[first] < nums[mid]){ if(nums[first]<= target && target < nums[mid]) last = mid; else first = mid+1;
}else if(nums[first] > nums[mid]){
if(nums[mid] < target && target <= nums[last-1] ) first = mid+1; else last = mid;
} else if(nums[first] == nums[mid]){ first++; }
} return false;
}


这里的难点是判断哪里需要加"="号, 

根据上述分析可以先写出代码结构


public: bool search(vector<int>& nums, int target) {        // ... 省略 while(first < last){ int mid = first + (last - first)/2; if(nums[mid] == target){ return true;            } if(nums[first] < nums[mid]){                // ??? block1            }else if(nums[first] > nums[mid]){                // ??? block2 } else if(nums[first] == nums[mid]){ first++;            } } return false;
}


首先可以先构想一个示例, 

2  5  6  7  8  0  1

mid 第一次代表的值为7, 如果 target 落在了 2 和 7 之间, 那就可以把搜索区间缩小到 first 到 mid 部分, 否则就缩小到 mid+1 和 last 之间 (左开右闭原则)


所以 block1 部分的代码:

if(nums[first] <= target && target < nums[mid]) last = mid;else first = mid+1;


为什么 nums[first] <= target 要加等于号, 而 target < nums[mid]) 不加? 


因为如果 target 落在 mid 的左半部分时, nums[first] 这个数字一定会参与到下一次的二分查找, 必须保证包含nums[first]这个数字, 所以nums[first] <= target 要加等于号. 


而 target 是否等于 nums[mid] 这个条件已经在

if(nums[mid] == target){  return true;  }

判断过了, 所以不用判断 target 是否等于 nums[mid] .



再构想一个示例, 

2  5  0  1  1  2  2


mid 第一次代表的值为1, 如果 target 落在了 1 和 2 之间, 那就可以把搜索区间缩小到 mid+1 到 last 部分, 否则就缩小到 first 和 mid 之间.


所以 block2 部分的代码:

if(nums[mid] < target && target <= nums[last-1] ) first = mid+1; else last = mid;


为什么 target <= nums[last-1] 要加等于号, 而 nums[mid] < target 不加? 


因为如果 target 落在 mid 的右半部分时, nums[last-1] 这个数字一定会参与到下一次的二分查找, 必须保证包含nums[last-1]这个数字, 所以nums[first] <= target 要加等于号. 


 target 是否等于 nums[mid] 这个条件如上所述, 已存在, 不用判断.


=======================


按理说到这里可以结束了, 已经写出了一份标准代码. 

但是如果第一层思路稍有不同, 也可以写出一份效果完全一样的代码.


如果写出框架时不是比较 nums[first] 和 nums[mid] 的关系, 

而是比较 nums[first] 和 nums[mid] 的关系, 写出如下框架: 

 if(nums[mid] < nums[last-1]){               // ???     }else if(nums[mid] > nums[last-1]){  // ???  }else if(nums[mid] == nums[last-1]){               // ???  }


判断思路与上同理, 可以自行试写.






完整代码如下:


public: bool search(vector<int>& nums, int target) { int size = nums.size(); int first = 0; int last = size;
while(first < last){ int mid = first + (last-first) /2 ; if(nums[mid] == target){ return true; } if(nums[mid] < nums[last-1]){ if(nums[mid] < target && target <= nums[last-1]){ first = mid + 1; }else{ last = mid; } }else if(nums[mid] > nums[last-1]){ if(nums[first] <= target && target < nums[mid]){ last = mid; }else{ first = mid + 1; } }else if(nums[mid] == nums[last-1]){ last--; } } return false;
}


以上是关于二分查找解决旋转数组的主要内容,如果未能解决你的问题,请参考以下文章

漫画:“旋转数组”中的二分查找

漫画算法:“旋转数组”中的二分查找

二分查找团灭力扣旋转排序数组系列

面试:谁说的无序不能用二分查找

剑指 Offer 11. 旋转数组的最小数字暴力二分查找

旋转数组最小值二分查找法