Leetcode 33 -- 二分查找&&归约思想
题目描述
二分的过程就是归约的过程
思路来源
一个重要的性质:源数组经过旋转之后,会划分为两个递增的数组,我们假设为 \(a\) 和 \(b\)
一个清晰的思路:这道题和平常二分法查找的不同就在于,把一个有序递增的数组分成了,两个递增的数组,我们需要做的就是判断这个数在哪一个递增的数组中,然后再去用常规的二分法去解决。
一个假设:我们一般性的假设源数组被旋转为: \([b1, b2, ..bm, a1, a2, a3 ,..., an]\)
如果我们从归约的角度去考虑二分问题,那么二分的过程就是不断归约的过程,而归约又与二叉树联系很密切。如果你仔细回想一下二分的过程,它不就是从一个起点和终点为 \([l,r]\) 的数组逐渐划分,最终变成一个起点和终点相等的单个元素的过程吗。如果我们把初始时的 \([l,r]\) 看作跟节点,那么叶子节点就是数组中的各个元素。这其实有点像线段数了。
总之,我想说的是,二分就是一个归约的过程(每次要么归约到 \(mid\) 的左半部分,要么归约到 \(mid\) 的右半部分),把这个归约的过程和二叉树结合起来更容易理解。
好了,明白了归约之后,再来看这道题。
思路1:一次二分
我们依据 \(mid\) 将 \(nums\) 划分为 \([l,mid-1]\), \([mid,r]\) 两个区间,那么接下来我们就要依据 \(target\) 在那个区间来进行规约,由于 \(nums\) 是一个旋转数组,导致 \([l,r]\) 未必有序,也就是这两个子区间未必都有序,但必然至少有一个是有序的。因此我们可以先找出那个区间是有序的,然后判断 \(target\)是 否在这个区间,若不在则 \(target\) 就在另一个区间。
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while(l < r) {
int mid = l + r + 1 >> 1;
// 根据mid将nums划分为两个区间:[l, mid - 1] [mid, r]
// 接下来我们需要根据target在那个区间来进行归约
// 由于两个区间只有一个有序,因此我们还需要先判断那个区间有序
if(nums[mid] < nums[r]) { // 右区间有序
if(nums[mid] <= target && target <= nums[r]) { // 在右区间
l = mid;
}
else { // 在左区间
r = mid - 1;
}
}
else { // 左区间有序
if(nums[l] <= target && target <= nums[mid - 1]) { // 在左区间
r = mid - 1;
}
else { // 在右区间
l = mid;
}
}
}
return nums[l] == target ? l : -1;
}
};
思路2:两次二分
由于 \(nums\) 可能由两个有序区间构成,那么我们是否能找到这两个有序区间,然后分别在这两个区间上进行二分呢?答案是可行的!并且这种做法更为简单,直接!
那么怎么确定这两个有序区间呢?也很简单,直接找最值,通过最值来确定区间
class Solution {
public:
int get_separate(vector<int> &nums) { // 找分隔点:e.g.最小值[l,pos(min)-1],[pos(min),r]
int l = 0, r = nums.size() - 1; // 如果找最大值就是 [l,pos(max)],[pos(max+1),r]
while(l < r) {
int mid = l + r >> 1;
// [l, mid-1], [mid,r]
if(nums[mid] < nums[r]) r = mid;
else l = mid + 1;
}
return l;
}
int binary_search(vector<int> &nums, int l, int r, int target) {
while(l < r) {
int mid = l + r + 1 >> 1;
if(nums[mid] > target) r = mid - 1;
else l = mid;
}
return nums[l] == target ? l : -1;
}
int search(vector<int>& nums, int target) {
int s = get_separate(nums);
cout << "sep: " << s << endl;
// 找到两个有序区间:[0, s], [s+1, n-1]
// 根据target在那个有序区间进行二分搜索
if(s - 1 >= 0 && nums[0] <= target && target <= nums[s - 1])
return binary_search(nums, 0, s - 1, target);
return binary_search(nums, s, nums.size() - 1, target);
}
};