代码随想录Day4-数组-有序数组的平方&长度最小的子数组:力扣977、209、904题

977. 有序数组的平方

题目链接
代码随想录文章讲解链接

方法一:双指针两头向中间遍历

思路

比较左右两端的元素的平方的大小,选取大的放置到结果数组的末端,然后向中间遍历。因为剩余元素中,平方值最大的元素一定在左右两头。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        vector<int> res(nums.size(), 0);
        int i = right;

        while (left <= right) {
            if (nums[left] * nums[left] > nums[right] * nums[right]) {
                res[i] = nums[left] * nums[left];
                ++left;
            } else {
                res[i] = nums[right] * nums[right];
                --right;
            }
            --i;
        }
        return res;
    }
};

方法二:双指针中间向两头遍历

思路

找到数组负数与非负数的分界点,设置两个指针从分界点处向两端遍历,依次把平方值小的元素放到结果数组中。思路与方法一类似,反过来而已,但是方法一更优,无需判断指针是否位于边界,且也不用事先寻找临界位置。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int size = nums.size();
        int left = size - 1;
        int right = size;
        vector<int> res;

        // 找到边界位置
        for (int i = 0; i < size; ++i) {
            if (nums[i] >= 0) {
                left = i - 1;
                right = i;
                break;
            }
        }

        while (left >= 0 || right < size) {
            if (left < 0) {
                res.push_back(nums[right] * nums[right++]);
            } else if (right >= size) {
                res.push_back(nums[left] * nums[left--]);
            } else {
                if (nums[left] * nums[left] < nums[right] * nums[right]) {
                    res.push_back(nums[left] * nums[left--]);
                } else {
                    res.push_back(nums[right] * nums[right++]);
                }
            }
        }
        return res;
    }
};

看完讲解的思考

代码实现遇到的问题


209. 长度最小的子数组

题目链接
代码随想录文章讲解链接

方法一:滑动窗口

思路

维护一个滑动窗口,当窗口内的和小于目标值时,窗口右端往前进,因为只有新增值才有可能达到目标值;
当和大于目标值时,此时的窗口满足要求,记录长度后窗口左端往前进,因为减去一个值也有可能满足要求,并且长度更短。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0;
        int right = 0;
        int size = nums.size();
        int res = 0;
        int sum = nums[0];

        while (right < size) {
            if (sum < target) {
                ++right;
                if (right < size) sum += nums[right];
            } else {
                if (res == 0) res = right - left + 1;
                else res = min(res, right - left + 1);
                sum -= nums[left++];
            }
        }
        return res;
    }
};

方法二:前缀和+二分查找

思路

暴力解法是两层for循环找到满足条件的数组,时间复杂度为 O ( n 2 ) O(n^2) O(n2),其中内层的for循环可以优化,由于数组都是正数,数组的前缀和是单调递增的数组,单调递增就想到二分查找。
首先计算数组的前缀和,用另外一个数组存储,然后以每个位置为开头,查找满足条件的长度,其实就是二分查找寻找当前位置前缀和加上目标值的位置。

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int size = nums.size();
        vector<int> preSum(size + 1, 0);
        int res = 0;

        // 计算前缀和并存储
        for (int i = 0; i < size; ++i) {
            preSum[i + 1] = preSum[i] + nums[i];
        }
        
        // 以每个位置为开头,找到满足条件的最短的数组
        for (int j = 0; j < size + 1; ++j) {
            // 要查找的目标是当前位置的前缀和加上目标值
            int t = target + preSum[j];
            int left = j;
            int right = size;
            // 若存在刚好等于t的前缀和则找到的是对应位置
            // 若不存在刚好等于t的前缀和,则找到比t大的最小值的位置
            while (left <= right) {
                int mid = (right - left) / 2 + left;
                if (preSum[mid] < t) left = mid + 1;
                else if (preSum[mid] > t) right = mid - 1;
                else {
                    left = mid;
                    break;
                }
            }
            if (left <= size) {
                if (res == 0) res = left - j;
                else res = min(res, left - j);
            }
        }
        return res;
    }
};

看完讲解的思考

方法二虽然性能没有方法一好,但是这种算法优化思想要掌握,特别是如果条件中有数组元素都是正整数,可以考虑前缀和,前缀和又是单调递增,可以考虑二分查找。

代码实现遇到的问题

方法二中,保存前缀和的数组的长度要比原来的数组长度多一位,即第一位0。


904. 水果成篮

题目链接

方法一:滑动窗口

思路

kind1kind2记录目前窗口内的的水果种类,last_kind1last_kind2记录两种水果目前最右边的位置,最右边的位置是用于更新窗口左边界。
判断新增的水果的种类是否是kind1或者kind2,若是的话继续移动右边界,并更新last_kind
若不是的话,则更新结果,再将其中一个种类替换成目前的新增的种类,新增水果左侧的水果种类是要保留的,另一个是要替换掉的,左边界也要右移至丢弃种类水果最右边位置的右边。(通俗说法:来了第三者,要与离它最近的水果组成新的cp,窗口内就要丢弃掉另外一种水果,所以左边界就要把要丢弃的水果全部排出。)

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int kind1 = fruits[0];
        int kind2 = -1;
        int last_kind1 = 0;
        int last_kind2 = -1;

        int left = 0;
        int right = 0;
        int size = fruits.size();
        int res = 0;

        while (right < size) {
            if (fruits[right] == kind1) {
                last_kind1 = right;
                ++right;
            } else if (fruits[right] == kind2) {
                last_kind2 = right;
                ++right;
            } else if (kind2 == -1) {
                last_kind2 = right;
                kind2 = fruits[right++];
            } else {
                res = max(res, right - left);
                if (fruits[right - 1] == kind1) {
                    left = last_kind2 + 1;
                    last_kind2 = right;
                    kind2 = fruits[right++];
                } else {
                    left = last_kind1 + 1;
                    last_kind1 = right;
                    kind1 = fruits[right++];
                }
            }
        }
        return max(res, right - left);
    }
};

看完讲解的思考

滑动窗口的精髓在于如何移动左右边界,一定要搞清楚移动的逻辑。

代码实现遇到的问题

无。


最后的碎碎念

今日有两道中等难度的题目,已经是满头大汗了。看到递增就要考虑到二分查找,看到子数组就要考虑到滑动窗口,牢记,牢记,要记到形成条件反射才行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值