二分查找-旋转数组求target‘s index or find min

1. 旋转数组中找target的index

由于题目说数字了无重复,举个例子:
1 2 3 4 5 6 7 可以大致分为两类,
第一类 2 3 4 5 6 7 1 这种,也就是 nums[start] <= nums[mid]。此例子中就是 2 <= 5。
这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。
第二类 6 7 1 2 3 4 5 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2。
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end],则在后半部分找,否则去前半部分找。

leetcode 33:搜索旋转排序数组中的某个值target(不含重复)

比较nums[mid]和nums[right]的大小,即判断后半部分是否有序。

public int search(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return -1;
        }
        int start = 0;
        int end = nums.length - 1;

        while (start <= end){
            int mid = start + (end -start)/2;
            if (nums[mid] == target){
                return mid;
            }

            //后半部分有序
            if(nums[mid] < nums[end]){
                if(nums[mid] < target && target <= nums[end]){
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }

            } else {
                 if(nums[mid] > target && target >= nums[start]){
                    end = mid - 1;
                    
                } else {
                    start = mid + 1;
                    
                }


            }
        }
        return -1;
        
    }

也可以写成比较nums[start]和nums[mid]的大小,判断前半部分是否有序。

public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int start = 0;
        int end = nums.length - 1;
        int mid;
        while (start <= end) {
            mid = start + (end - start) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            //前半部分有序,注意此处用小于等于
            if (nums[start] <= nums[mid]) {
                //target在前半部分
                if (target >= nums[start] && target < nums[mid]) {
                    end = mid - 1;
                } else {
                    start = mid + 1;
                }
            } else {
                if (target <= nums[end] && target > nums[mid]) {
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }
            }

        }
        return -1;

    }


为什么是小于等于?其实就是为了最后只剩两个数的时候,即left=mid的时候(因为mid向靠近left的方向取整)。

leetcode 81:搜索旋转排序数组中的某个值target(含重复)

第一类
1011110111 和 1110111101 这种。此种情况下 nums[start] == nums[mid],分不清到底是前面有序还是后面有序,此时 start++ 即可。相当于去掉一个重复的干扰项。
第二类
22 33 44 55 66 77 11 这种,也就是 nums[start] < nums[mid]。此例子中就是 2 < 5;
这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。
第三类
66 77 11 22 33 44 55 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2;
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end]。则在后半部分找,否则去前半部分找。

public boolean search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int start = 0;
        int end = nums.length - 1;
        int mid;
        while (start <= end) {
            mid = start + (end - start) / 2;
            if (nums[mid] == target) {
                return true;
            }
            if (nums[start] == nums[mid]) {
                start++;
                continue;
            }
            //前半部分有序
            if (nums[start] < nums[mid]) {
                //target在前半部分
                if (nums[mid] > target && nums[start] <= target) {
                    end = mid - 1;
                } else {  //否则,去后半部分找
                    start = mid + 1;
                }
            } else {
                //后半部分有序
                //target在后半部分
                if (nums[mid] < target && nums[end] >= target) {
                    start = mid + 1;
                } else {  //否则,去后半部分找
                    end = mid - 1;

                }
            }
        }
        //一直没找到,返回false
        return false;

    }

2. 旋转数组中找最小值

找最小值,将nums[mid]和nums[right]比较,判断后半部分是否有序,最小值一定在无序的一边:
中值 < 右值,后半部分有序,所以最小值在左半边,收缩右边界。(h = mid:中值 < 右值,中值也可能是最小值,右边界只能取到mid处)
中值 > 右值,后半部分无序,最小值在右半边,收缩左边界;(l = mid + 1:因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid)

leetcode 153:Find Minimum in Rotated Sorted Array (不含重复)

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]) {//后半部分有序,不包含最小值,因为mid比h小,所以不排除mid为最小值
            h = mid;
        } else if (nums[mid] > nums[h]){//后半部分无序,包含最小值,所以更新l到mid+1,因为mid比h大,所以排除mid
            l = mid + 1;
        }
    }
    return nums[l];
}

为什么mid == h合并到mid<h了呢?
和谁合并都无所谓,因为nums[mid]不会等于nums[right]。

如果输入数组多于一个数,循环到最后,会只剩两个数,nums[left] == nums[mid],以及nums[right],这里的位置left == mid == right - 1。

如果nums[left] == nums[mid] > nums[right],则左边大、右边小,
需要执行left = mid + 1,使得left == right,左右边界位置重合,循环结束,nums[left]与nums[right]都保存了最小值。

如果nums[left] == nums[mid] < nums[right],则左边小、右边大,
会执行right = mid,使得left == right,左右边界位置重合,循环结束,nums[left]、nums[mid]、nums[right]都保存了最小值。

leetcode 154:Find Minimum in Rotated Sorted Array (含重复)

使用left < right作while循环条件可以很方便推广到数组中有重复元素的情况,只需要在nums[mid] == nums[right]时挪动右边界(相当于去掉一个重复的干扰项):

public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1; 
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < nums[right]) {
                right = mid; 
            } else if (nums[mid] > nums[right]) {
                left = mid + 1;
            } else if (nums[mid] == nums[right]) {
                right--;
            }
        }
        return nums[left];
    }

能否将while循环的条件也选为左闭右闭区间left <= right?

可以,只要将nums[mid] == nums[right] 归到 nums[mid] > nums[right],最后返回nums[right]:

public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1; 
        while (left <= right) {                         // 循环的条件选为左闭右闭区间left <= right
            int mid = left + (right - left) / 2;
            if (nums[mid] >= nums[right]) {             // 注意是当中值大于等于右值时,
                left = mid + 1;                         // 将左边界移动到中值的右边
            } else {                                    // 当中值小于右值时
                right = mid;                            // 将右边界移动到中值处
            }
        }
        return nums[right];                             // 最小值返回nums[right]
    }


reference:

  1. https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/solution/er-fen-cha-zhao-wei-shi-yao-zuo-you-bu-dui-cheng-z/
  2. https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md#5-%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97
  3. https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/ji-bai-liao-9983de-javayong-hu-by-reedfan/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值