leetcode-带限制的子序列和

 题目是LeetCode第186场周赛的第四题,链接:带限制的子序列和。具体描述为:给你一个整数数组nums和一个整数k,请你返回非空子序列元素和的最大值,子序列需要满足:子序列中每两个相邻的整数nums[i]nums[j],它们在原数组中的下标ij满足i < jj - i <= k。数组的子序列定义为:将数组中的若干个数字删除(可以删除 0 个数字),剩下的数字按照原本的顺序排布。

  • 1 <= k <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

 示例1:

输入:nums = [10,2,-10,5,20], k = 2
输出:37
解释:子序列为 [10, 2, 5, 20] 。

 示例2:

输入:nums = [-1,-2,-3], k = 1
输出:-1
解释:子序列必须是非空的,所以我们选择最大的数字。

 示例3:

输入:nums = [10,-2,-10,-5,20], k = 2
输出:23
解释:子序列为 [10, -2, -5, 20] 。

 像这种求子序列最大值的一般都是动态规划的题目,这里也不出意外。假设dp[i]代表数组numsnums[i]为子序列最后一个元素的非空子序列元素和,则递推公式也比较容易得到: d p [ i ] = m a x ( n u m s [ i ] , n u m s [ i ] + m a x ( d p [ i − j ] ) ) , j ∈ [ 1 , k ] dp[i]=max(nums[i], nums[i]+max(dp[i-j])),j\in[1,k] dp[i]=max(nums[i],nums[i]+max(dp[ij])),j[1,k],也就是说当前的dp[i]跟前面k个状态(dp[i-k]~dp[i-1])有关,如果前面的k个状态的元素和都是小于0,那很明显当前的dp[i]就只能取nums[i]了,否则的话就是等于nums[i]加上前面k个状态的元素和最大者。为了求前面k个状态的最大者,最简单的方法就是暴力遍历,然后不出意外的就超时了,所以这里必须做优化。这不就是上一篇博客讲到的求滑动窗口中最大值么,所以还是可以用单调队列来解决这个问题。时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n)

 JAVA版代码如下:

class Solution {
    public int constrainedSubsetSum(int[] nums, int k) {
        int N = nums.length;
        int[] dp = new int[N];
        dp[0] = nums[0];
        int result = dp[0];

        Deque<Integer> deque = new LinkedList<>();
        deque.offerLast(0);

        for (int i = 1; i < N; ++i) {
            dp[i] = nums[i];
            if (dp[deque.peekFirst()] > 0) {
                dp[i] = nums[i] + dp[deque.peekFirst()];
            }
            while (deque.size() > 0 && dp[deque.peekLast()] < dp[i]) {
                deque.pollLast();
            }
            deque.offerLast(i);
            if (i - deque.peekFirst() >= k) {
                deque.pollFirst();
            }
            
            result = Math.max(result, dp[i]);
        }
        return result;
    }
}

 提交结果如下:


 为了加速,可以用数组代替双端队列如下。

 JAVA版代码如下:

class Solution {
    public int constrainedSubsetSum(int[] nums, int k) {
        int N = nums.length;
        int[] dp = new int[N];
        dp[0] = nums[0];
        int result = dp[0];

        int[] deque = new int[N];
        deque[0] = 0;
        int left = 0, right = 0;

        for (int i = 1; i < N; ++i) {
            dp[i] = nums[i];
            if (dp[deque[left]] > 0) {
                dp[i] = nums[i] + dp[deque[left]];
            }
            while (right - left >= 0 && dp[deque[right]] < dp[i]) {
                --right;
            }
            deque[++right] = i;
            if (i - deque[left] >= k) {
                ++left;
            }    
            result = Math.max(result, dp[i]);
        }
        return result;
    }
}

 提交结果如下:


 Python版代码如下:

class Solution:
    def constrainedSubsetSum(self, nums: List[int], k: int) -> int:
        N = len(nums)
        deque = collections.deque()
        dp = [0] * N
        dp[0] = nums[0]
        result = dp[0]
        deque.append(0)
        for i in range(1, N):
            dp[i] = nums[i] + max(0, dp[deque[0]])
            while len(deque) > 0 and dp[deque[-1]] < dp[i]:
                deque.pop()
            deque.append(i)
            if i - deque[0] >= k:
                deque.popleft()
            result = max(result, dp[i])
        return result

 提交结果如下:


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值