剑指offer面试题11(java版):旋转数组的最小数字

welcome to my blog

剑指offer面试题11(java版):旋转数组的最小数字

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

第四次做; 核心: 为什么要和右部分比? 因为目标值处于右部分; 这道题就是LeetCode 154题; 之所以不能让mid跟左边界比较的原因, 针对案例{10,1,10,10,10}, 如果mid和左边界比较, 那么left++后, left指向元素1, 下一轮循环时mid会认为自己在左部分, 而实际上mid此时已经在右部分了, 所以mid会错误把自己往右部分移动; 针对案例{10,10,10,1,10}, 如果mid和右边界比较, 那么right–后, right指向元素1, 下一轮循环时mid会认为自己在左部分, 这是正确的!
双指针; 左指针和右指针最后指向同一个元素, 也就是指向旋转数组的最小数字
核心: 和右边界比较; 最终要定位的元素: 右部分的第一个
要清楚, 左部分整体大于等于右部分
核心: 为什么mid和right比? 因为目标在右部分, 所以和处于右部分中的right比
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array==null || array.length==0)
            return 0;
        int n = array.length;
        //不能取等于, 因为:{10,1,10,10,10};   {10,10,10,1,10}
        if(array[0] < array[n-1])
            return array[0];
        //双指针; 左指针和右指针最后指向同一个元素, 也就是指向旋转数组的最小数字
        //核心: 和右边界比较; 最终要定位的元素: 右部分的第一个
        //要清楚, 左部分整体大于等于右部分
        //核心: 为什么mid和right比? 因为目标在右部分, 所以和处于右部分中的right比
        int left=0, right=n-1, mid;
        while(left < right){
            mid = left + ((right-left)>>1);
            //mid在左部分
            if(array[mid] > array[right])
                left = mid+1;
            //mid在右部分
            else if(array[mid] < array[right])
                right = mid;
            //不知道在哪个部分
            else{
                right--;
            }
        }
        return array[left];
    }
}
第三次做, 二分查找, 二分到死; 这次做错了! 这次做错了! 这次做错了! mid得和right比, 下面的方法不能通过案例{10,1,10,10,10}
/*
使用双指针, left位于前部末尾, right位于后部开头
使用二分法更新指针
二分到死
*/
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array==null || array.length==0)
            return 0;
        
        int left=0, right = array.length-1 , mid;
        while(left<=right){
            mid = left + ((right-left)>>1);
            //mid在左部分
            if(array[mid] > array[left])
                left = mid;
            //mid在右部分
            else if(array[mid] < array[left])
                right = mid;
            //不知道mid在哪部分, 左边界缩减1
            else
                left++;
        }
        return array[right];
    }
}
第二次做
  • 四处需要注意:
  • 左右指针的含义很关键
  • 只需要将arr[mid]和arr[right]进行比较,没用到arr[left]; 所以这道题不同于同时与两个数比较的
  • 循环条件很关键
  • 处理arr[mid]==arr[right]时很关键
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length==0) return 0;
        int left=0, right=array.length-1, mid;
        //还是用剑指上的思路更好一些,起初left指向左部分, right指向右部分
        //二分到最后: left最终指向左部分的末尾,right最终指向右部分的开头
        //二分到死的结果是left与right相邻,也就是left+1 == right
        //所以达不到这个条件时要继续二分
        while(left + 1 < right){ 
            mid = left + ((right-left)>>1);
            //理解各个分支的关键在于时刻明确:left最终指向左部分, right最终指向右部分
            if(array[mid] < array[right]) 
                right = mid;
            else if(array[mid] > array[right]) 
                left = mid;
            else{ //arr[mid] == arr[right]  相等时不知道最小值在左部分还是右部分
                right = right - 1;
            }
        }
        return array[right]; 
    }
}
第二次做
import java.util.ArrayList;
public class Solution {
    /*
    这道题让我想起了之前的两道题, 同一个数会和两个数进行比较, 比较后会出现多种情况
    比如,a分别和b,c进行比较, 结果可能是a>b,a<b;   a>c,a<c  此处暂未考虑等于
    如何巧妙使用if, else if, else是关键
    
    经分析知,如果数组分为左右两部分, 且左右两部分都是非递减的. 那么最小值大概率在右边这部分; 除非数组没有旋转,或者说整体一起旋转了
    此时主要考虑arr[mid]和arr[right]的大小关系
    
    使用二分法的思想,但不像平常遇到的二分法的题
    arr[mid] > arr[left], 说明arr[mid]在其中一个递增子序列中, left = mid + 1
    arr[mid] < arr[right], 说明arr[mid]在另一个递增子序列中, right = mid - 1
    
    arr[mid] < arr[left], right = mid - 1
    arr[mid] > arr[right], left = mid + 1
    
    arr[mid] == arr[left] && arr[mid] == arr[right]
    */
    public int minNumberInRotateArray(int [] array) {
        if(array.length==0) return 0;
        int left=0, right=array.length-1, mid;
        while(left<=right){
            mid = left + ((right-left)>>1);
            //各个条件下的语句都表明我们希望在右部分进行探索
            if(array[mid] < array[right]) //arr[mid]在右部分,且mid可能就是最终的结果,但还是需要以mid为新right, 二分到死
                right = mid;
            else if(array[mid] > array[right]) //arr[mid]在左部分
                left = mid + 1;
            else{ //arr[mid] == arr[right]  相等时不知道最小值在左部分还是右部分
                right = right - 1; //这一句能让我们跳出循环
            }
        }
        return array[left]; //最终要返回array[left]而不是array[right]
    }
}

思路

  • 见注释

复杂度

  • 时间复杂度: 因为采用的是二分查找,所以时间复杂度是O(logn)
  • 空间复杂度: O(1)
import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        // 健壮性判断
        if(array == null)
            return 0; //题目要求
        // 正式执行
        /*
        准备: 使用二分查找的思路, 设置两个指针p1,p2分别指向数组的起始和末尾, 还有一个mid指针指向p1,p2的中间位置
        1. p1永远指向第一个递增子序列中的元素, p2永远指向第二个递增子序列中的元素, 二分查找到最后:p1指向最大值, p2指向最小值
        2. 如果arr[mid] >= arr[p1] 说明mid在第一个递增子序列中, 令p1 = mid
           如果arr[mid] <= arr[p2] 说明mid在第二个递增子序列中, 令p2 = mid
           特例1:旋转了0个元素到末尾,此时arr[0]就是最小值
           特例2:arr[p1] == arr[p2] == arr[mid], 此时无法判断最小值位于哪个递增子序列中,只能用顺序查找
        */
        int p1 = 0;
        int p2 = array.length - 1;
        int mid = 0;
        // 特例1,同时也能解决数组只有1个元素的情况
        if(array[0]< array[array.length-1]) // 判断条件不能是<=
            return array[0];
        // 普通情况
        while(array[p1] >= array[p2]){ //while条件写什么好?
            if(p2-p1==1){
                mid = p2;
                break;
            }
            mid = (p1 + p2) / 2;
            if(array[mid] >= array[p1])
                p1 = mid;
            if(array[mid] <= array[p2])
                p2 = mid;
            // 特例2
            if(array[p1] == array[p2] && array[p1] == array[mid])
               return findByOrder(array, p1, p2);
        }
        return array[mid];
    }
    public int findByOrder(int[] array, int p1, int p2){
        int min = array[p1];
        for(int i=p1; i<=p2; i++){
            if(min > array[i])
                min = array[i];
        }
        return min;
    }
}
看看队列的toString()是什么样子

进队顺序为1,2,3,4,5

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length==0) return 0;
        //二分法去找最小值
        //最小值的特点:小于左边,小于等于右边
        //将array[mid]和它相邻的两个元素作比较没有用, 比较结果没有指导意义
        int left=0, right=array.length-1, mid;
        while(left <= right){
            mid = left+((right-left)>>1);
            if(array[mid] >= array[mid-1])
                left = mid + 1; //越界判断怎么加?
            else if(array[mid] > array[mid+1])
                left = mid + 1;
            else
                return array[mid];
        }
        return array[0];
        
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值