LeetCode旋转排序数组问题汇总
1、搜索旋转排序数组 (LC-33)
题目要求在旋转后的有序数组中寻找目标值,返回其索引。数组中的元素互不相同。
详细题解
解法:二分查找法
将数组从中间分成左右两部分,一定有一部分数组是有序的(比较nums[mid]和nums[right])。在使用二分查找时,通过判断target是否在有序的那个部分来确定应该如何改变二分查找的上下界。
class Solution {
public:
// 思路:二分查找法
int search(vector<int>& nums, int target) {
int len = (int) nums.size();
if (len == 0) return -1;
int left = 0, right = len - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (nums[mid] < nums[right]) {
// 区间[mid,right]一定是有序的
if (nums[mid] <= target && target <= nums[right]) {
left = mid;
} else {
right = mid - 1;
}
} else {
// 区间[left,mid]一定是有序的,但是我们强行只认为[left,mid-1]有序
if (nums[left] <= target && target <= nums[mid - 1]) {
right = mid - 1;
} else {
left = mid;
}
}
}
if (nums[left] == target) {
return left;
}
return -1;
}
};
2、搜索旋转排序数组 II (LC-81)
这是上面一题的延伸题目,本题中的数组可能包含重复元素。
详细题解
解法:二分查找法
解法大体上和上面一题差不多,就是要特别考虑nums[mid]==nums[right]的情况。
class Solution {
public:
// 思路:参考33题,对等于的情况特别考虑
bool search(vector<int>& nums, int target) {
int len = (int) nums.size();
if (len == 0) return false;
int left = 0, right = len - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (nums[mid] == nums[right]) {
// 此时无法判断哪个区间有序;注意区间内只剩下两个元素的情况也被归为这一类
// 因此在排除right之前要先判断一下nums[right]是否等于目标值
if (nums[right] == target) {
return true;
}
// 移动右边界
right = right - 1;
} else if (nums[mid] < nums[right]) {
// 区间[mid,right]一定是有序的
if (nums[mid] <= target && target <= nums[right]) {
left = mid;
} else {
right = mid - 1;
}
} else {
// 区间[left,mid]一定是有序的,但是我们强行只认为[left,mid-1]有序
if (nums[left] <= target && target <= nums[mid - 1]) {
right = mid - 1;
} else {
left = mid;
}
}
}
if (nums[left] == target) {
return true;
}
return false;
}
};
3、寻找旋转排序数组中的最小值 (LC-153)
假设按照升序排序的数组在预先未知的某个点上进行了旋转,找出其中最小的元素。数组中不存在重复元素。
详细题解
解法:二分查找法
如果中值<右值,则最小值在左半边,可以收缩右边界;如果中值>右值,则最小值在右半边,可以收缩左边界。通过比较中值与右值,可以确定最小值的位置范围,从而决定边界收缩的方向。
class Solution {
public:
int findMin(vector<int>& nums) {
int len = (int) nums.size();
int left = 0, right = len - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[right]) {
// 最小值在左半边,且中值也可能是最小值
right = mid;
} else {
// 最小值在右半边,且中值不可能是最小值
left = mid + 1;
}
}
return nums[left];
}
};
4、寻找旋转排序数组中的最小值 II (LC-154)
假设按照升序排序的数组在预先未知的某个点上进行了旋转,找出其中最小的元素。注意数组中可能存在重复的元素。
解法:二分查找法
class Solution {
public:
// 思路:参考153题
int findMin(vector<int>& nums) {
int len = (int) nums.size();
int left = 0, right = len - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == nums[right]) {
// 移动右边界
right = right - 1;
} else if (nums[mid] < nums[right]) {
// 最小值在左半边,且中值也可能是最小值
right = mid;
} else {
// 最小值在右半边,且中值不可能是最小值
left = mid + 1;
}
}
return nums[left];
}
};
5、寻找旋转排序数组中的最大值(拓展)
假设按照升序排序的数组在预先未知的某个点上进行了旋转,找出其中最大的元素;数组中不存在重复元素。
解法:二分查找法
要比较mid和left,因此当区间内只剩两个元素时,mid应该取right即上界。最后返回right。
class MYSolution {
public:
int findMax(vector<int>& nums) {
int len = (int) nums.size();
int left = 0, right = len - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (nums[mid] < nums[left]) {
// 最大值在左半边,且中值不可能是最大值
right = mid - 1;
} else {
// 最大值在右半边,且中值有可能是最大值
left = mid;
}
}
return nums[right];
}
};
6、寻找旋转排序数组中的最大值 II(拓展)
假设按照升序排序的数组在预先未知的某个点上进行了旋转,找出其中最大的元素。注意数组中可能存在重复的元素。
解法:二分查找法
class MYSolution {
public:
int findMax(vector<int>& nums) {
int len = (int) nums.size();
int left = 0, right = len - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (nums[mid] == nums[left]) {
// 移动左边界
left = left + 1;
} else if (nums[mid] < nums[left]) {
// 最大值在左半边,且中值不可能是最大值
right = mid - 1;
} else {
// 最大值在右半边,且中值有可能是最大值
left = mid;
}
}
return nums[right];
}
};
总结
关于旋转排序数组的问题,一般都用二分法来求解。在按照升序排序的数组中,通过比较[mid]和[right]来确定一个递增区间。注意如果数组中可能存在重复的元素,则要单独考虑[mid]等于[right]的情况。在实现二分查找法时要注意细节,如计算mid时取上界还是下界,返回left还是right等。