水题: 旋转数组系列

189. 轮转数组

思路1: 引用辅助数组 + 拷贝

func rotate(nums []int, k int)  {
   res := make([]int, len(nums))

   for i, v := range nums {
       res[(i+k) % len(nums)] = v
   }

   copy(nums, res)
}

思路2: 字符串逆转

func rotate(nums []int, k int) {
	reverse := func(nums[]int, beg int, end int) {
        for beg < end {
            nums[beg], nums[end-1] = nums[end-1], nums[beg]
            beg++;
            end--;
        }
    }

    k = k % len(nums)
    reverse(nums, 0, len(nums))
    reverse(nums, 0, k)
    reverse(nums, k, len(nums))
}

153. 寻找旋转排序数组中的最小值

  • 有序数组旋转
  • 寻找最小值
  • 不允许重复
func findMin(nums []int) int {
    l, r := 0, len(nums) - 1

    for l < r {
        m := l + (r - l)/2

        if nums[m] < nums[r] {
            r = m
        }else {
            l = m + 1
        }
    }

    return nums[r]
}

此处,为什么只看右边界nums[r]呢? 因为数组可能没有旋转过,如果我们通过左边界来判断,会误以为最小值在右子数组中。

154. 寻找旋转排序数组中的最小值 II

在153的前提下,允许数字重复。因此相等nums[r] == nums[m] 时我们需要将r挪动一个位置。直接将r = m可能会错过最小值, 因为可能出现这种状况[2, 2, 2, 1, 2]

  • 允许重复
  • 寻找最小值
  • 有序数组旋转
class Solution {
public:
    int findMin(vector<int>& nums) {
        int l = 0, r = nums.size() - 1;

        int m ;
        while(l < r) {
            m = l + (r - l)/2;
            if (nums[r] > nums[m]) {
                r = m;
            }else if(nums[r] == nums[m]) {
                r--;
            }else{
                l = m + 1;
            }
        }

        return nums[r];
    }
};

这个二分的写法与我们之前提及的二分查找的模板不一致,用之前的模板反而无法实现这样的情形。

33. 搜索旋转排序数组

其实,与153的旋转的概念依旧是一样的,只不过问题从找最小值,变成了找target

由于是有序数组的旋转,中点会将数组划分成两个子数组。可能两个子数组都有序,也可能一个有序而另一个无序,不可能出现两个都无序的情况。

对于有序的子数组,我们可以直接找到target在不在里面
对于无序的子数组,我们可以继续用中点划分

  • 不允许重复
  • 判断是否包含target值
  • 有序数组旋转
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;

        while (l <= r) {
            int m = l + (r - l)/2;
            if(nums[m] == target) return m;

            if(nums[m] < nums[r]) {
                // 右边有序
                if(nums[m] < target && nums[r] >= target){
                    // target在右子数组内部
                    l = m + 1;
                }else{
                    r = m;
                }
            }else{
                // 左边有序
                if(nums[m] > target && nums[l] <= target){
                    // target在左子数组内部
                    r = m;
                }else{
                    l = m + 1;
                }
            }
        }

        return -1;
    }
};

81. 搜索旋转排序数组 II

此处其实是33的进阶版本,多加了一个条件,即允许重复值。
同样,如果相等我们就挪动一步呗。

  • 允许重复
  • 判断是否包含target值
  • 有序数组旋转
class Solution {
public:
    bool search(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;

        while (l <= r) {
            int m = l + (r - l)/2;
            if(nums[m] == target) return true;

            if(nums[m] < nums[r]) {
                // 右边有序
                if(nums[m] < target && nums[r] >= target){
                    // target在右子数组内部
                    l = m + 1;
                }else{
                    r = m;
                }
            }else if(nums[m] > nums[r]){
                // 左边有序
                if(nums[m] > target && nums[l] <= target){
                    // target在左子数组内部
                    r = m;
                }else{
                    l = m + 1;
                }
            }else{
                r--;
            }
        }

        return false;
    }
};

这里的挪动一步,可以是l也可以是r

面试题 10.03. 搜索旋转数组

  • 有序数组的旋转
  • 允许重复
  • 寻找target值的最小下标

因为允许重复, 所以我们找到相同的只能一步一步挪

对于81略作修改,对于输入[5,5, 1, 2,3,4,5] 时target为5的情况,我们需要先排除掉。因此,只需要用常规作法即可。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;

        while (l <= r) {
            int m = l + (r - l)/2;
            // 排除target恰好同时在最前和最后
            if(nums[l] == target) return l;

            if(nums[m] == target) {
                for(;m >= 0 && nums[m] == target; m--);
                return m + 1;
            }

            if(nums[m] < nums[r]) {
                // 右边有序
                if(nums[m] < target && nums[r] >= target){
                    // target在右子数组内部
                    l = m + 1;
                }else{
                    r = m;
                }
            }else if(nums[m] > nums[r]){
                // 左边有序
                if(nums[m] > target && nums[l] <= target){
                    // target在左子数组内部
                    r = m;
                }else{
                    l = m + 1;
                }
            }else{
                r--;
            }
        }

        return -1;
    }
};

对于中间那部分,如果担心复杂度超过O(logn), 可以也可以写成这样

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;

        while (l <= r) {
            int m = l + (r - l)/2;
            // 排除target恰好同时在最前和最后
            if(nums[l] == target) return l;

            if(nums[m] == target) {
                // for(;m >= 0 && nums[m] == target; m--);
                // return m + 1;
                r = m;
                continue;
            }

            if(nums[m] < nums[r]) {
                // 右边有序
                if(nums[m] < target && nums[r] >= target){
                    // target在右子数组内部
                    l = m + 1;
                }else{
                    r = m;
                }
            }else if(nums[m] > nums[r]){
                // 左边有序
                if(nums[m] > target && nums[l] <= target){
                    // target在左子数组内部
                    r = m;
                }else{
                    l = m + 1;
                }
            }else{
                r--;
            }
        }

        return -1;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值