题目
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [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;
}
}