一、leetcode 33. 搜索旋轉排序數組
題目描述(題目難度,中等)
假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,1,2,4,5,6,7]
可能變爲 [4,5,6,7,0,1,2]
)。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1
。
你可以假設數組中不存在重複的元素。
你的算法時間複雜度必須是 O(log n) 級別。
示例 1:
輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4
示例 2:
輸入: nums = [4,5,6,7,0,1,2], target = 3
輸出: -1
題目求解
解法一
先二分查找數組中最大值的位置,將數組分成兩個升序子數組,再在目標值所在的子數組內二分查找目標值。
class Solution {
public int search(int[] nums, int target) {
int len = nums.length;
if(len == 0) return -1;
int low = 0, high = len-1, mid = 0;
while(low < high) {
mid = (low+high) >>> 1;
if(mid+1 <= high && nums[mid] > nums[mid+1]) break;
if(nums[mid] > nums[high]) low = mid+1;
else high = mid;
}
// 未旋轉的情況
if(low == high) return binarySearch(nums, 0, len-1, target);
if(nums[len-1] < target) return binarySearch(nums, 0, mid, target);
else return binarySearch(nums, mid+1, len-1, target);
}
private int binarySearch(int[] nums, int low, int high, int target) {
int mid;
while(low <= high) {
mid = (low+high) >>> 1;
if(nums[mid] < target) low = mid+1;
else if(nums[mid] > target) high = mid-1;
else return mid;
}
return -1;
}
}
解法二
直接改造二分查找。
class Solution {
public int search(int[] nums, int target) {
int low = 0, high = nums.length-1, mid;
while(low <= high){
mid = (low+high) >>> 1;
if(nums[mid] < target){
// 中值在右區間,目標值在左區間,需左移
if(nums[low]>nums[mid] && nums[low]<=target) high = mid-1;
// 正常情況,右移
else low = mid+1;
}else if(nums[mid] > target){
// 中值在左區間,目標值在右區間,需右移
if(nums[low]<=nums[mid] && nums[low]>target) low = mid+1;
// 正常情況,左移
else high = mid-1;
}else return mid;
}
return -1;
}
}
對比來看的話,解法二應該更優一點。
二、leetcode 81. 搜索旋轉排序數組 II
題目描述(題目難度,中等)
這是上一題的延伸題目,和上一題的區別是,本題中的 nums 可能包含重複元素。
題目求解
由於本題中的 nums 包含重複元素,所以當 nums[mid] == nums[low] == nums[high] 時,nums[mid] 沒辦法根據 nums[low] 或 nums[high],確定 mid 當前是處於左升序區間還是右升序區間。
例如,[2, 2, 2, 3, 2] 和 [2, 1, 2, 2, 2]。
我的解決方案是,首先通過指數搜索,快速找到第一個不等於 nums[nums.length-1] 的值,如果沒找到,就老老實實順序查找,否則,就採用上一題的思路,繼續查找。
class Solution {
public boolean search(int[] nums, int target) {
int i = 0, len = nums.length;
while(i < len && nums[i] == nums[len-1]) i = (i+1) << 1;
if(i >= len) {
// 順序查找
for(i = 0; i < len; ++i) {
if(nums[i] == target) return true;
}
return false;
}else if(i == 0){
return search0(nums, 0, len-1, target);
}else {
if(nums[i] < nums[len-1] && nums[i] > target) {
// 這種情況,搜索左邊區間即可
return search0(nums, (i>>1)-1, i, target);
}
if(nums[i] > nums[len-1] && nums[i] < target) {
// 這種情況,搜索右邊區間即可
return search0(nums, i, len-1, target);
}
return search0(nums, i, len-1, target)
|| search0(nums, (i>>1)-1, i, target);
}
}
private boolean search0(int[] nums, int floor, int ceil, int target) {
int low = floor, high = ceil, mid;
while(low <= high){
mid = (low+high) >>> 1;
if(nums[mid] == target) return true;
if(nums[mid] < target){
// 中值在右區間,目標值在左區間,需要左移
if(nums[low] > nums[mid] && nums[low] <= target) high = mid-1;
// 正常情況下的右移
else low = mid+1;
}else{
// 中值在左區間,目標值在右區間,需要右移
if(nums[low] <= nums[mid] && nums[low] > target) low = mid+1;
// 正常情況下的左移
else high = mid-1;
}
}
return false;
}
}