类型题Ⅳ:前缀和

类型题Ⅳ:前缀和

文章目录

前缀和

前缀和 就是数组 第 0 项当前项 的总和。比如数组 nums,那么它的前缀和 prefixSum[x] 就表示 nums 从第 0 项到第 x 项的总和。

     p


     r


     e


     f


     i


     x


     S


     u


     m


     [


     x


     ]


     =


     n


     u


     m


     s


     [


     0


     ]


     +


     n


     u


     m


     s


     [


     1


     ]


     +


     .


     .


     .


     +


     n


     u


     m


     s


     [


     x


     ]



     prefixSum[x] = nums[0] + nums[1] + ...+ nums[x] 


 prefixSum[x]=nums[0]+nums[1]+...+nums[x] 




     n


     u


     m


     s


     [


     x


     ]


     =


     p


     r


     e


     f


     i


     x


     S


     u


     m


     [


     x


     ]


     −


     p


     r


     e


     f


     i


     x


     S


     u


     m


     [


     x


     −


     1


     ]



     nums[x] = prefixSum[x] - prefixSum[x-1] 


 nums[x]=prefixSum[x]−prefixSum[x−1] 




     n


     u


     m


     [


     i


     ]


     +


     .


     .


     .


     +


     n


     u


     m


     [


     j


     ]


     =


     p


     r


     e


     f


     i


     x


     S


     u


     m


     [


     j


     ]


     −


     p


     r


     e


     f


     i


     x


     S


     u


     m


     [


     i


     −


     1


     ]



     num[i] + ... + num[j] = prefixSum[j] - prefixSum[i-1] 


 num[i]+...+num[j]=prefixSum[j]−prefixSum[i−1]<br> 特别注意,由于 `i` 可以为 0,`i-1` 会出现数组越界,所以前缀和数组一般需要在 0 位置扩充一个 0,便于计算数组 nums 中第 0 项到第 k 项之和。

前缀和数组每一项 dp[i] 都是原数组从第 0 项到第 i 项的总和,所以如果 j 大于 i,那么 dp[j] 就一定是包含 dp[i] 的。

相关题目

560. 和为K的子数组

思路一暴力解法,双重循环,每次固定一个 i,用 j 循环往前走,每到一个位置判断子数组和是否为 target,是则 count + 1。时间复杂度为

    O


    (



     n


     2



    )



   O(n^2)


O(n2),会超时。

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        if not nums: return 0
        n = len(nums)
        count = 0
        for i in range(n):
            sum = 0
            for j in range(i, -1, -1):
                sum += nums[j]
                if sum == k: count += 1
        return count

思路二前缀和解法。假设目标值为 k,对于任意子数组的和(假设为 x),为了使得这个子数组和能满足目标值 k,就要找另一个子数组的和(假设为 y),使得 y - x = k。

可以创建一个与原数组大小相同的新数组 dp,即前缀和数组。该数组每个位置上都保存了原数组从 0 到对应位置的所有元素的和。从后向前遍历前缀和数组,每到一个位置,如果不等于目标值 target,则向前寻找是否有 dp[j] - target,找到一个 count 数就加 1。

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        n = len(nums)
        prefix = [0]  # 前缀和数组要额外加一个0
        # 创建前缀和数组
        for i in range(n):
            prefix.append(prefix[i] + nums[i])
        # 遍历计数
        count = 0
        for i in range(n, 0, -1):
            target = prefix[i] - k
            for j in range(i-1, -1, -1):
                if prefix[j] == target:
                    count += 1
        return count

这样遍历其实仍然是双重循环,时间复杂度并没有降低,运行一下依然超时。

思路二改进:考虑用空间换时间,使用 前缀和 + 哈希表。用哈希表存储前缀和,键为 “前缀和的值”,值为 “该前缀和的出现次数”。从左向右遍历前缀和数组,将当前前缀和的值存入哈希表中,如果已经存在则直接加 1,并在哈希表中查找是否有键等于 “当前前缀和 - target”,若存在则给计数器 count 加上该键对应的值。

j &gt; i,则我们的目标是 prefix[j] - prefix[i] = target

  • 如果从左向右遍历前缀和数组,每次来到的位置都是 j,在哈希表里的查找目标就是 pre[i] = pre[j] - target- 如果从右向左遍历前缀和数组,每次来到的位置都是 i,在哈希表里的查找目标就是 pre[j] = target + pre[i] ``` class Solution: def subarraySum(self, nums: List[int], k: int) -> int:
      n = len(nums)
      prefix = [0]
      count, dict = 0, {<!-- -->0:1}
      # 从左向右遍历
      for i in range(n):
          pre = prefix[i] + nums[i]
          prefix.append(pre)
          # 查找目标值,若无则返回-1
          c = dict.get(pre - k, -1) # 查找目标pre[i] = pre[j]-target
          if c != -1:
              count += c
          # 当前前缀和添加到哈希表(注意先找目标值,再添加到哈希表)
          if pre not in dict:
              dict[pre] = 1
          else:
              dict[pre] += 1
      return count

只需要一层循环,时间复杂度为 O(n)。用哈希表加速运算的思路适合 <mark>不关心具体的解,仅关心解的个数</mark> 的情况。

[1248. 统计「优美子数组」](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/)

**思路:前缀和 + 差分**。约定数组 `pre` 中 `pre[i]` 表示以第 `i` 个元素为结尾的子数组 `[nums[0]...nums[i]]` 中奇数的个数,那么 `pre[i]` 可以由前置状态 `pre[i-1]` 计算得到,即:`pre[i] = pre[i-1] + 1 if nums[i] 是奇数 else pre[i-1]`。

那么题目要求 `[j...i]` 这个子数组的奇数个数恰好为 k,可以将这个条件转化为 `pre[i] - pre[j-1] == k`。

在实际求解时,从左向右计算 `pre` 数组中各个位置的状态(即奇数个数),然后将这个个数登记在哈希表里,生成键值对:“奇数个数-出现次数”,每次登记时,顺便在哈希表看一看有没有目标值 `pre[j-1]`,如果有就可以将个数累加在最终的答案 sum 上。`pre` 数组并不需要真的建立,只需要维护前一个值即可。

**时间复杂度**:




        O


        (


        n


        )



       O(n)


    O(n),遍历长度为 n 的数组<br> **空间复杂度**:




        O


        (


        n


        )



       O(n)


    O(n),需要哈希表记录奇数出现频次,最坏情况下哈希表长度等于原数组

class Solution: def numberOfSubarrays(self, nums: List[int], k: int) -> int: n = len(nums) pre = 0 res, dict = 0, {0:1} for i in range(n): pre = pre + 1 if nums[i] % 2 != 0 else pre dict[pre] = dict.get(pre, 0) + 1 res += dict.get(pre - k, 0) return res


[长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/):给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0.

**思路:前缀和 + 二分查找**。额外创建一个数组用于存储原数组的前缀和,由于题目规定原数组均为正整数,所以该前缀和数组可保证是递增的。前缀和数组 `sums[i]` 表示从 `nums[0]` 到 `nums[i-1]` 的元素和。对于每个元素下标,可通过二分查找得到一个大于或等于 `i` 的最小下标 `j`,使得 `sums[j] - sums[i-1] >= s`,此时更新新子数组的最小长度。

**时间复杂度**:




        O


        (


        n


        l


        o



         g


         n



        )



       O(nlog_n)


    O(nlogn​)<br> **空间复杂度**:




        O


        (


        n


        )



       O(n)


    O(n)

class Solution: def minSubArrayLen(self, s: int, nums: List[int]) -> int: if not nums: return 0

    n = len(nums)
    ans = n + 1
    sums = [0]
    for i in range(n):
        sums.append(sums[-1] + nums[i])

    for i in range(1, n + 1):
        target = s + sums[i - 1]
        bound = bisect.bisect_left(sums, target)  # 二分查找
        if bound != len(sums):
            ans = min(ans, bound - (i - 1))

    return 0 if ans == n + 1 else ans


另外,最开始写的暴力法肯定会超时:

class Solution: def minSubArrayLen(self, s: int, nums: List[int]) -> int: n = len(nums) dp = [n+1 for _ in range(n)] res = n+1 for i in range(n): cur = 0 for j in range(i,-1,-1): cur += nums[j] if cur >= s: res = min(res, i-j+1) if res < n+1: return res else: return 0

```

posted @ 2021-01-06 13:49  刘桓湚  阅读(83)  评论(0编辑  收藏  举报