目录
[1. 划分题&记忆化搜索 @cache](#1. 划分题&记忆化搜索 @cache)
[3003. 执行操作后的最大分割数量 -- 记忆化搜索](#3003. 执行操作后的最大分割数量 -- 记忆化搜索)
[3144. 分割字符频率相等的最少子字符串 -- 记忆化搜索](#3144. 分割字符频率相等的最少子字符串 -- 记忆化搜索)
[698. 划分为k个相等的子集](#698. 划分为k个相等的子集)
[3117. 划分数组得到最小的值之和](#3117. 划分数组得到最小的值之和)
[2963. 统计好分割方案的数目](#2963. 统计好分割方案的数目)
[2. 执行操作 思路题](#2. 执行操作 思路题)
[3347. 执行操作后元素的最高频率 II](#3347. 执行操作后元素的最高频率 II)
[3397. 执行操作后不同元素的最大数量](#3397. 执行操作后不同元素的最大数量)
[2273. 移除字母异位词后的结果数组 -- 快慢指针](#2273. 移除字母异位词后的结果数组 -- 快慢指针)
[56. 合并区间](#56. 合并区间)
[1553. 吃掉 N 个橘子的最少天数](#1553. 吃掉 N 个橘子的最少天数)
1. 划分题&记忆化搜索 @cache
3003. 执行操作后的最大分割数量 -- 记忆化搜索
对字符串s 可以最多修改一个字符,然后进行规则划分(前缀不能超过k个不同字符)

记忆化搜索:( i, mask, changed)
现在到第几个字符;这一段前面出现了哪些字符 用位运算进行mask;有没有changed过。
mask : 26个字母是否出现;
不修改:加上 i ,mask中的数量超过 k,就要在 i-1 分割,分割+1;否则接着mask。
如果 changed 可以修改,枚举改成哪个字母,找 max。
python
class Solution:
def maxPartitionsAfterOperations(self, s: str, k: int) -> int:
@cache
def dfs(i: int, mask: int, changed: bool) -> int:
if i == len(s):
return 1
# 不改 s[i]
bit = 1 << (ord(s[i]) - ord('a'))
new_mask = mask | bit
if new_mask.bit_count() > k:
# 分割出一个子串,这个子串的最后一个字母在 i-1
# s[i] 作为下一段的第一个字母,也就是 bit 作为下一段的 mask 的初始值
res = dfs(i + 1, bit, changed) + 1
else: # 不分割
res = dfs(i + 1, new_mask, changed)
if changed:
return res
# 枚举把 s[i] 改成 a,b,c,...,z
for j in range(26):
new_mask = mask | (1 << j)
if new_mask.bit_count() > k:
# 分割出一个子串,这个子串的最后一个字母在 i-1
# j 作为下一段的第一个字母,也就是 1<<j 作为下一段的 mask 的初始值
res = max(res, dfs(i + 1, 1 << j, True) + 1)
else: # 不分割
res = max(res, dfs(i + 1, new_mask, True))
return res
return dfs(0, 0, False)
3144. 分割字符频率相等的最少子字符串 -- 记忆化搜索
对每个位置 i,往后枚举找可划分区间 (i,j)
在循环找末端点位置 j 的时候,用 Counter 计数,并用 all 判断是否次数都同。
python
class Solution:
def minimumSubstringsInPartition(self, s: str) -> int:
n = len(s)
@cache
def dfs(i:int)->int:
if i==n:
return 0
# Counter 进行计数
c=Counter()
ans=inf
for j in range(i,n):
c[s[j]]+=1
# 优化不用判断,字母种类不是字母数目的因数的话 不可能都相等了
if (j-i+1)%len(c)!=0:
continue
cc=c[s[j]]
# 可划分就 dfs
if all(cc==ac for ac in c.values()):
ans=min(ans,dfs(j+1)+1)
return ans
return dfs(0)
698. 划分为k个相等的子集
记忆化搜索:
判断能不能把 n 个数 划分为 k 个相等的子集。 先看因数和最大值判断。
二进制 mask 表示有没有使用过。 从 2^n-1 -> 0
dfs (s, p) 代表对当前状态 s,当前余数 p,是否可以继续成功划分。
nums.sort() 从小到大,如果 nums[i] 塞到 p 会爆,后面更大的也会爆(减少判断次数)。
python
class Solution:
def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
all = sum(nums)
if all % k:
return False
per = all // k
nums.sort() # 方便下面剪枝
if nums[-1] > per:
return False
n = len(nums)
@cache
def dfs(s, p):
if s == 0:
return True
for i in range(n):
if nums[i] + p > per:
break
if s >> i & 1 and dfs(s ^ (1 << i), (p + nums[i]) % per): # p + nums[i] 等于 per 时置为 0
return True
return False
return dfs((1 << n) - 1, 0)
动态规划 dp[s] 存状态 s 当前的余数。
(因为不管什么顺序放,最后剩下来的一个桶(余数)是一定的)
没塞过,且不爆的 -> 转换为可达状态。
python
class Solution:
def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
all = sum(nums)
if all % k:
return False
per = all // k
nums.sort()
if nums[-1] > per:
return False
n = len(nums)
dp = [-1] * (1 << n)
dp[0] = 0
for i in range(0, 1 << n):
if dp[i] == -1:
continue
for j in range(n):
if dp[i] + nums[j] > per:
break
if (i >> j & 1) == 0:
next = i | (1 << j)
if dp[next] == -1:
dp[next] = (dp[i] + nums[j]) % per
return dp[(1 << n) - 1] != -1
3117. 划分数组得到最小的值之和
目标划分为 每一段的& 和给出的第二个数组 对应相等。 数组的"值"定义为 数组最后一个元素。
dfs(i, j, _and) 现在在位置 i,正在划分第 j 段,第 j 段累积的&值为 _and。
当前 _and 与对应值相等则可以进行划分。
python
class Solution:
def minimumValueSum(self, nums: List[int], andValues: List[int]) -> int:
n, m = len(nums), len(andValues)
@cache
def dfs(i: int, j: int, and_: int) -> int:
if n - i < m - j: # 剩余元素不足
return inf
if j == m: # 分了 m 段
return 0 if i == n else inf
and_ &= nums[i]
res = dfs(i + 1, j, and_) # 不划分
if and_ == andValues[j]: # 划分,nums[i] 是这一段的最后一个数
res = min(res, dfs(i + 1, j + 1, -1) + nums[i])
return res
ans = dfs(0, 0, -1)
return ans if ans < inf else -1
2963. 统计好分割方案的数目
所有相同数字必须出现在一段的分割。分割的方案数。
先看哪些位置是必须划分在一起的,如果 m 段,中间有 m-1 个位置是可拼接的,结果即为 2^(m-1)

出现过的数都要被包含,要到最右端 max_r。
先第一遍循环,使用覆盖,得到 r[x] 为 x 出现的最右边位置。
python
class Solution:
def numberOfGoodPartitions(self, nums: List[int]) -> int:
r = {}
for i, x in enumerate(nums):
r[x] = i
m = max_r = 0
for i, x in enumerate(nums):
max_r = max(max_r, r[x])
if max_r == i: # 区间无法延长
m += 1
return pow(2, m - 1, 1_000_000_007)
2. 执行操作 思路题
3347. 执行操作后元素的最高频率 II
可以对 numOperations 个元素调整 [-k,k],最多可以多少相同的数。
nums[i] 可以变成 nums[i] - k ~ nums[i] + k 区间里的数,只需要知道哪个位置被最多的区间包含。
区间+1用差分实现,后一轮统计时 sum += diff
还有修改元素个数条件,用 cnt[x] 记录 x 本身有多少个。上界是 cnt[x] + numOperations。
所以为 min(sum_x,cnt[x] + numOperations)
由于这个值 sum_x 只受diff影响,后一项只受 cnt[x] 影响。
所以最后结果的最大值位置 只可能出现在 x,x-k,x+k+1。只需要记录、比较这些位置。
python
class Solution:
def maxFrequency(self, nums: List[int], k: int, numOperations: int) -> int:
cnt = defaultdict(int)
diff = defaultdict(int)
for x in nums:
cnt[x] += 1
diff[x] # 把 x 插入 diff,以保证下面能遍历到 x
diff[x - k] += 1 # 把 [x-k,x+k] 中的每个整数的出现次数都加一
diff[x + k + 1] -= 1
ans = sum_d = 0
for x, d in sorted(diff.items()):
sum_d += d
ans = max(ans, min(sum_d, cnt[x] + numOperations))
return ans
3397. 执行操作后不同元素的最大数量
可以把每个数上下调整k,问最多可以有多少不同的数。
可以建模为军训站队,这个区域最多可以站多少人?最左边的同学会移动到最左边,方便右边的同学好站。
把数从小到大安排,每次安排到能放到的(x-k,x+k)区间,最左边的空位(前一个人pre 之后)。
python
class Solution:
def maxDistinctElements(self, nums: List[int], k: int) -> int:
nums.sort()
ans = 0
pre = -inf # 记录每个人左边的人的位置
for x in nums:
x = min(max(x - k, pre + 1), x + k)
if x > pre:
ans += 1
pre = x
return ans
2273. 移除字母异位词后的结果数组 -- 快慢指针
一个词如果和前一个词异位(同样的单词组成 只是顺序不同)就删掉。
快慢指针的思想:快指针遍历,慢指针把要保留的单词存下来。(实现原空间覆盖)
python
class Solution:
def removeAnagrams(self, words: List[str]) -> List[str]:
k = 1
for s, t in pairwise(words): # 快指针遍历 pairwise取连续两个词
if sorted(s) != sorted(t): # 慢指针存
words[k] = t
k += 1
del words[k:] # 删掉后面的
return words
56. 合并区间
开始位置从前到后排序,和前一个区间不重叠就添加,重叠就通过max结束位置进行合并。
python
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key=lambda x: x[0])
ans = []
for interval in intervals:
# 不重叠 添加新区间
if not ans or ans[-1][1] < interval[0]:
ans.append(interval)
# 与最后一个区间合并
else:
ans[-1][1] = max(ans[-1][1], interval[1])
return ans
1553. 吃掉 N 个橘子的最少天数

只吃一个橘子,在 n 比较大的时候一定是比较劣的选择。
实际从 除以2和除以3 中选,吃一个只是为了吃掉对应的余数。
开记忆化 + 函数自我调用。
python
class Solution:
@cache
def minDays(self, n: int) -> int:
if n<=1:
return n
return min(n%2+1+self.minDays(n//2), n%3+1+self.minDays(n//3))