今天想学习前缀和,然后把昨天的拓扑排序学习了。
但是呢? 我们先惯例打开每日一题,看看难不难哈哈哈哈
然后呢,真的是无比简单的一题啊,那就让我们来练手吧
文章目录
每日一题:1137. 第 N 个泰波那契数
https://leetcode-cn.com/problems/n-th-tribonacci-number/
题目描述
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。
示例 1:
输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4
示例 2:
输入:n = 25
输出:1389537
提示:
0 <= n <= 37
答案保证是一个 32 位整数,即 answer <= 2^31 - 1。
思路分析
非常非常典型的动态规划题,
时间复杂度:O(n)
空间复杂度:如果用滑动数组的话,O(1)
需要注意边界处理: 0,1,2 的赋值
用时: 21:28 - 21:31
class Solution:
def tribonacci(self, n: int) -> int:
if n == 0:return 0
if n==1 or n == 2: return 1
pre_list = [0,1,1]
for i in range(3,n+1):
pre_list = pre_list[1:] + [sum(pre_list)]
return pre_list[-1]
真的就so easy。
那么接下来我们就开始干活吧,虽然很晚了,但是我还是挺想吃板栗的,但是要忍住呀!
– 21:50的时候还是没忍住,吃了一袋。。
怎么说呢,经过我最近的反思,特别是从 课程表2 那题痛的领悟,我发现了,还是要先背模版再做题目,效率贼高,增益也大。所以我们先来看看前缀和的定义,以及它的模版是个什么玩意儿~
前缀和:
假设我们有一个字符串ABCDE,什么是这个单词的前缀,A、AB、ABC、ABCD、ABCDE就是这个单词的前缀,就是从第一个字母开始,依次往后拼接。E、ED、EDC、EDCB、EDCBA被称为这个单词的后缀。
前缀和大致分为两类,一维前缀和、二维前缀和。
适用场景:
一维前缀和的最主要目的就是求子数组的和的大小.
例如元素a[1]到a[3]的和。
a[1] + a[2] + a[3] = sum[3] - sum[0]
预处理数组的前缀和,可以达到
O
(
1
)
O(1)
O(1)时间得到区间和
模版
一维前缀和
数组nums :
1- 定义一个数组s:长度为 len(nums)+1, 其中s[i]代表:前i个元素的和
2- 初始化 s[0] = 0, 原始元素的下标从1开始。
3-求l 到r 区间和: 前r个元素的和减去前l-1个元素的和即: s[r] - s[l-1]
二维前缀和
学习了模版之后,我们现在就是勇敢牛牛啦,冲呀!
一维前缀和例题:1365. 有多少小于当前数字的数字
https://leetcode-cn.com/problems/how-many-numbers-are-smaller-than-the-current-number/
题目描述:
给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。
换而言之,对于每个 nums[i] 你必须计算出有效的 j 的数量,其中 j 满足 j != i 且 nums[j] < nums[i] 。
以数组形式返回答案。
示例 1:
输入:nums = [8,1,2,2,3]
输出:[4,0,1,1,3]
解释:
对于 nums[0]=8 存在四个比它小的数字:(1,2,2 和 3)。
对于 nums[1]=1 不存在比它小的数字。
对于 nums[2]=2 存在一个比它小的数字:(1)。
对于 nums[3]=2 存在一个比它小的数字:(1)。
对于 nums[4]=3 存在三个比它小的数字:(1,2 和 2)。
示例 2:
输入:nums = [6,5,4,8]
输出:[2,1,0,3]
示例 3:
输入:nums = [7,7,7,7]
输出:[0,0,0,0]
提示:
2 <= nums.length <= 500
0 <= nums[i] <= 100
思路:
说实话,我没明白这道题和前缀和的关系,
最简单的方法:
计数+ 排序
用时: 9:52- 10:00
class Solution:
def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]:
# 元素计数
nums_dict = Counter(nums)
# 对元素大小排列
nums_sort = sorted(nums_dict.keys())
# 前缀和计算前面元素的数量
nums_sort_dict = {nums_sort[0]:0}
for ind,num in enumerate(nums_sort[1:],start=1):
nums_sort_dict[num] = nums_sort_dict[nums_sort[ind-1]] + nums_dict[nums_sort[ind-1]]
# 返回
return [nums_sort_dict[num] for num in nums]
其实写着写着就明白为啥用前缀和了。
简单题做完了,我们来一题中等题:,话说山姆的有机板栗可真好吃呀
一维前缀和:1248. 统计「优美子数组」
题目描述
给你一个整数数组 nums 和一个整数 k。
如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。
示例 1:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。
示例 3:
输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
思路
用时:10:05- 10:13
咋么说呢,如果不告诉我是一个前缀和的题目,我绝对想不到。
感觉确实要滑窗啊。
所以这个前缀和的定义可以是啥呢:答案呼之欲出,前i的元素中奇数的数量。
然后计算之后计算区间和,然后返回相等的数量。
那么就开搞吧~
特殊用例: k==1
class Solution:
def numberOfSubarrays(self, nums: List[int], k: int) -> int:
# 前缀和pre_sum[i]表示前i个元素中奇数的数量
pre_sum = [0] + [0 for num in nums]
for ind,num in enumerate(nums,start=1):
flg = 1 if num %2 ==1 else 0
pre_sum[ind] = pre_sum[ind-1] + flg
# 计算区间的数量,并且区间和数量为k的之后计数
len_nums = len(nums)
res = 0
# 注意所有下标从1开始
# i->j的区间和 j>= i
for i in range(1,len_nums+1): # 这里的边界需要注意
for j in range(i,len_nums+1):
tmp_sum = pre_sum[j] - pre_sum[i-1]
if tmp_sum == k:
res += 1
return res
时间复杂度:
o
(
n
2
)
o(n^2)
o(n2),前缀和遍历o(n),区间和遍历
o
(
n
2
)
o(n^2)
o(n2),取
o
(
n
2
)
o(n^2)
o(n2)
空间复杂度:
o
(
n
)
o(n)
o(n) 记录前缀和的空间
然后依然超时了: 18 / 38 个通过测试用例
是一个特别大的,所以我们可以优化,去掉一些不必要的遍历,比如:
(1)区间长度小于k的,奇数的个数肯定小于k
(2)当区间长度为m的时候,奇数的个数大于k了,那么更长的肯定更大于,也没必要判断啦。
然后做了这两个优化之后:20 / 38 个通过测试用例
怎么说呢,就很扯淡。
那就再加个优化,从后缀和大于的地方开始遍历。
然后又扯淡的通过了23 / 38 个通过测试用例
– 我不想优化了,看题解吧。
然后我自己想到的优化,就是分成两半,分别计算。
题解思路:前缀和+ 查分
[i…j] 子数组中奇数个数恰好为k,条件可以转化为:
p
r
e
s
u
m
[
j
]
−
p
r
e
s
u
m
[
i
−
1
]
=
=
k
pre_sum[j]-pre_sum[i-1] ==k
presum[j]−presum[i−1]==k
即:
p
r
e
s
u
m
[
i
−
1
]
=
=
p
r
e
s
u
m
[
j
]
−
k
pre_sum[i-1] == pre_sum[j] - k
presum[i−1]==presum[j]−k
所以可以建立频次数组:cnt记录pre_sum[j] 出现的次数,从左往右更新答案,cnt[pre_sum[j-1]-k] 可以O(1) 拿到。
同时 pre_sum[i]的计算只与前一项有关可以使用滑动数组。
时间复杂度: O(n),其中 n为数组的大小。我们只需要遍历一遍数组即可求得答案。
**空间复杂度:**其n 为数组的大小。频次数组
c
n
t
cnt
cnt 记录的最大值不会超过
n
n
n ,因此只需要额外的
O
(
n
)
O(n)
O(n) 的空间。
class Solution:
def numberOfSubarrays(self, nums: List[int], k: int) -> int:
cnt = [0] * (len(nums) +1 )
cnt[0] = 1
odd ,ans = 0,0
for num in nums:
if num%2==1:
odd += 1
if odd >= k:
ans += cnt[odd-k]
cnt[odd] += 1
return ans
我只能说官方题解这个写的太妙了,比如那一长串好太多了,还是要多学习呀!
参考链接:
1- 前缀和+模版 https://blog.youkuaiyun.com/yl_puyu/article/details/109173844