【Leetcode 153、154、33、81、35】二分实现旋转数组问题【Java】



旋转数组:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转


0. 二分法

0.1 用二分的两个前提
  1. 【顺序存储】比如说数据存放在数组中,可以用索引 O(1) 时间内查找到数据;

  2. 【有序】数组中的元素是有序的。(包括部分有序)


0.2 步骤
  1. 先初始化左右两个指针;
  2. while 循环体,其中判断条件一般为 left < right 或者 left <= right
  3. 循环体内,先判断数组长度是否为1、要找的数不在范围内的边界条件;
  4. 计算中间的指针 int mid = (l + r) / 2
  5. 分别讨论左右两边的情况,缩短两个指针的间距,直到退出循环体。

0.3 注意事项
  • 在循环体内,缩小边界的时候,为什么有时候是 left = mid + 1right = mid - 1,有时候却知只是 left = midright = mid呢?

>>> 若当前这个数也算在范围内,则不能 ±1,例如 LeetCode 154。





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

LeetCode 154

1. 解题思路

本题可以用二分 + 暴力迭代(处理重复问题)。

二分的两个前提:

  1. 【顺序存储】√
  2. 【有序】虽说本题只是局部有序,但也足够了,因为我们可以知道 target 是否在有序的那部分里面。√

具体地,

根据数组的首尾元素 leftright 得出中间点 mid

  • 如果 midright 大:说明 分界点在右边,例如 [3,4,5,1,2]
  • 如果 midright 小:说明 分界点在左边,例如 [5,1,2,3,4]
  • 如果 mid == right有重复的数,右指针左移,例如 [4,1,4,4,4][4,4,4,1,4]

2. 代码(Java实现)

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






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

LeetCode 153

本题是 154 的简化版本,数组里面的元素不会重复。代码实现与上面一样。(去掉最后一个 else 也能通过)







35. 搜索插入位置

LeetCode 35

这题不是旋转数组,但也用的二分,感觉解法有些类似

  • 如果要找的 target 不在数组上下界范围内:return 0 或者 nums.length

  • 如果要找的 target 数组中没有,则把 left 定在即将要插入的位置上(该位置上的数是比 target 大的);

  • 如果找到了,就直接返回 mid

注意:

  • left = mid + 1 是因为此时的 mid 小于 target ,所以真正要返回的索引肯定在下一个之后。
  • 但是right = mid 是因为,此时的 mid 已经大于 target 了,很有可能就是我们要找的位置,不能 + 1

代码

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






33. 搜索旋转排序数组

找出 target 在旋转数组中的索引是多少,如果没有这个数,则返回 -1 。

LeetCode 33


注意的点:

  • 始终让 mid 去判断是否有 target 这个数,两部的指针只用作缩小范围。
  • while (left <= right) 这里的等于,是为了循环体中的 return mid 能够执行。
  • 由于这里面 mid 如果是我们需要的就在前面 return 出去了,所以后面的判断中,mid 不可能是我们要找的数,leftright 可以放心地 ±1 。

代码

public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;


        while (left <= right) {     // 等于是为了return mid
            int mid = (left + right) / 2;
            if (nums[mid] == target) return mid;    // 先判断 mid 是否为 target


            // 以下的判断仅用作缩小范围,但不在里面 return
            if (nums[mid] > nums[right]) {  // 分界线在右边 ——> 【左边是完整的】
                if (nums[left] <= target && target < nums[mid]) {   // target 在mid左边
                    right = mid - 1;
                } else {    // target 在mid右边
                    left = mid + 1;
                }
            } else {    // 分界线在左边 ——> 右边是完整的
                if (nums[mid] < target && target <= nums[right]) {   // target 在mid右边
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return -1;
    }






81. 搜索旋转排序数组 II

与前面的题LC 33类似,要找出 target 在旋转数组中的索引是多少,如果没有这个数,则返回 -1 。不同的地方在于,数组中的数可以重复。

LeetCode 81


解题思路

与153到154类似,这题可以在上面的33题基础上,增加一个 right-- 的暴力步骤。


代码

class Solution {
    public boolean search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] == target) return true;
            if (nums[mid] > nums[right]) {  // 分隔在右边 ——> 左半边是顺序的
                if (nums[left] <= target && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else if (nums[mid] < nums[right]){
                // nums[mid] <= nums[right] 分隔就是mid或者还在更左边的位置 ——> 右半边是顺序的
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            } else {
                right--;
            }
        }
        return false;
    }
}

注意:

  • 如果 while 循环条件不用 =nums=[1] 这种情况就过不了了,因为 leftright 一开始就相等了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值