第二题
题目:给定一个字符串,只包括r,e,d求子字符串的数量,要求该子字符串中r,e,d都出现且出现的次数相同。
例子:rrededrde
输出:4
解释:r[red]edrde, rred[edr]de, r[rededr]de, rreded[rde]
关键考点:动态哈希+差异状态设计
思路
- 暴力法:
枚举左右端点,统计区间内的r、e、d字符个数。o(n^3) - 暴力优化:
区间的字符个数可用前缀和之差获得。o(n^2) - dp大法:
用map维护所有以i为结尾的子字符串的统计情况,位置i+1的更新规则为:在map中搜索所有加入第i+1个字符串后满足题意的子字符串个数并累加到答案中,然后根据该字符更新map中的所有字符串统计情况,更新得到以i+1为结尾的子字符串的统计情况。
更新map比较费时,最差情况下也是o(n^2) - o(n)解法:
如果我们知道当前位置3⃣️的前缀和(3,2,2),则我们要去找前面有多少个前缀和为(2,1,1)或者(1,0,0)的,例如图中的位置1⃣️和位置2⃣️,都能使以位置3⃣️为区间右端点内的r、e、d的个数相等。很自然的我们可以在从左至右的遍历过程中用map维护所有的前缀和,但这样做在对当前位置查找左端点时要枚举很多种r、e、d的统计情况。例如若当前前缀为(4,3,3),则需在map中get(3,2,2)、(2,1,1)、(1,0,0)。我们发现实际上我们并不在乎r、e、d的绝对个数是多少,我们实际关注的是他们之间个数的差异。(4,3,3)、(3,2,2)、(2,1,1)、(1,0,0)都能统一表示为r、e、d相对于r的个数(0,-1,-1)。所以在做前缀时,我们直接记录并保存差异即可。只需遍历一遍,时间复杂度为o(n)。
from collections import defaultdict
string = 'rrededrde'
n = len(string)
pre_map = defaultdict(int)
cnt = defaultdict(int)
ans = 0
for i in range(n):
cnt[string[i]] += 1
r_cnt, e_cnt, d_cnt = cnt['r'], cnt['e'], cnt['d']
pre = f'{0}_{e_cnt-r_cnt}_{d_cnt-r_cnt}'
ans += pre_map[pre]
pre_map[pre] += 1
print(ans)
ps:其实dp大法和最优解法思想差不多,只不过dp法是通过更新dp的值来保持其时效正确性,而最优法是采取丢入map的技巧,减少维护的代价。
第三题
n, k = map(int, input().split())
nums = list(map(int, input().split()))
# 2**30
nums_bina = [[0]*30 for _ in range(n)]
for i, num in enumerate(nums):
bina = list(map(int, bin(num)[2:][::-1]))
for j in range(len(bina)):
nums_bina[i][j] += bina[j]
i = 29
ans = [0]*29
def dfs(i, k, nums, nums_bina):
if i < 0: return
if sum(nums_bina[j][i] for j in range(len(nums))) == k:
res = 2**29 -1
for j in range(len(nums)):
if nums_bina[j][i] == 1:
res &= nums[j]
bina = list(map(int, bin(res)[2:][::-1]))
for k in range(i):
ans[k] = bina[k]
return
elif sum(nums_bina[j][i] for j in range(len(nums))) > k:
nums_ = []
nums_bina_ = []
for j in range(len(nums)):
if nums_bina[j][i] == 1:
nums_.append(nums[j])
nums_bina_.append(nums_bina[j])
ans[i] = 1
dfs(i-1, k, nums_, nums_bina_)
else:
dfs(i-1, k, nums, nums_bina)
dfs(29, k, nums, nums_bina)
ans_out = 0
for i in range(len(ans)):
if ans[i]:
ans_out += 2 ** i
print(ans_out)