leetcode: 918.环形子数组的最大和 单调队列或Kanade

918.环形子数组的最大和

目录

题目描述

算法介绍

第一种是单调队列

第二种是kanade

第三种是大根堆

解题思路

一、前缀和+单调队列

单调队列

大根堆(优先队列)

二、kanade

完整代码

单调队列

大根堆

kanade


题目描述

给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 

环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。

子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。

示例 1:

输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3

示例 2:

输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10

示例 3:

输入:nums = [3,-2,2,-3]
输出:3
解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3

提示:

  • n == nums.length
  • 1 <= n <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104

算法介绍

该题我觉得可用三种算法来写

第一种是单调队列

单调队列的介绍

单调队列是一种主要用于解决滑动窗口类问题的数据结构,即,在长度为 n 的序列中,求每个长度为 m 的区间的区间最值。它的时间复杂度是 O(n) ,在这个问题中比 O(nlogn⁡) 的ST表线段树要优。

单调队列的基本思想是,维护一个双向队列(deque),遍历序列,仅当一个元素可能成为某个区间最值时才保留它。

学习单调队列可以先去看看滑动窗口这个题,很典型的一个题

洛谷:滑动窗口模板题

第二种是kanade

kanade 算法就是给定一个数组,寻找这个数组中最大的连续字串和,该算法是基于动态规划的逻辑思维

令 f[j] 为以 a[j] 结尾的最大子段和。也就是,

        f[j] = \frac{max}{i}(a_i + a_{i+1} + .....a_j])

由于一个子段一定从某个位置截止,所以 \frac{max}{i}f[j] 就是需要的答案。

代码实现:

int ans = 0;
int res = 0;
for(int i = 0; i < n; i++)
{
    ans = a[i] + max(ans,0);
    res = max(res,ans);
}
return res ;

第三种是大根堆

大根堆的思路和单调队列的方式是差不多的,只是用大根堆来维护这个最小值,让最小值始终是在最前面。

解题思路

一、前缀和+单调队列

对于单调队列的写法有三种

单调队列

该题的中心思想是先计算前缀和,然后计算对于第 sum[i] 个前面可以减去的最小的那个前缀和得到的值就是在该点处可以取得的最大值,那么求sum[i] 前面可以减去的最小的那个前缀和的值可以运用单调队列的思路进行求解,然后求得的值的数组下标与前缀和对应的下标一样,那么就进行求哪一段的值最大。

因为我们要进行求环形的数组,而且数组的中的值都只能用一次,那么我们就将两个数组进行拼接。例如A = [1,2,3,4,5] , B = A + A,那么拼接的数就是 B = [1,2,3,4,5,1,2,3,4,5];

这里给出简单的拼接方法(vector)

vector<int> A,B;
A.insert(A.end(),A.begin(),A.end());
B = A;

大根堆(优先队列)

该题思路和上题的思路一样,只不过求求sum[i] 前面可以减去的最小的那个前缀和的值的方法不一样,该题还是看代码比较好理解。

二、kanade

这个的代码实现比较简单,但比较难想,需要对kanade有着比较深刻的理解才能写出来

完整代码

单调队列

class Solution {
public:
    typedef pair<int,int> PII;
    int maxSubarraySumCircular(vector<int>& nums) {
        vector<int> a = nums;
        a.insert(a.end(),a.begin(),a.end());
        const int n = nums.size();
        vector<int> sum(2 * n);
        sum[0] = a[0];
        for(int i = 1; i < 2 * n; i++)
        {
            sum[i] = sum[i-1] + a[i];
        }
        deque<int> d;
        d.push_back(0);
        int res = sum[0];
        for(int i = 1; i < 2 * n; i++)
        {
            while(n != 1 && d.size() && d.front() < i - n) d.pop_front();
            while(d.size() && sum[i] <= sum[d.back()]) d.pop_back();
            res = max(res, sum[i]-sum[d.front()]);
            d.push_back(i);
        }
        return res ;
    }
};


大根堆

class Solution {
public:
    typedef pair<int,int> PII;
    int maxSubarraySumCircular(vector<int>& nums) {
        vector<int> a = nums;
        a.insert(a.end(),a.begin(),a.end());
        const int n = nums.size();
        vector<int> sum(2 * n);
        sum[0] = a[0];
        for(int i = 1; i < 2 * n; i++)
        {
            sum[i] = sum[i-1] + a[i];
        }
        priority_queue<PII,vector<PII>,greater<PII>> p;
        p.push({sum[0],0});
        int res = sum[0];
        for(int i = 1; i < 2 * n; i++)
        {
            while(!p.empty()&&p.top().second < i-n) p.pop();
            res = max(res,sum[i]-p.top().first);
            p.push({sum[i],i});
        }
        return res ;
    }
};

kanade

class Solution {
public:
    typedef pair<int,int> PII;
    int maxSubarraySumCircular(vector<int>& nums) {
        int total = 0, maxSum = nums[0], curMax = 0, minSum = nums[0], curMin = 0;
    for (int& a : nums) {
        curMax = max(curMax + a, a);
        maxSum = max(maxSum, curMax);
        curMin = min(curMin + a, a);
        minSum = min(minSum, curMin);
        total += a;
    }
    return maxSum > 0 ? max(maxSum, total - minSum) : maxSum;
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值