Leetcode 81. 搜索旋转排序数组 II

题目

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

示例 1:

输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true

示例 2:

输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false

进阶:

  • 这是 搜索旋转排序数组 的延伸题目,本题中的 nums 可能包含重复元素。
  • 这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?

解答

解法一:暴力搜索

简单易懂, 但是最好和最差的时间复杂度都是 O(n)

没啥好说的,看代码把。

代码
class Solution {
    public boolean search(int[] nums, int target) {
        for(int i = 0; i < nums.length; i ++) {
            if(nums[i] == target) return true;
        }
        
        return false;
    }
}
结果 ( 竟然也不慢 : )

在这里插入图片描述

解法二:直接二分法

首先去重, 去重完后当 无重复元素的旋转排序数组 处理即可

搜索无重复元素的旋转排序数组可以看我之前的博客。博客:https://blog.csdn.net/LetJava/article/details/95382745

时间复杂度:最好 O(logn) 最差 O(n)

基本代码
class Solution {
    public boolean search(int[] nums, int target) {
        if(nums == null || nums.length == 0) return false;
        
        int l = 0;
        int r = nums.length - 1;
        while(l <= r) {
            // 去除重复元素,此处最差会导致退化成 O(n)
            while(l < r && nums[l] == nums[l + 1]) l ++;
            while(l < r && nums[r] == nums[r - 1]) r --;
            
            // 参考 Leetcode 33 题,搜索无重复元素的旋转排序数组。(我博客里也有这道题)
            int mid = l + (r - l) / 2;
            if(nums[mid] == target) {
                return true;
            } else if(nums[mid] < nums[r]) {
                if(nums[mid] < target && nums[r] >= target) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            } else {
                if(nums[l] <= target && nums[mid] > target) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            }
        }
        
        return false;
    }
}

去重的过程能不能再优化呢?

可以优化的,假如要搜索 3,对于 [ l ] 2 2 2 2 2 2 2 2 2 3 4 [ r ], 可以使用二分,就可以避免从左边一个个去重了。

优化后的代码
class Solution {
    public boolean search(int[] nums, int target) {
        if(nums == null || nums.length == 0) return false;
        
        int l = 0;
        int r = nums.length - 1;
        while(l <= r) {
            // 小优化,如果有序那就直接二分查找
            // 比如 [l] 2 2 2 2 2 2 2 2 2 3 4 [r], 如果不二分,又要从左边一个个去重了。
            if(nums[l] < nums[r]) return binarySearch(nums, l, r, target); 
            
            // 去除重复元素,此处最差会导致退化成 O(n)
            while(l < r && nums[l] == nums[l + 1]) l ++;
            while(l < r && nums[r] == nums[r - 1]) r --;
            
            // 参考Leetcode 33题,搜索无重复元素的旋转排序数组
            int mid = l + (r - l) / 2;
            if(nums[mid] == target) {
                return true;
            } else if(nums[mid] < nums[r]) {
                if(nums[mid] < target && nums[r] >= target) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            } else {
                if(nums[l] <= target && nums[mid] > target) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            }
        }
        
        return false;
    }
    
    
    private boolean binarySearch(int[] nums, int start, int end, int target) {
        int lt = start;
        int gt = end;
        while(lt <= gt) {
            int mid = (gt - lt) / 2 + lt;
            if(nums[mid] == target) {
                return true;
            } else if(nums[mid] > target) {
                gt = mid - 1;
            } else {
                lt = mid + 1;
            }
        }
        
        return false;
    }

}
最终结果

在这里插入图片描述

解法三:先定位分界点,再二分

先定位分界点, 可以参考 剑指Offer

找到分界点后,对左右两部分进行二分即可。

时间复杂度:最好 O(logn) 最差 O(n)

代码
class Solution {
    public boolean search(int[] nums, int target) {
        if(nums == null || nums.length == 0) return false;
        
        int lt = 0;
        int gt = nums.length - 1;
        while(lt < gt && nums[lt] >= nums[gt]) {
            if(gt - lt == 1) break;
            
            int mid = (gt - lt) / 2 + lt;
            // 当 左边界 中间值 右边界 三者相等时,无法二分,只能顺序遍历找分界点。 
            // 此处会导致时间复杂度退化成 O(n)
            if(nums[lt] == nums[gt] && nums[lt] == nums[mid]) {
                lt = searchIndex(nums, lt, gt);
                break;
            }
            
            if(nums[lt] <= nums[mid]) lt = mid;
            if(nums[gt] >= nums[mid]) gt = mid;
        }
        
        boolean left = binarySearch(nums, 0, lt, target);
        boolean right = binarySearch(nums, lt + 1, nums.length - 1, target);
        return left || right;
    }
    
    private int searchIndex(int[] nums, int start, int end) {
        for(int i = start; i < end; i ++) {
            if(nums[i] > nums[i + 1]) {
                return i;
            }
        }
        
        return start;
    }
    
    private boolean binarySearch(int[] nums, int start, int end, int target) {
        int lt = start;
        int gt = end;
        while(lt <= gt) {
            int mid = (gt - lt) / 2 + lt;
            if(nums[mid] == target) {
                return true;
            } else if(nums[mid] > target) {
                gt = mid - 1;
            } else {
                lt = mid + 1;
            }
        }
        
        return false;
    }

}
结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值