前言:
今天做題的時候看到一道題:33. Search in Rotated Sorted Array。由於LeetCode題目不給範圍和時間。然我們儘量想複雜度小的方法。所以首先便把順序便歷排除。可是接下來就陷入了僵局。因爲一個原本升序的數列進行旋轉之後的數列不熟悉。然後果斷搜索了一波發現是一個循環有序序列;
循環有序序列:
該序列是從一個原本有序的數列經過了一定的旋轉操作得到的序列。它的性質是將數列從中間分爲兩個部分,分別記爲arr1、arr2。而首尾元素記爲arr[low]、arr[hig]。中間記爲arr[mid](如果數列元素個數爲偶數時不管將mid定爲中間元素的哪一個結果都一樣);此時循環有序序列的性質可以描述爲:
1. 當arr[mid] > arr[low]:說明前半部分arr1有序,而後半部分arr2則爲一個相對小一點的循環有序序列。
2. 當arr[mid] < arr[hig]:說明後半部分arr2有序,而前半部分arr2則爲一個相對小一點的循環有序序列。
在循環有序序列中高效率查找目標元素:
給定一個循環有序序列和目標元素。在序列中查找目標元素,如果找到返回索引,未找到返回-1。
知道了上訴的性質,我們可以利用二分的思想來搜索指定元素。二分思想是建立在有序的基礎上的一種思想。當看到該題時也想到過二分。可因爲該序列被旋轉爲無序的狀態,無法運用這種思想。不過既然知道了它的性質——局部有序。這時我們可以使用局部二分。先看代碼:
// 改進後的二分搜索
int Search(int A[],int n, int num)
{
int left = 0 ;
int right = n - 1 ;
int mid = 0 ;
int pos = -1 ; //默認返回-1,表示查找失敗
while(left <= right)
{
mid = (left + right)/2 ; //將mid定爲後半部分
if (A[mid] == num) //查找成功
{
pos = mid ;
break;
}
if (A[left] <= A[mid]) //前半部分是嚴格遞增的,後半部分是一個更小的循環遞增數組
{
if(A[left] <= num && num < A[mid] )
{
right = mid - 1 ;
}
else
{
left = mid + 1 ;
}
}
else //後半部分是嚴格遞增的,前半部分是一個更小的循環遞增數組
{
if ( A[mid] < num && num <= A[right])
{
left = mid + 1 ;
}
else
{
right = mid - 1 ;
}
}
}
return pos;
}
emmmmm 代碼很清晰,當前半部分是有序的就利用left和mid來確定num是否在前半部分,如果在則將right指向mid-1。如果不在則將left指向mid+1;後半部分也是利用了有序來判斷num是否在後半部分。
二分查找有重複元素的循環有序序列:Search in Rotated Sorted Array II
當循環有序序列是從擁有重複元素的有序序列得到的話。上面的方法因爲mid、right、left可能三者或兩者相等,這時候代碼中判斷哪一半有序的邏輯便出現問題。
開始的想法:通過移動mid來避免right、left和mid有相等的情況。(PS:這個相等的情況並不是說三者必須相等,而是代碼中用來比較的兩者如果相等就喪失了判斷部分有序的邏輯。當然可以通過判斷mid和另一邊,可是也有三者相等的情況。所以我們採取避免代碼中判斷的兩者相等的情況而不是通過更多的判斷來確定哪部分有序)
後來一直在debug,就漏洞百出。便覺得自己的方法是錯誤的,通過討論區發現可以通過修改right或者left來達到避免相等的目的。先上代碼:
class Solution {
public:
bool search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int pos = -1;
int mid = 0;
mid = (left + right) / 2;
while(left <= right){
mid = (left + right)/2;
if(nums[mid] == target){
pos = mid;
break;
}else if(nums[mid] < nums[right]){ // right is ordered
if(nums[mid] < target && nums[right] >= target){
left = mid+1;
}else{
right = mid - 1;
}
}else if(nums[mid] > nums[right]){ // left is ordered
if(nums[mid] > target && nums[left] <= target){
right = mid - 1;
}else{
left = mid + 1;
}
}else{
right --; //移動right,可以同時修改mid
}
}
return pos != -1;
}
};
清晰思路:通過修改邊界確實是一個非常清晰的方法,上面代碼通過mid與right比較,所以下面的討論也是圍繞這個討論,不過與left比較也是同樣的道理。
首先mid與right不等的情況可以沿用沒有重複元素的情況。如果相等(mid==right)。這時候我們通過移動right,即right--。這時候再進入循環。可以達到避免相等的目的。當然這個方法首先看到的時候我有有個疑惑,就是當right--之後是否right正好是rarget呢?肯定不是。因爲right和mid相等,代碼只有確定了mid與target不想等才進入下面的邏輯。
複雜度:複雜度因爲有重複元素,所以最好的情況(沒有重複元素)時複雜度爲O(logn)。最壞情況(全部相等的情況)下,right要移動到left。這時複雜度爲O(n);
求循環有序序列的最小元素:
給定一個循環有序序列及其長度,讓你求這個序列的最小元素和它的索引。
沒有重複元素:
class Solution {
public:
int findMin(vector<int>& nums) {
int right = nums.size()-1;
int left = 0;
while(left < right){
int mid = (left + right)/2;
if(nums[mid] > nums[right]){ //[left, mid] sorted, (mid, right] contain minNUm
left =mid + 1;
}else{ //[mid, right] sorted, [left, mid] contain minNume
right = mid;
}
}
return min(nums[left], nums[right]);
}
};
有重複元素:
class Solution {
public:
int findMin(vector<int>& nums) {
int right = nums.size()-1;
int left = 0;
while(left < right){
int mid = (left + right)/2;
if(nums[mid] > nums[right]){ //[left, mid] is sorted, (mid, right] contain minNum
left =mid + 1;
}else if(nums[mid] < nums[right]){ //[mid,right] is sorted, [left, mid] contain minNum
right = mid;
}else{ //nums[mid] == nums[right], right--. if nums[right] is minNum.
//mid can get it
right --;
}
}
return min(nums[left], nums[right]);
}
};
//todo lower bound and upper bound