LeetCode 第201次周赛 1546. Maximum Number of Non-Overlapping Subarrays With Sum Equals Target

Leetcode 1546. Maximum Number of Non-Overlapping Subarrays With Sum Equals Target

  • 题目描述
  • 思路
  • 周赛代码
  • 优化代码
  • 复杂度分析

题目描述

Given an array nums and an integer target.

Return the maximum number of non-empty non-overlapping subarrays such that the sum of values in each subarray is equal to target.

Example 1:

Input: nums = [1,1,1,1,1], target = 2
Output: 2
Explanation: There are 2 non-overlapping subarrays [1,1,1,1,1] with sum equals to target(2).
Example 2:

Input: nums = [-1,3,5,1,4,2,-9], target = 6
Output: 2
Explanation: There are 3 subarrays with sum equal to 6.
([5,1], [4,2], [3,5,1,4,2,-9]) but only the first 2 are non-overlapping.
Example 3:

Input: nums = [-2,6,6,3,5,4,1,2,8], target = 10
Output: 3
Example 4:

Input: nums = [0,0,0], target = 0
Output: 3

Constraints:

1 <= nums.length <= 105
-104<= nums[i] <= 104
0 <= target <= 106

题目是给定了一个数组,求不重叠的求和为target的子数组最多同时存在多少个。从数据规模来看,数组最长是10万,算法的复杂度一定是o(nlogn)或者更低。n2肯定是过不了的。

思路

一般求解子数组和的题目首先考虑前向累计和(presum)。如果当前的presum是s,那我们只需要找此前有没有出现一个presum是s - t(t代表target,简写一下),那么就存在一段子数组以当前数字结尾并且满足和是t。此前presum是s-t出现过的位置可能有多个,取哪个呢?回答是取最后出现的那个。很容易看出来的一个结论就是长度越长,最后的结果有机会越大。如果前m个数字就能找出最多有k个满足条件的不重叠数组。那前m+1个数字肯定不会比前m个数字得出的结果差,至少满足前m个数的那个最优解在前m+1个数字里还是一个可行解。所以随着长度的增加,res的值只可能单调不减。

在当时做周赛的时候我想到是用hash map配合dp求解
dp[i]表示前i个数能求到的最优解,根据前面描述,dp[i]的值一定是单调不减
index[presum]表示上一个presum出现的位置

周赛代码

const int N = 1e5 + 10;
//dp[i]表示前i个数字能得到的最优解
int dp[N];
class Solution {
     
public:
    int maxNonOverlapping(vector<int>& nums, int t) {
     
        memset(dp, 0, sizeof dp);
        unordered_map<int, int> index; //key是index, value是[0,key]这个范围能求出的最大值
        int n = nums.size();
        index[0] = -1;
        int s = 0; //这个是presum
        int res = 0;
        for ( int i = 0; i < n; ++i ) {
     
            s += nums[i];
            if ( index.count(s - t) ) {
     
            	//这里写index[s-t]+1是因为dp[i]表示的是前i个数,和index有1个偏移
                res = max(res, dp[index[s - t] + 1] + 1);
            }
            index[s] = i;
            dp[i + 1] = res;
        }
        return res;
    }
};

这段代码的思路是先找到s-t上一次出现的位置,用那个位置的最优解来计算当前的可能最优解。

优化代码

看了discuss里面的高票答案,发现自己的周赛代码中有一些冗余,周赛代码中是通过presum找到index,然后根据index来找之前的最优解。其实没这个必要,index这一步完全可以省去,只需要记录下来当前累计和捆绑的最优解是多少就可以了。就算之前出现过当前的presum,那当前的也一定不会比之前的差(单调性结论),更何况每次查找s - t,都是查找最后出现的那个,因为最后出现的那个覆盖的范围最大。

class Solution {
     
public:
    int maxNonOverlapping(vector<int>& nums, int t) {
     
        unordered_map<int, int> sum; //key是presum, value是这个presum对应的最优解
        int n = nums.size();
        sum[0] = 0;
        int presum = 0;
        int res = 0;
        for ( int i = 0; i < n; ++i ) {
     
            presum += nums[i];
            if ( sum.count(presum - t) ) {
     
                res = max(res, sum[presum - t] + 1);
            }
            sum[presum] = res;
        }
        return res;
    }
};

复杂度分析

时间空间都是o(n)

这题本质上和2 sum差不多,利用res单调性可以使用贪心。

你可能感兴趣的:(算法,greedy,dp,算法)