剑指 Offer 11. 旋转数组的最小数字

剑指 Offer 11. 旋转数组的最小数字

注意,这里并没有告诉我们数组是否有序,而是可能有序,因此我们可以和选择字符串那题联系一下,找到分别有序的两个部分,将两部分分别反转后再整体反转,即可得到一个完全有序的数组。再返回数组的第一个数字即可。

class Solution {
    public int minArray(int[] numbers) {
        int idx = 0, n = numbers.length;
        if(n == 1) return numbers[0];
        // 找出两个有序区间的分割点
        for(int i = 1; i < n; i++) {
            if(numbers[i] < numbers[i - 1]) {
                idx = i;
                break;    
            }
        }
        // 反转区间
        reverse(numbers, 0, idx - 1);
        reverse(numbers, idx, n - 1);
        reverse(numbers, 0, n - 1);
        return numbers[0];
    }
    private void reverse(int[] numbers, int s, int e) {
        while(s < e) {
            int tmp = numbers[s];
            numbers[s] = numbers[e];
            numbers[e] = tmp;
            s++;
            e--;
        }
    }
}

时间复杂度为\(O(n)\),空间复杂度为\(O(1)\)
若不利用有序条件,直接枚举每一个位置也是一种可行解法,时间复杂度为\(O(n)\),空间复杂度为\(O(1)\)

class Solution {
    public int minArray(int[] nums) {
        int ans = Integer.MAX_VALUE;
        for(int num : nums) ans = Math.min(ans, num);
        return ans;
    }
}

二分的做法本菜鸡没有想到🤣,去评论区学习了一下大佬的二分做法:

public class Solution {
    public int minArray(int[] numbers) {
        int l = 0, r = numbers.length - 1;
        while (l < r) {
            int mid = ((r - l) >> 1) + l;
            //只要右边比中间大,那右边一定是有序数组
            if (numbers[r] > numbers[mid]) {
                r = mid;
            } else if (numbers[r] < numbers[mid]) {
                l = mid + 1;
             //去重
            } else r--;
        }
        return numbers[l];
    }
}

思路在于:若中点值小于 nums[right],说明 (mid,right] 区间内正确升序,无旋转点,旋转点在 [left,mid]之间,若中点值大于 nums[right],说明(mid,right] 区间内存在旋转点导致了降序,此时令 left=mid+1 继续循环,若中点值刚好与 nums[right]相等,说明存在重复元素,故 right 向左移一(多)位即可。(去重)值比较一直采用了 nums[right],因为它与可以配合 nums[mid] 正确地进行区间缩减。

posted @ 2022-01-19 12:40  NullPointer_C  阅读(33)  评论(0)    收藏  举报