1.前缀和模板
单纯的前缀和模板是好写的,给定一个数组a,要求得到一个数组prefix_sum,记录a的前缀和
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
s = 0
prefix_sum = []
for num in a:
s += num
prefix_sum.append(s)
print(prefix_sum)
2. LeetCode 560
比如LeetCode 560这道题:
给定一个数组,要求返回该数组中和为k的子数组的个数
那这道题就不好做,因为和为k的子数组的起点不一定是给定数组的第一个结点,有可能是给定数组的中间某段。
满足sum1 + k = s,其中s指前缀和,sum1是开头一段前缀和,k是中间一段数组的和。
从前往后遍历原数组,每走到一个元素就计算一下当前的前缀和,此时s是固定住的,如果当前段内有和为k的子数组,那么该子数组出现的次数就应该是sum1出现的次数,k不是前缀和,但是sum1是前缀和,这样就可以把中间一段的次数转换为sum1这个子前缀和出现的次数。
另外还有一个值得注意的点就是,有可能出现sum1为0的情况,也就是说和为k的子数组并不是出现在中间,而是正好从原数组的开头开始算,这种情况也是成立的,因此得考虑sum1为0,此时k直接等于s
还可以用defaultdict(int)来初始化prefix_sum,顺便提一嘴,prefix_sum最好用字典存储,而不是用数组存储,因为我们需要统计每个前缀和出现的次数,数组只能记录前缀和,如果要统计某个前缀和出现的次数,那就得
- 遍历一遍数组,判断sum1是否在数组中
- 如果在数组中,还得prefix_sum.count(sum1) 这两步做下来首先时间复杂度高,遍历一遍数组是O(n);其次代码写起来也比较麻烦。
但是如果用字典实现,那么if sum1 in prefix_sum可以直接判断sum1是否在前缀和中出现;字典的value也可以记录sum1出现的次数。
以nums = [1,2,3], k = 3为例,[1, 2], [3],出现两次,答案应该是2
from collections import defaultdict
nums = [1, 2, 3]
k, ans = 3, 0
s = 0
# 从collections把defaultdict引进来,相比于直接用{0:1}定义数组,
# defaultdict的方式可以自动初始化不存在的key的value为0,从而省去if else的逻辑
prefix = defaultdict(int)
prefix[0] = 1
for num in nums:
s += num
if s - k in prefix:
ans += prefix[s - k]
prefix[s] += 1
print(ans)
3. LeetCode 437
这题可以看成是560的进阶版本,它将数组中找一截和为k改成了在二叉树中找一截满足和为k。
二叉树本身就要比数组复杂一些,所以初步判断应该是递归 + 前缀和
有了560的理解之后,唯一一个不太好理解的地方就是递归遍历完左右子树之后还要prefix_sum[s] -= 1 我对这句代码的理解就是:遍历另一条从上往下的路径的时候,prefix_sum在前一条路径中的值是不能用的, 所以回溯的时候要减1
先定义二叉树
class TreeNode:
def __init__(self, val, left = None, right = None):
self.val = val
self.left = left
self.right = right
root = TreeNode(10)
root.left = TreeNode(5)
root.left.left = TreeNode(3)
root.left.left.left = TreeNode(3)
root.left.left.right = TreeNode(-2)
root.left.right = TreeNode(2)
root.left.right.right = TreeNode(1)
root.right = TreeNode(-3)
root.right.right = TreeNode(11)
算法解题
from collections import defaultdict
k = 8
prefix_sum = defaultdict(int)
prefix_sum[0] = 1
ans, s = 0, 0
'''
递归到某个点,看的就是从根节点到该点的路径上是否存在某一小段的和为k
'''
def dfs(root, s):
if root == None: return
global ans
s += root.val
ans += prefix_sum[s - k]
prefix_sum[s] += 1
if root.left: dfs(root.left, s)
if root.right: dfs(root.right, s)
'''
回溯的时候需要剪枝,剪枝出现在递归调用的后面
'''
prefix_sum[s] -= 1
dfs(root, 0)
print(ans)