[Leetcode力扣 560] Subarray Sum Equals K

一、题目描述

题意是给一个数组nums,和一个目标值k,让我们找到子数组内元素之和为k的子数组数量。值得注意的是,题目还给出了测试数据的规模,最大的数组规模为2*10^{4},而一般的OJ一秒内能承受的运算次数为10^{8}\rightarrow 10^{9},所以这个题时间复杂度为O(n^{2})及以上的算法是肯定通过不了的,所以当题目给定输入规模时,我们就要对提出的算法的时间复杂度的级别心里有数了。

二、解法一:暴力法

最笨的暴力法解决问题的逻辑,其实就是把题意用计算机语言实现一遍罢了。题目让找所有子数组的和,那么我们就先用两层循环找到所有的子数组subarray{i, j},然后再对i到j内的元素进行累加,时间复杂度为O(n^{3}),空间复杂度为O(1),C++代码如下:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) 
    {
        int n = nums.size(), res = 0;
        for(int i = 0; i < n; ++i)
        {
            for(int j = i; j < n; ++j)
            {
                int tempSum = 0;
                for(int k = i; k <= j; ++k)
                {
                    tempSum += nums[k];
                }
                if(tempSum == k) ++res;
            }
        }
        return res;
    }
};

三、解法二:优化的暴力法

解法一肯定过不了,所以考虑对解法一进行优化。在解法一中,对于subarray{i, j}中的每一个值,我们每次都老老实实得一个个加起来,做了大量重复的工作。简单思考后我们发现,只要得到子数组subarray{0, i}和sum_i子数组subarray{0, j}的和sum_j,那么子数组subarray{i, j}的和sum就等于sum_j - sum_i + nums[i](其中0 <= i <= j < n),而且所有subarray{0, i}的和的值都可以通过预处理原数组在O(n)时间内得到。

根据以上逻辑,可以得到优化的暴力法:定义一个新数组pre[n],其中pre[i]代表子数组subarray{0, i}的和,遍历一遍原数组就可以得到数组pre。然后还是要通过两重循环找到每一个子数组subarray{i, j},不过这一次subarray{i, j}的和可以用pre[j] - pre[i] + nums[i]在O(1)时间内得到,所以整个算法的时间复杂度为O(n+n^{2}) = O(n^{2}),空间复杂度为O(n),C++代码如下:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) 
    {
        int n = nums.size(), res = 0, sum = 0;
        
        vector<int> pre(n);
        for(int i = 0; i < n; ++i)
        {
            sum += nums[i];
            pre[i] = sum;
        }
        
        for(int i = 0; i < n; ++i)
        {
            for(int j = i; j < n; ++j)
            {
                if(pre[j] - pre[i] + nums[i] == k) 
                    ++res;
            }
        }
        
        return res;
    }
};

但是根据我们最开始的分析,这个O(n^{2})的方法还是过不了,所以还需要继续优化。

四、解法三:还记得Leetcode第一题两数之和的优化方法吗?

先思考一下解法二,我们在解法二中搞了一个pre数组,把问题转化为了找所有pre[j] - pre[i] == k的数目(先不用管加不加nums[i]的事,这些细节问题可以在写具体程序的时候再仔细考虑),为了实现这个目的我们用两重循环遍历数组。

那么怎么优化呢?突然发现这个式子pre[j] - pre[i] == k好眼熟啊,我们是不是在哪里见过啊?对!就是leetcode第一题两数之和!回想到两数之和是让我们找到所有nums[i] + nums[j] == k的情况(这里不管是加是减逻辑上是一样的),用暴力法也是O(n^{2}),我们当时是用什么方法优化成O(n)的来着?对!就是哈希表!想到这里这个题的优化逻辑其实就清楚起来了。

我们需要找到所有pre[j] - pre[i] == k(其中0 <= i <= j < n)的情况,当我们遍历到pre[j]时,暴力法会在i = 0~j的范围内一个一个找符合要求的值,需要的时间为O(n),但其实我们可以把j之前的所有pre[i]的结果都存在一个哈希表里,这样我们就可以在O(1)时间内找到我们需要的值了,所以算法总的时间复杂度为O(n)

总结一下:先得到pre数组,然后定义一个哈希表,在C++中我们可以使用unordered_map,哈希表中每一个元素的key代表pre[i]的值,value代表pre[i]出现的次数,因为pre数组可能有重复的元素。每次遍历到j时,只需要看有几个pre[j] - k在哈希表里即可,显然哈希表需要添加一个初始化值为make_pair(0, 1),为了正确处理pre[j] == k的情况,C++代码如下:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) 
    {
        int n = nums.size(), sum = 0, res = 0;
        vector<int> pre(n);
        unordered_map<int, int> mp;
        mp[0] = 1;
        
        for(int i = 0; i < n; ++i)
        {
            sum += nums[i];
            pre[i] = sum;
        }
        
        for(int i = 0; i < n; ++i)
        {
            if(mp.count(pre[i]-k) != 0)
                res += mp[pre[i]-k];
            mp[pre[i]] += 1;
        }
        
        return res;
    }
};

该方法时间复杂度为O(n+n) = O(n),空间复杂度为O(n+n) = O(n)

进一步分析发现,其实没必要对先把pre数组计算出来,只需要动态计算sum的值并把结果存到哈希表里就行了,所以去掉初始化pre数组的开销,时间复杂度可以优化为O(n) = O(n),空间复杂度也为O(n) = O(n),C++代码如下:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) 
    {
        unordered_map<int, int> mp;
        mp[0] = 1;
        int n = nums.size(), sum = 0, res = 0;
        
        for(int i = 0; i < n; ++i)
        {
            sum += nums[i];
            if(mp.count(sum - k) != 0)
                res += mp[sum - k];
            mp[sum] += 1;
        }
        
        return res;
    }
};

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值