关于旋转排序数组leetcode中共有4道题目,思路都是基于二分查找。
什么是旋转排序数组?
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
具体问题
找最小值和普通搜索两种
找最小值问题
1.假设数组中不存在相同元素(153题 中等)
示例:
输入: [3,4,5,1,2]
输出: 1
思路:
每次将mid和hi位置的数比较即可判断最小值所在的区间
代码:
public int findMin(int[] nums) {
int l=0,h=nums.length-1;
while (l<h){
int mid=l+(h-l)/2;
if (nums[mid]>nums[h]){
l=mid+1;
}else {
h=mid;
}
}
return nums[l];
}
2.假设数组中存在相同元素(154题 困难)
示例:
输入: [1,1,1,0,1]
输出: 0
思路:
如果数组元素允许重复的话,那么就会出现一个特殊的情况:nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。例如对于数组 {1,1,1,0,1},l、m 和 h 指向的数都为 1,此时无法知道最小数字 0 在哪个区间。
代码:
public int findMin(int[] nums) {
int l=0,h=nums.length-1;
while (l<h){
int mid=l+(h-l)/2;
if (nums[l]==nums[mid]&&nums[mid]==nums[h]){
return find(nums,l,h);//切换到顺序查找
}
else if (nums[mid]<=nums[h]){
h=mid;
}else {
l=mid+1;
}
}
return nums[l];
}
public int find(int[] nums,int l,int h){
for (int i = l; i <h ; i++) {
if (nums[i]>nums[i+1]){
return nums[i+1];
}
}
return nums[l];
}
搜索问题
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1
1.假设数组中不存在相同元素(33题 中等)
实例:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
思路:
可以基于153题,第一次二分查找找到最小值的位置(翻转点的位置)。有了最小值的索引便有个一个映射关系,mid和realmid。第二遍的二分查找就是一个常规的二分查找,但要考虑映射关系,即每次拿realmid的值比较而不是mid位置的值。
代码:
public int search(int[] nums, int target) {
int l=0,h=nums.length-1;
//用二分查找找到最小值的索引
while (l<h){
int mid=l+(h-l)/2;
if (nums[mid]<=nums[h]){
h=mid;
}else {
l=mid+1;
}
}//l=h 为最小值的索引 也就是翻转的位置
int rot=l;
l=0;
h=nums.length-1;
//第二遍二分查找 就是正常的二分查找但要考虑翻转点
while (l<=h){
int mid=l+(h-l)/2;
int realmid=(mid+rot)%nums.length; //通过映射关系找到真实的中间点
if (nums[realmid]==target){
return realmid;
}else if (nums[realmid]>target){
h=mid-1;
}else {
l=mid+1;
}
}
return -1;
}
2.假设数组中存在相同元素(81题 中等)
示例:
Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true
思路:
这题相当于在154题的基础上,
第一步,用154题的函数找到存在重复元素数组中最小值(翻转点)的位置;
第二步,参考1的做法,通过映射关系完成一次常规二分查找。
代码:
public boolean search(int[] nums, int target) {
//调用函数找到最小值的索引
int rot=findMinIndex(nums);
int l=0,h=nums.length-1;
//二分查找 就是正常的二分查找但要考虑翻转点
while (l<=h){
int mid=l+(h-l)/2;
int realmid=(mid+rot)%nums.length; //通过映射关系找到真实的中间点
if (nums[realmid]==target){
return true;
}else if (nums[realmid]>target){
h=mid-1;
}else {
l=mid+1;
}
}
return false;
}
public int find(int[] nums,int l,int h){ //顺序查找
for (int i = l; i < h; i++) {
if (nums[i]>nums[i+1]){
return i+1;
}
}
return l;
}
public int findMinIndex(int[] nums) {
int l=0,h=nums.length-1;
while (l<h){
int mid=l+(h-l)/2;
if (nums[l]==nums[mid]&&nums[mid]==nums[h]){
return find(nums,l,h);//切换到顺序查找
}
else if (nums[mid]<=nums[h]){
h=mid;
}else {
l=mid+1;
}
}
return l;
}
总结:
旋转排序数组中的问题无非就是查找问题,而排序数组中的查找问题采用二分查找效率最高,所以要尽量利用其中的顺序关系转化为二分查找来解决。
对于找最小值(旋转点)问题,又可以分为无重复元素和有重复元素;对于无重复元素找最小值问题,每次将mid位置元素和h位置元素比较;对于有重复,注意特例类似[1,1,1,0,1,1],出现特例时就要局部利用顺序查找来继续查找了,所以复杂度会由O(logN)退化到O(N)。
对于搜索问题,第一步都是解决找旋转点的问题,根据有无重复元素采用不同函数。第二步,在找到旋转点后,便有了“mid”和“realmid”的映射关系,就可以转化为普通二分查找搜索问题。