Bootstrap

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等。

;