[英雄星球六月集训LeetCode解题日报] 第八日 前缀和

一、 1838. 最高频元素的频数

链接: 1838. 最高频元素的频数

1. 题目描述

元素的 频数 是该元素在一个数组中出现的次数。

给你一个整数数组 nums 和一个整数 k 。在一步操作中,你可以选择 nums 的一个下标,并将该下标对应元素的值增加 1 。

执行最多 k 次操作后,返回数组中最高频元素的 最大可能频数 。

2. 思路分析

1.把每个数想象成柱状图,问题转化为,用最多k个格子,填一部分柱子,使这部分柱子等高,满足的话更新宽度。
2.排完序更好想。
3.那么实际上是求左上方三角形面积,用长方形-柱状图面积:s=r*(r-l+1)-sum_interval(l,r),需求s<=k,可以双指针。
4.按这个思路不用前缀和,直接排序+滑窗也可以。复杂度O(nlgn)

3. 代码实现

class Solution:
    def maxFrequency(self, nums: List[int], k: int) -> int:
        nums.sort()
        n = len(nums)
        presum = list(accumulate(nums,initial=0))
        def sum_interval(i,j):
            return presum[j+1]-presum[i]
        def calc_s(i,j):
            return nums[j]*(j-i+1)-sum_interval(i,j)
        ans = 1
        l=r=0
        while l<n and r<n:
            s = calc_s(l,r)
            if s <= k:
                ans = max(ans,r-l+1)
                r += 1
            else:
                l += 1
                if l==r:
                    r += 1        
        return ans

"""
1.把每个数想象成柱状图,问题转化为,用最多k个格子,填一部分柱子,使这部分柱子等高,满足的话更新宽度。
2.排完序更好想。
3.那么实际上是求做上方三角形面积,用长方形-柱状图面积:s=r*(r-l+1)-sum_interval(l,r),需求s<=k,可以双指针。
4.按这个思路不用前缀和,直接排序+滑窗也可以。
"""

二、 1590. 使数组和能被 P 整除

链接: 1590. 使数组和能被 P 整除

1. 题目描述

给你一个正整数数组 nums,请你移除 最短 子数组(可以为 空),使得剩余元素的 和 能被 p 整除。 不允许 将整个数组都移除。

请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。

子数组 定义为原数组中连续的一组元素。

2. 思路分析

看题解才会做:

  1. 用哈希表维护每个前缀和模p结果的最近下标
  2. 那么每次都可以去查找一下当前模-mod对应的值是否存在,存在表示满足需求可以移除。
  3. 复杂度O(n)

3. 代码实现

class Solution:
    def minSubarray(self, nums: List[int], p: int) -> int:
        n = len(nums)
        total = sum(nums)
        mod = total%p
        if mod == 0:
            return 0
        elif n == 1:
            return -1

        # 如果某个子数组的和%p==mod,就可以移除。        
        # 计算前缀和的过程中,用字典记录前边每个和模p结果的最近下标,
        # 如果当前模-mod存在,那么这个差就是这个区间的和,模==mod
        ans = n  # 不允许移除整个数组
        presum = 0
        pres = {0:-1}
        for i in range(n):
            presum += nums[i]
            cur_mod = presum%p
            need_mod = (cur_mod+p-mod)%p
            # print(presum,cur_mod,need_mod)
            need_idx = pres.get(need_mod,-2)
            if need_idx != -2 :
                ans = min(ans,i-need_idx)
                if ans == 1:
                    return 1

            pres[cur_mod] = i           

        if ans == n:  # 不允许移除整个数组
            return -1

        return ans

三、 1589. 所有排列中的最大和

链接: 1589. 所有排列中的最大和

1. 题目描述

有一个整数数组 nums ,和一个查询数组 requests ,其中 requests[i] = [starti, endi] 。第 i 个查询求 nums[starti] + nums[starti + 1] + … + nums[endi - 1] + nums[endi] 的结果 ,starti 和 endi 数组索引都是 从 0 开始 的。

你可以任意排列 nums 中的数字,请你返回所有查询结果之和的最大值。

由于答案可能会很大,请你将它对 109 + 7 取余 后返回。

2. 思路分析

题意实际上是需要访问次数越大的位置,放个越大的值。
那么由于数据范围是10^5,我们可以用树状数组计算每个点的访问次数。
对访问次数和原数组都逆序排序,对应的位置放对应的数计算即可。
这个方法比较慢,前缀和的思路还没想出来,待补充。
复杂度O(nlgn)
明白了前缀和了:
实际上树状数组的IUPQ模型是基于差分数组的:

  • sum(l,r,v):对差分数组区间两端操作。
  • get(index):相当于求差分数组截止index的前缀和。
    树状数组是为了查询任意区间的,这题我们只需要前缀和,那么可以去掉树状数组的lgN。
    直接展开差分数组做。
    当然可以优化掉差分数组的那层空间,直接计算freq。
    在这里插入图片描述

3. 代码实现

树状数组

class BinIndexTree:
    def __init__(self, size):
        self.size = size
        self.bin_tree = [0 for _ in range(size+5)]
    def add(self,i,v):
        while i<=self.size :
            self.bin_tree[i] += v
            i += self.lowbit(i)
    def update(self,i,v):
        val = v - (self.sum(i)-self.sum(i-1))
        self.add(i,val)
    def sum(self,i):
        s = 0
        while i >= 1:
            s += self.bin_tree[i]
            i -= self.lowbit(i)
        return s
    def lowbit(self,x):
        return x&-x
    def _point_query(self,i):
   		return self.sum(i)
    def _interval_add(self,l,r,v):
   		self.add(l,v)
   		self.add(r+1,-v)

class Solution:
    def maxSumRangeQuery(self, nums: List[int], requests: List[List[int]]) -> int:
        n = len(nums)
        mod = 10**9+7
        nums.sort(reverse=True)
        tree = BinIndexTree(n+5)
        for start,end in requests:
            tree._interval_add(start+1,end+1,1)
        freq = [0] * n
        for i in range(n):
            freq[i] = tree._point_query(i+1)
        ans = 0
        freq.sort(reverse=True)
        for i in range(n):
            if freq[i] ==0:
                break
            ans += freq[i]*nums[i]%mod
            ans %= mod        
        return ans

差分数组

class Solution:
    def maxSumRangeQuery(self, nums: List[int], requests: List[List[int]]) -> int:
        n = len(nums)
        mod = 10**9+7
        nums.sort(reverse=True)
        diff = [0]*(n+1)
        
        for start,end in requests:
            diff[start] += 1
            diff[end+1] -= 1
        freq = [0] * n
        s = 0
        for i in range(n):
            s += diff[i]
            freq[i] = s
        ans = 0
        freq.sort(reverse=True)
        for i in range(n):
            if freq[i] ==0:
                break
            ans += freq[i]*nums[i]%mod
            ans %= mod
        
        return ans

差分数组省去空间开销

class Solution:
    def maxSumRangeQuery(self, nums: List[int], requests: List[List[int]]) -> int:
        n = len(nums)
        mod = 10**9+7
        nums.sort(reverse=True)
        diff = [0]*(n+1)
        
        for start,end in requests:
            diff[start] += 1
            diff[end+1] -= 1
        freq = [0] * n
        s = 0
        for i in range(n):
            s += diff[i]
            freq[i] = s
        ans = 0
        freq.sort(reverse=True)
        for i in range(n):
            if freq[i] ==0:
                break
            ans += freq[i]*nums[i]%mod
            ans %= mod
        
        return ans

四、 1712. 将数组分成三个子数组的方案数

链接: 1712. 将数组分成三个子数组的方案数

1. 题目描述

我们称一个分割整数数组的方案是 好的 ,当它满足:

数组被分成三个 非空 连续子数组,从左至右分别命名为 left , mid , right 。
left 中元素和小于等于 mid 中元素和,mid 中元素和小于等于 right 中元素和。
给你一个 非负 整数数组 nums ,请你返回 好的 分割 nums 方案数目。由于答案可能会很大,请你将结果对 109 + 7 取余后返回。

2. 思路分析

  1. 先计算前缀和使区间和消耗降为O(1)
  2. 然后枚举中间数组的左端点,范围是[1,n-1]。
  3. 由于数据非负,我们发现左端点固定时,右端点向左移动会使mid变小、right变大,向右移动会使mid变大,right变小。
  4. 也就是说,左端点固定时,右端点是有个取值范围的[j_min,j_max],而这个范围可以二分。
  5. 二分效率较低O(nlgn),我们发现i,j_min,j_max一直是同向(右)移动的,因此可以三指针,复杂度降为O(n)

3. 代码实现

二分

class Solution:
    def waysToSplit(self, nums: List[int]) -> int:
        n = len(nums)
        mod = 10**9+7
        presum = list(accumulate(nums,initial=0))
        def sum_interval(i,j):
            return presum[j+1]-presum[i]
        ans = 0
        # 这段4796 ms 5.09%,O(nlgn)
        # 遍历mid左端点
        for i in range(1,n-1):
            left = sum_interval(0,i-1)
            # 左端点固定,右端点向右移动时,mid不断变大,right不断变小
            # 那么右端点最小值可以二分:这个区域的和要大于等于left
            j_min = bisect_left(range(n-1),left,lo=i,key=lambda j:sum_interval(i,j))
            # 如果找不到,j_min==n-1,则这个不能当左端点;如果以这个位置分割,right<mid,也不可以。
            if j_min >= n-1 or sum_interval(j_min+1,n-1) < sum_interval(i,j_min):
                continue
            # 右端点最大值必须满足:right-mid>=0,即mid-right<=0;随着j增加mid-right增加;
            # 由于上边排除了,因此一定能找到j_max
            j_max = bisect_right(range(n-1),0,lo=j_min,key=lambda j:sum_interval(i,j)-sum_interval(j+1,n-1))-1
            ans += j_max-j_min+1
            ans %= mod
        return ans

三指针

class Solution:
    def waysToSplit(self, nums: List[int]) -> int:
        n = len(nums)
        mod = 10**9+7
        presum = list(accumulate(nums,initial=0))
        def sum_interval(i,j):
            return presum[j+1]-presum[i]
        ans = 0

        # 随着i右移,j_min是右移的,j_max右移的,所以可以三指针寻找。O(n)
        j_min,j_max = 1,1
        for i in range(1,n-1):
            left = sum_interval(0,i-1)
            j_min = max(j_min,i)
            while j_min<n-1 and sum_interval(i,j_min) < left:
                j_min += 1
            # print(i,j_min,j_max,ans,left,sum_interval(i,j_min),sum_interval(j_min+1,n-1))
            if j_min >= n-1 :
                return ans
            if sum_interval(j_min+1,n-1) < sum_interval(i,j_min):
                continue
            while j_max+1<n-1 and sum_interval(i,j_max+1) <= sum_interval(j_max+2,n-1):
                j_max += 1
            ans += j_max-j_min+1
            ans %= mod
            
        return ans

五、 附每日一题 1037. 有效的回旋镖

链接: 1037. 有效的回旋镖

1. 题目描述

给定一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点,如果这些点构成一个 回旋镖 则返回 true 。

回旋镖 定义为一组三个点,这些点 各不相同 且 不在一条直线上 。

2. 思路分析

水题直接模拟。

  1. 数组长度是3。
  2. 每个点不同。
  3. 两个斜率不同。

3. 代码实现

模拟

class Solution:
    def isBoomerang(self, points: List[List[int]]) -> bool:
        n = len(points)
        if n  != 3:
            return False
        p1,p2,p3 = (points[0][0],points[0][1]),(points[1][0],points[1][1]),(points[2][0],points[2][1])

        if p1 == p2 or p1 == p3 or p2==p3:
            return False
        
        y1 = p1[1]-p2[1]
        y2 = p1[1]-p3[1]
        x1 = p1[0]-p2[0]
        x2 = p1[0]-p3[0]
        if x1*y2==x2*y1:
            return False
        return True
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值