有序旋轉數組是指將有序數組向左或者向右移動k個位置得到的結果,其查找算法不難理解,因為局部有序,因此很容易想到二分查找是最合適的方法,時間復雜度O(nlogn),本文總結四道相關的算法題目。
(一)旋轉數組
題目:189. 旋轉數組
題目描述:
給定一個數組,將數組中的元素向右移動 k 個位置,其中 k 是非負數。
示例 1:
輸入: [1,2,3,4,5,6,7] 和 k = 3
輸出: [5,6,7,1,2,3,4]
解釋:
向右旋轉 1 步: [7,1,2,3,4,5,6]
向右旋轉 2 步: [6,7,1,2,3,4,5]
向右旋轉 3 步: [5,6,7,1,2,3,4]
解題思路:三次反轉
實際上本題不難想到解答,也是在面試中很常見的一個題目,實際上它類似於【劍指Offer】43、左旋轉字符串,對於左旋轉,可以分為前k個元素和其他元素,而右旋轉可以分為后k個元素和前面的其他元素,仍然可以借鑒三次反轉的算法來實現。
代碼思路:
class Solution {
public void rotate(int[] nums, int k) {
//右移k位,三次反轉
if(nums==null || nums.length==0 || k==0)
return ;
int len=nums.length;
k=k%len;
reverse(nums,len-k,len-1);
reverse(nums,0,len-k-1);
reverse(nums,0,len-1);
}
//數組反轉
public void reverse(int[] nums,int begin,int end){
int i=begin,j=end;
while(i<j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
i++;
j--;
}
}
}
(二)有序數組的查找
在有序數組中查找一個特定的值target,這就是典型的二分查找算法,其過程為:
- 若 target == nums[mid],直接返回
- 若 target < nums[mid],則 target 位於左側區間 [left,mid) 中。令 right = mid-1,在左側區間查找
- 若 target > nums[mid],則 target 位於右側區間 (mid,right] 中。令 left = mid+1,在右側區間查找
public boolean search(int[] nums, int target) {
if(nums==null || nums.length==0)
return false;
int low=0,high=nums.length-1;
while(low<=high){
int mid=low+(high-low)/2;
if(nums[mid]==target)
return true;
else if(target<nums[mid]){
high=mid-1;
}else{
low=mid+1;
}
}
return false;
}
(三)搜索旋轉排序數組
題目:33. 搜索旋轉排序數組
題目描述:
假設按照升序排序的數組在預先未知的某個點上進行了旋轉。( 例如,數組 [0,1,2,4,5,6,7] 可能變為 [4,5,6,7,0,1,2] )。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1 。你可以假設數組中不存在重復的元素。你的算法時間復雜度必須是 O(log n) 級別。
示例:
輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4
輸入: nums = [4,5,6,7,0,1,2], target = 3
輸出: -1
解題思路:二分查找
對於本題,二分查找是自然而然可以想到的思路,特別是更要求算法時間復雜度是O(logn),那幾乎二分查找就成為了唯一的選擇。但是,由於本題並不是完全有序,而是經過旋轉后的局部有序。
因此,對於這種局部有序的數組,我們可以觀察到一個特點:從任一位置進行分隔,則必有其中一半是有序的。因此,我們可以仍然從中點位置進行分隔,然后通過將最左邊元素nums[low]和nums[mid]進行比較,從而可以判斷出來,是左側還是右側是連續有序的,進而可以選擇接下來繼續選擇哪一邊進行繼續查找。
1、若 target == nums[mid],直接返回
2、若 nums[left] <= nums[mid],說明左側區間 [left,mid]「連續遞增」。此時:
若 nums[left] <= target <= nums[mid],說明 target 位於左側。令 right = mid-1,在左側區間查找
否則,令 left = mid+1,在右側區間查找
3、否則,說明右側區間 [mid,right]「連續遞增」。
此時:
若 nums[mid] <= target <= nums[right],說明 target 位於右側區間。令 left = mid+1,在右側區間查找
否則,令 right = mid-1,在左側區間查找
代碼實現:
class Solution {
public int search(int[] nums, int target) {
if(nums==null || nums.length==0)
return -1;
int low=0,high=nums.length-1;
while(low<=high){
int mid=low+(high-low)/2;
if(nums[mid]==target)
return mid;
else if(nums[low]<=nums[mid]){ //左側連續
if(target<nums[mid] && target>=nums[low])
high=mid-1;
else
low=mid+1;
}else{ //右側連續
if(target>nums[mid] && target<=nums[high])
low=mid+1;
else
high=mid-1;
}
}
return -1;
}
}
(四)搜索旋轉排序數組II
題目描述:
假設按照升序排序的數組在預先未知的某個點上進行了旋轉。( 例如,數組 [0,0,1,2,2,5,6] 可能變為 [2,5,6,0,0,1,2] )。編寫一個函數來判斷給定的目標值是否存在於數組中。若存在返回 true,否則返回 false。
示例 :
輸入: nums = [2,5,6,0,0,1,2], target = 0
輸出: true
解題思路:
本題是上一題的延續,其區別在於數組中是否包含重復元素,當數組中允許元素重復時,如果nums[low]和nums[mid]是相等的,那我們就無法判斷究竟是哪一邊連續有序,這時我們可以讓指針只移動一位,相當於排除一個元素。其余和上題基本相同。
代碼實現:
class Solution {
public boolean search(int[] nums, int target) {
if(nums==null || nums.length==0)
return false;
int low=0,high=nums.length-1;
while(low<=high){
int mid=low+(high-low)/2;
if(nums[mid]==target)
return true;
if(nums[low]==nums[mid]){
low++;
}else if(nums[low]<nums[mid]){ //左邊有序
if(target>=nums[low] && target<nums[mid])
high=mid-1;
else
low=mid+1;
}else{ //右邊有序
if(target>nums[mid] && target<=nums[high])
low=mid+1;
else
high=mid-1;
}
}
return false;
}
}
總結:
本文總結了關於旋轉排序數組的四道題目,重點是二分查找算法的變形應用,是一個重要的算法知識點。