前缀和-哈希表

#前缀和 大概就是使用哈希表或字典的键统计前缀和 值统计前缀和的位置或个数
#求个数用for i in s:
#求长度用for i in cnt: 需要初始化cnt[0]=-1 都是键是同一个 用值相减


"""
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
子数组是数组中元素的连续非空序列。
"""
#前缀和之差为k 那么子数组之和即为k 前缀和x-y=k 已有x 表示y=x-k
#在哈希表中找x-k 若有则说明前缀和之差为k
#哈希表 键 前缀和 值 相同前缀和的个数
from collections import defaultdict
from typing import List
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        #整数数组有负数 求和不能用滑动窗口 因为不能保证和一直是增大
        s = [0] * (len(nums) + 1) #前缀和 两个之间的差是k时就是一种情况
        for i,x in enumerate(nums):
            s [i + 1] = s[i] + x
        cnt = defaultdict(int) #哈希表
        n = 0
        for i in s: #s是前缀和的列表
            n += cnt[i - k] #在哈希表中找是否有i-k 如果有说明存在两个前缀和的差为k
            #你可以检查 i - k 是否已经出现过。如果出现过,说明从某个位置到当前位置的子数组和为 k
            cnt[i] += 1
        return n


"""
给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的非空 子数组 的数目。
子数组 是数组中 连续 的部分。
"""
#子数组之和可被k整除 前缀和之差为子数组之和 那么前缀和之差可被k整除 (x-y)%k==0
#已有x 表示y x%k==y%k 如上可知前缀和对k取余相同 则说明前缀和之差可被k整除
#统计前缀和对k取余后相等的个数
#哈希表 键 前缀和对k取余的结果 值 相同余数的个数
class Solution:
    def subarraysDivByK(self, nums: List[int], k: int) -> int:
        #子数组之和可被k整除的非空子数组数目
        #依然不能滑动窗口
        #两个前缀和之差能够被k整除 (x-y)%k==0
        #这个式子还可以写成 x%k == y%k 就是找前缀和对k取余相等的个数
        s = [0] * (len(nums) + 1)
        cnt = defaultdict(int)
        ans = 0
        for i, x in enumerate(nums):
            s[i + 1] = s[i] + x
        for i in s:
            a = i % k #都行 加k是为了防止余数是负数 调整成正数
            ans += cnt[a]
            cnt[a] += 1
        return ans


"""
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
"""
#数组中的元素只分成两类 那么一类为1 另一类为0
#转换为子数组的和为0 那么前缀和之差也为0 x-y=0 那么y=x 找前缀和相同的值
#该题求长度长度 因此哈希表的值存储的是前缀和的位置 键 前缀和

class Solution:
    def findMaxLength(self, nums: List[int]) -> int:
        cnt = defaultdict(int)
        cnt[0] = -1
        s = 0
        ans = 0
        for i, x in enumerate(nums):
            if x == 1:
                s += 1
            else:
                s -= 1
            if s in cnt: #s是前缀和 s在哈希表中是键
                ans = max(ans, i - cnt[s])
            else:
                cnt[s] = i #把第一次出现该前缀和的位置存到值中
        return ans


"""
给你一个整数数组 nums 和一个整数 k ,
如果 nums 有一个 好的子数组 返回 true ,否则返回 false:
一个 好的子数组 是: 长度 至少为 2 ,且子数组元素总和为 k 的倍数。
注意:子数组 是数组中 连续 的部分。
如果存在一个整数 n ,令整数 x 符合 x = n * k ,则称 x 是 k 的一个倍数。
0 始终 视为 k 的一个倍数。
"""
#子数组之和为k的倍数 前缀和之差也为k的倍数 即为前缀和之差可被k整除 长度用前缀和的位置相减来表示
#(x-y)%k==0 已有x 表示y x%k==y%k 如上可知前缀和对k取余相同
class Solution:
    def checkSubarraySum(self, nums: List[int], k: int) -> bool:
        cnt = defaultdict(int)
        cnt[0] = -1 #索引
        s = 0
        for i, x in enumerate(nums):
            s += x #前缀和
            a = s % k
            if a < 0:
                a = a + k #使等效的余数归到一类
            if a in cnt:
                if i - cnt[a] >= 2:
                    return True
            else:
                cnt[a] = i
        return False



"""
给你一个长度为 n 的数组 nums 和一个 正 整数 k 。
如果 nums 的一个子数组中,第一个元素和最后一个元素 差的绝对值恰好为 k ,我们称这个子数组为好的。
换句话说,如果子数组 nums[i..j] 满足 |nums[i] - nums[j]| == k ,
那么它是一个好子数组。
请你返回 nums 中 好子数组的最大和,如果没有好子数组,返回 0 。
"""
#子数组的第一个元素和最后一个元素的差的绝对值为k
#|a[i]-a[j]|=k 即为 a[i]=a[j]+k 或 a[i]=a[j]-k
#求的是好子数组的最大和 还是前缀和之差最大 x-y 最大 那么y 最小
#s[j+1]-s[i] 最大 s[i] 最小
#与位置长度无关 不用管他长还是短 只考虑前缀和的大小 不考虑前缀和的位置
from collections import defaultdict
from math import inf
from typing import List

class Solution:
    def maximumSubarraySum(self, nums: List[int], k: int) -> int:
        # 哈希表,记录每个数字对应的最小前缀和
        cnt = defaultdict(lambda: inf)
        m = -inf  # 初始化最大和为负无穷
        s = 0  # 当前前缀和
        for i in nums:
            # 检查是否存在满足条件的子数组
            # 条件:|nums[j] - nums[i]| = k,即 nums[j] = nums[i] + k 或 nums[j] = nums[i] - k
            if (i - k) in cnt:
                m = max(m, s + i - cnt[i - k])
            if (i + k) in cnt:
                m = max(m, s + i - cnt[i + k])
            # 更新当前数字对应的最小前缀和
            cnt[i] = min(cnt[i], s)
            # 更新前缀和
            s += i
        # 如果没有任何满足条件的子数组,返回 0
        return m if m > -inf else 0

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值