Leetcode刷题总结--前缀和

Leetcode刷题总结

前缀和:

前缀和常用与求解子数组的问题,最常见的就是子数组求和问题。

定义 p r e [ i ] pre[i] pre[i]为数组从0~i位置的和, s u m ( i , j ) sum(i,j) sum(i,j)表示从i位置到j位置的子数组的和,则有 s u m ( i , j ) = p r e ( j ) − p r e ( i ) sum(i,j) = pre(j) - pre(i) sum(i,j)=pre(j)pre(i)

根据上述的推论可以很轻易的求解出任意一段子数组的和

1. Leetcode560 和为k的子数组

题目描述:给定一个整数数组和一个整数 **k,**你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1:
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

思路

根据题目的描述,我们可以想到使用前缀和来解决这一题。即只要两个前缀和之差满足 p r e [ j ] − p r e [ i ] = k pre[j]-pre[i]=k pre[j]pre[i]=k即视为找到了一种情况。

算法步骤

  1. 顺序遍历数组,计算每个位置的前缀和 p r e [ i ] pre[i] pre[i]
  2. 寻找前缀和之差为k的组合,计数

进阶优化:

在算法步骤2中,如果是使用蛮力的方法,需要用双循环来寻找满足条件的组合,时间复杂度为Θ(n^2)

不妨把 p r e [ i ] pre[i] pre[i] p r e [ j ] pre[j] pre[j]的组合记录为一种状态,那么我们很容易就可以分析出来上述方法低效的原因,因为没有记住这种状态。下一次遇到相同的 p r e [ i ] pre[i] pre[i] p r e [ j ] pre[j] pre[j],仍然需要做一次判断。

因此优化的方法就是使用哈希表记录下当前已经判断过的前缀和,(参考Leetcode3 两数之和)利用哈希表的快速查找的特点,可以使时间复杂度降低为Θ(n)。当遍历到前缀和 p r e [ i ] pre[i] pre[i]时,哈希表记录下 p r e [ i ] pre[i] pre[i],如果后续遍历有 p r e [ j ] pre[j] pre[j]满足 p r e [ j ] − k = = p r e [ i ] pre[j]-k == pre[i] pre[j]k==pre[i],其差值必然可以在哈希表中找到。

代码

int subarraySum(vector<int> &nums, int k)
{
    int len = nums.size();
    if (len == 0) return 0;
    unordered_map<int, int> m;
    m[0] = 1; //没有元素时,前缀和为0(对应特例为一个值为k的元素) 
    int sum = 0;
    int count = 0;
    for (int i = 0; i < len; i++)
    {
        sum += nums[i];
        if (m.find(sum - k) != m.end())
        {
            count += m[sum - k];
        }
        m[sum]++;
    }
    return count;
}
2. Leetcode523 连续子数组和

题目描述:给定一个包含 非负数 的数组和一个目标 整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。

示例 1:

输入:[23,2,4,6,7], k = 6
输出:True
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6。

思路

本题也是一个前缀和的题目,只不过这次求得前缀和是K的倍数。因此重点就在于如何去判断当前前缀和是不是K的倍数,蛮力法?时间复杂度太高。这里要提一个平时很容易忽视的点,模运算。

如果一个数i是K的倍数,那么有 i ( m o d k ) = 0 i(mod k)=0 i(modk)=0

如果两个数之差是K的倍数,那么有 ( j − i ) m o d k = 0 ( j- i)mod k=0 (ji)modk=0,即 i ( m o d k ) = j ( m o d k ) i(mod k)= j(mod k) i(modk)=j(modk)

那么把 j j j看作是前缀和 p r e [ j ] pre[j] pre[j],把 i i i看作是前缀和 p r e [ i ] pre[i] pre[i],如果两个前缀和模K后结果相同,证明它们之间的子数组和为K的倍数。

算法步骤:

  1. 计算所有前缀和模K的结果

  2. 找寻两个相同的前缀和,且其包含的子数组数目大于2。依然使用哈希表,这次哈希表存储当前位置前缀和对应的下标。

代码:

bool checkSubarraySum(vector<int>& nums, int k)
{
    int len = nums.size();
    if(len<=1) return false;
    unordered_map<int,int> m;
    // 第一个参数存储presum%k 第二个参数存储下标
    m[0]=-1; //没有开始前,前缀和为0,下标为-1
    int presum = 0;
    for(int i=0;i<len;i++)
    {
        presum += nums[i];
        //提前将结果模k,不影响结果但是可以加速过程,同时处理了K = 0时的特殊情况
        if(k!=0) presum = presum % k; 
        if(m.count(presum)==1)
        {
            if(i-m[presum]>1)
                return true;
            //如果前缀余数相同,但是长度为1,不必更新下标,因为子数组越长,越容易满足题目要求
        }
        else 
        {
            m[presum] = i;
        }
    }
    return false;
}
3. Leetcode1371 每个元音字母包含偶数次的最长子字符串

**题目描述:**给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。

示例 :

输入:s = “eleetminicoworoep”
输出:13
解释:最长子字符串是 “leetminicowor” ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。

思想:

要求寻找一个子串,其中所有元音字母出现的次数均为偶数次。如何统计一个子串里面的一个元音字母个数?前缀和。如何统计所有元音字母个数?二维前缀和数组 p r e [ 5 ] [ j ] pre[5][j] pre[5][j],前一个维度代表元音字母。问题就转化为求解所有的满足 ( p r e [ 5 ] [ j ] − p r e [ 5 ] [ i ] ) m o d 2 = = 0 (pre[5][j]-pre[5][i] )mod 2 == 0 (pre[5][j]pre[5][i])mod2==0 时的 m a x ( j − i ) max(j-i) max(ji)

算法步骤

  1. 记录每个位置的元音字母的前缀和
  2. 根据前缀和寻找最大的满足题意的子串长度

**优化:**程序的基本思想已经写好了,但是搜寻过程并不是那么直接,蛮力做的话需要遍历很多次,做很多判断才行。根据以前的优化思路,我们需要记录状态。那么这一题要如何记录状态呢?

如果一个区间内的某个元音字母出现的次数为偶数次,这个区间两个端点的前缀和之差模2的结果相同

如果一个区间内所有元音字母出现的次数为偶数次,这个区间两个端点的所有元音字母前缀和应该相同。

把所有元音字母前缀和当作一个状态记录下来,如果存在两个相同状态的前缀和,那么就证明对应区间满足题意。

奇数模2为1,偶数模2为0,因此可以用一个五位二进制来记录前缀和的状态。最多有32个状态需要存储,与之前相比需要的空间大大减小。

代码

int findTheLongestSubstring(string s)
{
    int ans = 0, status = 0, n = s.length();
    vector<int> pos(1 << 5, -1);
    pos[0] = 0;
    for (int i = 0; i < n; i++)
    {
        if (s[i] == 'a')
        {
            status ^= 1 << 0;
        }
        else if (s[i] == 'e')
        {
            status ^= 1 << 1;
        }
        else if (s[i] == 'i')
        {
            status ^= 1 << 2;
        }
        else if (s[i] == 'o')
        {
            status ^= 1 << 3;
        }
        else if (s[i] == 'u')
        {
            status ^= 1 << 4;
        }

        // 出现重复状态
        if(~pos[status])
        {
            ans = max(ans, i + 1 - pos[status]);
        }
        // 没有出重复状态就把最早的状态记录在内
        else
        {
            pos[status] = i + 1;
        }
    }
    return ans;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值