81. 搜尋旋轉排序陣列 II
技術標籤: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
可能包含重複元素。 - 這會影響到程式的時間複雜度嗎?會有怎樣的影響,為什麼?
題解:
參考 搜尋旋轉排序陣列 一題,這題增加了一個條件:可以有重複值,這個條件有點麻煩。。。
還是用二分來搞定。
注意:二分不一定必須要有單調性,二分的本質是尋找某種性質的分界點。只要找到某種性質,可以確定目標在區間的前半部分還是後半部分,就可以用二分找到這個分界點。
為了便於分析,將陣列中的元素畫在二維座標系中,橫軸代表下標,縱軸代表值。
水平的實線表示相同的元素。可以發現,除了黑色水平那段,其餘部分均滿足二分性質:豎直的虛線左邊的元素均滿足
a
r
r
[
i
]
>
=
a
r
r
[
0
]
arr[i]>=arr[0]
首先,我們需要把黑色的水平段刪除,至於為什麼刪除呢?考慮一種情況: a r r [ m i d ] = = a [ 0 ] arr[mid] == a[0] arr[mid]==a[0],這種情況下,根本不知道往哪邊走,所以只能刪除。
還需要處理陣列未旋轉的情況,當刪除黑色水平段後,若剩下的最後一個元素大於等於arr[0],說明陣列完全單調。
最壞情況:元素均相等情況下的時間複雜度為
O
(
n
)
O(n)
法一:
兩次二分。參考 在有序旋轉陣列中找到最小值 一題,如果我們可以找到分界點,就可以確定 target
在左右哪個區間,然後直接在該區間上進行二分查詢即可。
把複雜問題分解成簡單的子問題 思想很重要。
class Solution {
public:
bool search(vector<int>& nums, int target) {
int n = nums.size();
if ( !n ) return false;
n -= 1;
while ( n && nums[n] == nums[0] ) --n;
if ( !n ) return nums[0] == target;
if ( nums[0] < nums[n] ) {
if ( target < nums[0] || target > nums[n] )
return false;
}
int l = 0, r = n, m;
if ( nums[0] > nums[n] ) {
while ( l < r ) {
m = ( l + r ) >> 1;
if ( nums[m] == target ) return true;
if ( nums[m] >= nums[0] ) l = m + 1;
else r = m;
}
if ( target <= nums[n] ) r = n;
else l = 0, --r;
}
while ( l < r ) {
m = ( l + r ) >> 1;
if ( nums[m] == target ) return true;
if ( nums[m] > target ) r = m;
else l = m + 1;
}
return nums[r] == target;
}
};
/*
時間:4ms,擊敗:94.08%
記憶體:13.6MB,擊敗:29.95%
*/
法二:
在二分過程中,通過一些條件判斷 target
在哪個區間也行:
- 若
n
u
m
s
[
m
i
d
]
>
=
n
u
m
s
[
0
]
nums[mid] >= nums[0]
nums[mid]>=nums[0],說明
mid
在左邊區間:- 若
t
a
r
g
e
t
>
=
n
u
m
s
[
l
]
a
n
d
t
a
r
g
e
t
<
=
n
u
m
s
[
m
i
d
]
target >= nums[l] \quad and \quad target <= nums[mid]
target>=nums[l]andtarget<=nums[mid],說明此時要在區間
[l,mid]
查詢,r = mid
; - 若
t
a
r
g
e
t
<
n
u
m
s
[
l
]
target < nums[l]
target<nums[l],說明
target
在右邊區間,l = mid + 1
; - 若
t
a
r
g
e
t
>
n
u
m
s
[
m
i
d
]
target > nums[mid]
target>nums[mid],說明
target
在mid
右邊,l = mid + 1
;
- 若
t
a
r
g
e
t
>
=
n
u
m
s
[
l
]
a
n
d
t
a
r
g
e
t
<
=
n
u
m
s
[
m
i
d
]
target >= nums[l] \quad and \quad target <= nums[mid]
target>=nums[l]andtarget<=nums[mid],說明此時要在區間
- 若
n
u
m
s
[
m
i
d
]
<
n
u
m
s
[
0
]
nums[mid] < nums[0]
nums[mid]<nums[0],說明
target
在右邊區間:- 若
t
a
r
g
e
t
<
=
n
u
m
s
[
m
i
d
]
target <= nums[mid]
target<=nums[mid],說明
target
在mid
左邊,r = mid
; - 若
t
a
r
g
e
t
>
n
u
m
s
[
n
]
target > nums[n]
target>nums[n],說明
target
在左邊區間,r = mid
; - 否則的話,說明
target
在mid
右邊,l = mid + 1
;
- 若
t
a
r
g
e
t
<
=
n
u
m
s
[
m
i
d
]
target <= nums[mid]
target<=nums[mid],說明
class Solution {
public:
bool search(vector<int>& nums, int target) {
int n = nums.size();
if ( !n ) return false;
n -= 1;
while ( n && nums[n] == nums[0] ) --n;
if ( !n ) return nums[0] == target;
if ( nums[0] < nums[n] ) {
if ( target < nums[0] || target > nums[n] )
return false;
}
int l = 0, r = n, m;
while ( l < r ) {
m = ( l + r ) >> 1;
if ( nums[m] == target ) return true;
if ( nums[m] >= nums[0] ) {
if ( nums[0] <= target && target <= nums[m] ) r = m;
else l = m + 1;
} else {
if ( target <= nums[m] || target > nums[n] ) r = m;
else l = m + 1;
}
}
return nums[r] == target;
}
};
/*
時間:4ms,擊敗:94.08%
記憶體:13.6MB,擊敗:30.23%
*/
法三:
直接掃描一遍 nums
。。。
class Solution {
public:
bool search(vector<int>& nums, int target) {
for ( auto& it : nums )
if ( it == target ) return true;
return false;
}
};
/*
時間:4ms,擊敗:94.08%
記憶體:13.6MB,擊敗:22.29%
*/
三種方法記憶體消耗簡直離譜。。。