前缀和基础训练
选自灵神题单,前缀和基础1.1 。
303. 区域和检索 - 数组不可变
给定一个整数数组 nums
,处理以下类型的多个查询:
- 计算索引
left
和right
(包含left
和right
)之间的nums
元素的 和 ,其中left <= right
实现 NumArray
类:
NumArray(int[] nums)
使用数组nums
初始化对象int sumRange(int i, int j)
返回数组nums
中索引left
和right
之间的元素的 总和 ,包含left
和right
两点(也就是nums[left] + nums[left + 1] + ... + nums[right]
)
示例 1:
输入:
["NumArray", "sumRange", "sumRange", "sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))
class NumArray:def __init__(self, nums: List[int]):self.s = [0] * (len(nums) + 1) # 前缀和数组,多加1是为了防止下面的循环越界# 可以将前缀和数组的下标看成是第“i”个数字,元素个数“i”从1开始for i, x in enumerate(nums):self.s[i + 1] = self.s[i] + xdef sumRange(self, left: int, right: int) -> int:return self.s[right + 1] - self.s[left]# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(left,right)
3427. 变长子数组求和
给你一个长度为 n
的整数数组 nums
。对于 每个 下标 i
(0 <= i < n
),定义对应的子数组 nums[start ... i]
(start = max(0, i - nums[i])
)。
返回为数组中每个下标定义的子数组中所有元素的总和。
子数组 是数组中的一个连续、非空 的元素序列。
示例 1:
输入: nums = [2,3,1]
输出: 11
解释:
下标 i | 子数组 | 和 |
---|---|---|
0 | nums[0] = [2] | 2 |
1 | nums[0 ... 1] = [2, 3] | 5 |
2 | nums[1 ... 2] = [3, 1] | 4 |
总和 | 11 |
总和为 11 。因此,输出 11 。
示例 2:
输入: nums = [3,1,1,2]
输出: 13
解释:
下标 i | 子数组 | 和 |
---|---|---|
0 | nums[0] = [3] | 3 |
1 | nums[0 ... 1] = [3, 1] | 4 |
2 | nums[1 ... 2] = [1, 1] | 2 |
3 | nums[1 ... 3] = [1, 1, 2] | 4 |
总和 | 13 |
总和为 13 。因此,输出为 13 。
class Solution:def subarraySum(self, nums: List[int]) -> int:pre = [0] * (len(nums) + 1) # 前缀和数组s = 0for i, x in enumerate(nums):pre[i + 1] = pre[i] + xfor i, x in enumerate(nums):s += pre[i + 1] - pre[max(0, i - x)]return s
2559. 统计范围内的元音字符串数
给你一个下标从 0 开始的字符串数组 words
以及一个二维整数数组 queries
。
每个查询 queries[i] = [li, ri]
会要求我们统计在 words
中下标在 li
到 ri
范围内(包含 这两个值)并且以元音开头和结尾的字符串的数目。
返回一个整数数组,其中数组的第 i
个元素对应第 i
个查询的答案。
注意: 元音字母是 'a'
、'e'
、'i'
、'o'
和 'u'
。
示例 1:
输入:words = ["aba","bcb","ece","aa","e"], queries = [[0,2],[1,4],[1,1]]
输出:[2,3,0]
解释:以元音开头和结尾的字符串是 "aba"、"ece"、"aa" 和 "e" 。
查询 [0,2] 结果为 2(字符串 "aba" 和 "ece")。
查询 [1,4] 结果为 3(字符串 "ece"、"aa"、"e")。
查询 [1,1] 结果为 0 。
返回结果 [2,3,0] 。
示例 2:
输入:words = ["a","e","i"], queries = [[0,2],[0,1],[2,2]]
输出:[3,2,1]
解释:每个字符串都满足这一条件,所以返回 [3,2,1] 。
class Solution:def vowelStrings(self, words: List[str], queries: List[List[int]]) -> List[int]:pre = [0] * (len(words) + 1) # 前缀和数组for i, w in enumerate(words):pre[i + 1] = pre[i]if w[0] in "aeiou" and w[-1] in "aeiou":pre[i + 1] += 1ans = []for q in queries:ans.append(pre[q[1] + 1] - pre[q[0]])return ans
3152. 特殊数组 II
如果数组的每一对相邻元素都是两个奇偶性不同的数字,则该数组被认为是一个 特殊数组 。
你有一个整数数组 nums
和一个二维整数矩阵 queries
,对于 queries[i] = [fromi, toi]
,请你帮助你检查
nums[fromi..toi]
是不是一个 特殊数组 。
返回布尔数组 answer
,如果 nums[fromi..toi]
是特殊数组,则 answer[i]
为 true
,否则,answer[i]
为 false
。
示例 1:
输入: nums = [3,4,1,2,6], queries = [[0,4]]
输出:[false]
解释:
子数组是 [3,4,1,2,6]
。2 和 6 都是偶数。
示例 2:
输入: nums = [4,3,1,6], queries = [[0,2],[2,3]]
输出:[false,true]
解释:
- 子数组是
[4,3,1]
。3 和 1 都是奇数。因此这个查询的答案是false
。 - 子数组是
[1,6]
。只有一对:(1,6)
,且包含了奇偶性不同的数字。因此这个查询的答案是true
。
class Solution:def isArraySpecial(self, nums: List[int], queries: List[List[int]]) -> List[bool]:# 前缀和记录[0,i]有多少个数字发生奇偶变化pre = [0] * (len(nums))for i in range(1, len(nums)):pre[i] = pre[i - 1]# 如果与前面的数字相比较,奇偶性不同,则增加一个变化if nums[i] % 2 != nums[i - 1] % 2:pre[i] += 1ans = [False] * len(queries)for i, (l, r) in enumerate(queries):# 计算[l+1,r]之间的奇偶性变化的次数个数是否与nums[r+1,l]个数相同# 不算nums[r]是因为数组只有一个元素时,奇偶不变化if pre[r] - pre[l] == r - l:ans[i] = Truereturn ans
1749. 任意子数组和的绝对值的最大值
给你一个整数数组 nums
。一个子数组 [numsl, numsl+1, ..., numsr-1, numsr]
的 和的绝对值 为 abs(numsl + numsl+1 + ... + numsr-1 + numsr)
。
请你找出 nums
中 和的绝对值 最大的任意子数组(可能为空),并返回该 最大值 。
abs(x)
定义如下:
- 如果
x
是负整数,那么abs(x) = -x
。 - 如果
x
是非负整数,那么abs(x) = x
。
示例 1:
输入:nums = [1,-3,2,3,-4]
输出:5
解释:子数组 [2,3] 和的绝对值最大,为 abs(2+3) = abs(5) = 5 。
示例 2:
输入:nums = [2,-5,1,-4,3,-2]
输出:8
解释:子数组 [-5,1,-4] 和的绝对值最大,为 abs(-5+1-4) = abs(-8) = 8 。
class Solution:def maxAbsoluteSum(self, nums: List[int]) -> int:s1 = [0] * (len(nums) + 1)for i, x in enumerate(nums):s1[i + 1] = s1[i] + x# 子数组和的绝对值等于abs(s[j]-s[i])# 所以要让两者之差尽可能大return max(s1) - min(s1)
2389. 和有限的最长子序列
给你一个长度为 n
的整数数组 nums
,和一个长度为 m
的整数数组 queries
。
返回一个长度为 m
的数组 answer
,其中 answer[i]
是 nums
中 元素之和小于等于 queries[i]
的 子序列 的 最大 长度 。
子序列 是由一个数组删除某些元素(也可以不删除)但不改变剩余元素顺序得到的一个数组。
示例 1:
输入:nums = [4,5,2,1], queries = [3,10,21]
输出:[2,3,4]
解释:queries 对应的 answer 如下:
- 子序列 [2,1] 的和小于或等于 3 。可以证明满足题目要求的子序列的最大长度是 2 ,所以 answer[0] = 2 。
- 子序列 [4,5,1] 的和小于或等于 10 。可以证明满足题目要求的子序列的最大长度是 3 ,所以 answer[1] = 3 。
- 子序列 [4,5,2,1] 的和小于或等于 21 。可以证明满足题目要求的子序列的最大长度是 4 ,所以 answer[2] = 4 。
示例 2:
输入:nums = [2,3,4,5], queries = [1]
输出:[0]
解释:空子序列是唯一一个满足元素和小于或等于 1 的子序列,所以 answer[0] = 0 。
class Solution:def answerQueries(self, nums: List[int], queries: List[int]) -> List[int]:# 由题目中的子序列和求和可以推出答案与元素顺序无关,所以可以排序(注意不是子数组,子数组就不能排序)# 排序后由于我们要让子序列的长度尽可能大,所以可以从第一个元素开始计算nums.sort()pre = [0] * (len(nums) + 1)for i, x in enumerate(nums):pre[i + 1] = pre[i] + xans = []for q in queries:ans.append(bisect_right(pre, q) - 1)return ans
3361. 两个字符串的切换距离
给你两个长度相同的字符串 s
和 t
,以及两个整数数组 nextCost
和 previousCost
。
一次操作中,你可以选择 s
中的一个下标 i
,执行以下操作 之一 :
- 将
s[i]
切换为字母表中的下一个字母,如果s[i] == 'z'
,切换后得到'a'
。操作的代价为nextCost[j]
,其中j
表示s[i]
在字母表中的下标。 - 将
s[i]
切换为字母表中的上一个字母,如果s[i] == 'a'
,切换后得到'z'
。操作的代价为previousCost[j]
,其中j
是s[i]
在字母表中的下标。
切换距离 指的是将字符串 s
变为字符串 t
的 最少 操作代价总和。
请你返回从 s
到 t
的 切换距离 。
示例 1:
输入: s = “abab”, t = “baba”, nextCost = [100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], previousCost = [1,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
输出: 2
解释:
- 选择下标
i = 0
并将s[0]
向前切换 25 次,总代价为 1 。 - 选择下标
i = 1
并将s[1]
向后切换 25 次,总代价为 0 。 - 选择下标
i = 2
并将s[2]
向前切换 25 次,总代价为 1 。 - 选择下标
i = 3
并将s[3]
向后切换 25 次,总代价为 0 。
示例 2:
**输入:**s = “leet”, t = “code”, nextCost = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], previousCost = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
输出: 31
解释:
- 选择下标
i = 0
并将s[0]
向前切换 9 次,总代价为 9 。 - 选择下标
i = 1
并将s[1]
向后切换 10 次,总代价为 10 。 - 选择下标
i = 2
并将s[2]
向前切换 1 次,总代价为 1 。 - 选择下标
i = 3
并将s[3]
向后切换 11 次,总代价为 11 。
class Solution:def shiftDistance(self, s: str, t: str, nextCost: List[int], previousCost: List[int]) -> int:pre = [0] * (26 * 2 + 1) # 向前切换的前缀和nex = [0] * (26 * 2 + 1) # 向后切换的前缀和for i in range(26 * 2 + 1):pre[i] = pre[i - 1] + previousCost[(i - 1) % 26]nex[i] = nex[i - 1] + nextCost[(i - 1) % 26]ans = 0for i, j in zip(s, t):if i == j:continues1 = ord(i) - 97s2 = ord(j) - 97# 建议在纸上模拟以下这两种情况且向前切换的代价与向后前后切换的代价if s1 < s2:# 向前切换的代价cost1 = pre[s1 + 26 + 1] - pre[s2 + 1]# 向后转换的代价cost2 = nex[s2] - nex[s1]else:cost1 = pre[s1 + 1] - pre[s2 + 1]cost2 = nex[s2 + 26] - nex[s1]ans += min(cost1, cost2)return ans
2055. 蜡烛之间的盘子
给你一个长桌子,桌子上盘子和蜡烛排成一列。给你一个下标从 0 开始的字符串 s
,它只包含字符 '*'
和 '|'
,其中 '*'
表示一个 盘子 ,'|'
表示一支 蜡烛 。
同时给你一个下标从 0 开始的二维整数数组 queries
,其中 queries[i] = [lefti, righti]
表示 子字符串 s[lefti...righti]
(包含左右端点的字符)。对于每个查询,你需要找到 子字符串中 在 两支蜡烛之间 的盘子的 数目 。如果一个盘子在 子字符串中 左边和右边 都 至少有一支蜡烛,那么这个盘子满足在 两支蜡烛之间 。
- 比方说,
s = "||**||**|*"
,查询[3, 8]
,表示的是子字符串"*||***\**\***|"
。子字符串中在两支蜡烛之间的盘子数目为2
,子字符串中右边两个盘子在它们左边和右边 都 至少有一支蜡烛。
请你返回一个整数数组 answer
,其中 answer[i]
是第 i
个查询的答案。
示例 1:
输入:s = "**|**|***|", queries = [[2,5],[5,9]]
输出:[2,3]
解释:
- queries[0] 有两个盘子在蜡烛之间。
- queries[1] 有三个盘子在蜡烛之间。
示例 2:
输入:s = "***|**|*****|**||**|*", queries = [[1,17],[4,5],[14,17],[5,11],[15,16]]
输出:[9,0,0,0,0]
解释:
- queries[0] 有 9 个盘子在蜡烛之间。
- 另一个查询没有盘子在蜡烛之间。
class Solution:def platesBetweenCandles(self, s: str, queries: List[List[int]]) -> List[int]:pre = [0] * (len(s) + 1) # pre[i]表示[0,i-1]中有多少个盘子for i, x in enumerate(s):pre[i + 1] = pre[i]if x == "*":pre[i + 1] += 1left = [-1] * (len(s) + 1) # 计算每个下标中左侧最近的蜡烛下标c = -1for i, x in enumerate(s):if s[i] == "|":c = ileft[i + 1] = cright = [len(s)] * (len(s) + 1) # 计算每个下标中右侧最近的蜡烛下标c = len(s)for i in range(len(s) - 1, -1, -1):if s[i] == "|":c = iright[i + 1] = cans = []for l, r in queries:r = left[r + 1] # 右边界l = right[l + 1] # 左边界if l < r: # 合法的边界# 计算[l,r]中有多少个盘子,就是计算s[l-1,r-1]中有多少个盘子(注意left与right数组向右扩展了一位)ans.append(pre[r] - pre[l])else: # 说明里面没有盘子ans.append(0)return ans
1744. 你能在你最喜欢的那天吃到你最喜欢的糖果吗?
给你一个下标从 0 开始的正整数数组 candiesCount
,其中 candiesCount[i]
表示你拥有的第 i
类糖果的数目。同时给你一个二维数组 queries
,其中 queries[i] = [favoriteTypei, favoriteDayi, dailyCapi]
。
你按照如下规则进行一场游戏:
- 你从第
0
天开始吃糖果。 - 你在吃完 所有 第
i - 1
类糖果之前,不能 吃任何一颗第i
类糖果。 - 在吃完所有糖果之前,你必须每天 至少 吃 一颗 糖果。
请你构建一个布尔型数组 answer
,用以给出 queries
中每一项的对应答案。此数组满足:
answer.length == queries.length
。answer[i]
是queries[i]
的答案。answer[i]
为true
的条件是:在每天吃 不超过dailyCapi
颗糖果的前提下,你可以在第favoriteDayi
天吃到第favoriteTypei
类糖果;否则answer[i]
为false
。
注意,只要满足上面 3 条规则中的第二条规则,你就可以在同一天吃不同类型的糖果。
请你返回得到的数组 answer
。
示例 1:
输入:candiesCount = [7,4,5,3,8], queries = [[0,2,2],[4,2,4],[2,13,1000000000]]
输出:[true,false,true]
提示:
1- 在第 0 天吃 2 颗糖果(类型 0),第 1 天吃 2 颗糖果(类型 0),第 2 天你可以吃到类型 0 的糖果。
2- 每天你最多吃 4 颗糖果。即使第 0 天吃 4 颗糖果(类型 0),第 1 天吃 4 颗糖果(类型 0 和类型 1),你也没办法在第 2 天吃到类型 4 的糖果。换言之,你没法在每天吃 4 颗糖果的限制下在第 2 天吃到第 4 类糖果。
3- 如果你每天吃 1 颗糖果,你可以在第 13 天吃到类型 2 的糖果。
示例 2:
输入:candiesCount = [5,2,6,4,1], queries = [[3,1,2],[4,10,3],[3,10,100],[4,100,30],[1,3,1]]
输出:[false,true,true,false,false]
class Solution:def canEat(self, candiesCount: List[int], queries: List[List[int]]) -> List[bool]:pre = [0] * (len(candiesCount) + 1) # 吃了数量为区间[pre[i-1],pre[i]]颗糖果,一定能吃到第i个种类的糖果for i in range(len(candiesCount)):pre[i + 1] = pre[i] + candiesCount[i]ans = []for ft, fd, d in queries:# 到第fd天为止最多吃了多少颗糖果,且这个糖果数量大于pre[ft](pre[ft]表示前ft-1类的糖果数量)# 到第fd-1天为止最少吃了多少颗糖果且这个糖果数量一定要小于pre[ft+1](pre[ft+1]表示前ft类的糖果数量)# 注意题目给的fd从0开始if (fd + 1) * d > pre[ft] and fd < pre[ft + 1]:ans.append(True)else:ans.append(False)return ans
53. 最大子数组和
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
class Solution:def maxSubArray(self, nums: List[int]) -> int:pre = [0] * (len(nums) + 1)for i, x in enumerate(nums):pre[i + 1] = pre[i] + xans = -inf # 记录最大子数组和min_pre = 0 # 记录最小前缀和for i in range(1, len(pre)):# 更新答案,同时能保证最小前缀和一定在最大前缀和前面(至少包含一个元素)ans = max(ans, pre[i] - min_pre)min_pre = min(min_pre, pre[i]) # 最后再更新最小前缀和return ans