以示例 1 为例,$\textit{nums}=[1,2,1,1,3]$。
假如好子序列的最后一个数是
-
单独作为一个子序列。 -
和好子序列的倒数第二个数相同。 -
和好子序列的倒数第二个数不同。由于 左边只有 ,那么就需要知道,最后一个数是 的好子序列的最长长度。
在这个过程中,我们需要跟踪如下关键信息:
- 子序列的最后一个数
。下文把该子序列称作「以 结尾的子序列」。 - 子序列中有多少个
seq[i] != seq[i + 1]
,也就是「相邻不同」数对的个数。
遍历到
对于
- 不选:$f[x][j]$ 不变。但其实这种情况无需考虑,因为直接把
加到以 结尾的好子序列的末尾,并不会增加「相邻不同」数对的个数。为了让子序列尽量长,选肯定是更优的。 - 选,且
与子序列末尾相同,或者作为子序列的第一个数:$f[x][j]$ 增加 。 - 选,且
与子序列末尾不同:设末尾元素为 ,我们需要知道最大的 。
对于第三种决策,如果暴力枚举
如何更快地计算最大的
当
-
是最大值。 -
是次大值,其中 。
于是:
- 如果
,那么最大的 就是 。 - 如果
,那么最大的 就是 。
这样我们就无需暴力枚举
把最大的
对于不同的
由于在计算
class Solution:
def maximumLength(self, nums: List[int], k: int) -> int:
fs = {}
records = [[0] * 3 for _ in range(k + 1)]
for x in nums:
if x not in fs:
fs[x] = [0] * (k + 1)
f = fs[x]
for j in range(k, -1, -1):
f[j] += 1
if j > 0:
mx, mx2, num = records[j - 1]
f[j] = max(f[j], (mx if x != num else mx2) + 1)
# records[j] 维护 fs[.][j] 的 mx, mx2, num
v = f[j]
p = records[j]
if v > p[0]:
if x != p[2]:
p[2] = x
p[1] = p[0]
p[0] = v
elif x != p[2] and v > p[1]:
p[1] = v
return records[k][0]
class Solution {
public int maximumLength(int[] nums, int k) {
Map<Integer, int[]> fs = new HashMap<>();
int[][] records = new int[k + 1][3];
for (int x : nums) {
int[] f = fs.computeIfAbsent(x, i -> new int[k + 1]);
for (int j = k; j >= 0; j--) {
f[j]++;
if (j > 0) {
int mx = records[j - 1][0], mx2 = records[j - 1][1], num = records[j - 1][2];
f[j] = Math.max(f[j], (x != num ? mx : mx2) + 1);
}
// records[j] 维护 fs[.][j] 的 mx, mx2, num
int v = f[j];
int[] p = records[j];
if (v > p[0]) {
if (x != p[2]) {
p[2] = x;
p[1] = p[0];
}
p[0] = v;
} else if (x != p[2] && v > p[1]) {
p[1] = v;
}
}
}
return records[k][0];
}
}
class Solution {
public:
int maximumLength(vector<int>& nums, int k) {
unordered_map<int, vector<int>> fs;
vector<array<int, 3>> records(k + 1);
for (int x : nums) {
auto& f = fs[x];
f.resize(k + 1);
for (int j = k; j >= 0; j--) {
f[j]++;
if (j) {
auto& r = records[j - 1];
int mx = r[0], mx2 = r[1], num = r[2];
f[j] = max(f[j], (x != num ? mx : mx2) + 1);
}
// records[j] 维护 fs[.][j] 的 mx, mx2, num
int v = f[j];
auto& p = records[j];
if (v > p[0]) {
if (x != p[2]) {
p[2] = x;
p[1] = p[0];
}
p[0] = v;
} else if (x != p[2] && v > p[1]) {
p[1] = v;
}
}
}
return records[k][0];
}
};
func maximumLength(nums []int, k int) int {
fs := map[int][]int{}
records := make([]struct{ mx, mx2, num int }, k+1)
for _, x := range nums {
if fs[x] == nil {
fs[x] = make([]int, k+1)
}
f := fs[x]
for j := k; j >= 0; j-- {
f[j]++
if j > 0 {
p := records[j-1]
m := p.mx
if x == p.num {
m = p.mx2
}
f[j] = max(f[j], m+1)
}
// records[j] 维护 fs[.][j] 的 mx,mx2,num
v := f[j]
p := &records[j]
if v > p.mx {
if x != p.num {
p.num = x
p.mx2 = p.mx
}
p.mx = v
} else if x != p.num && v > p.mx2 {
p.mx2 = v
}
}
}
return records[k].mx
}
只需要维护
- 如果
,那么转移和前文是一样的,用 更新 的最大值。 - 如果
,强行使用 ,相当于用 更新 的最大值。注意这个转移是不符合状态定义的(应该用 ),但由于 越大,能选的数越多,所以 ,考虑到第二种决策会用 更新 ,这不会低于 。所以用 更新 的最大值其实不会改变 。
综上所述,可以直接用
上面代码中的
上面代码需要判断
具体请看 视频讲解 第四题,欢迎点赞关注!
class Solution:
def maximumLength(self, nums: List[int], k: int) -> int:
fs = {}
mx = [0] * (k + 2)
for x in nums:
if x not in fs:
fs[x] = [0] * (k + 1)
f = fs[x]
for j in range(k, -1, -1):
f[j] = max(f[j], mx[j]) + 1
mx[j + 1] = max(mx[j + 1], f[j])
return mx[-1]
class Solution {
public int maximumLength(int[] nums, int k) {
Map<Integer, int[]> fs = new HashMap<>();
int[] mx = new int[k + 2];
for (int x : nums) {
int[] f = fs.computeIfAbsent(x, i -> new int[k + 1]);
for (int j = k; j >= 0; j--) {
f[j] = Math.max(f[j], mx[j]) + 1;
mx[j + 1] = Math.max(mx[j + 1], f[j]);
}
}
return mx[k + 1];
}
}
class Solution {
public:
int maximumLength(vector<int>& nums, int k) {
unordered_map<int, vector<int>> fs;
vector<int> mx(k + 2);
for (int x : nums) {
auto& f = fs[x];
f.resize(k + 1);
for (int j = k; j >= 0; j--) {
f[j] = max(f[j], mx[j]) + 1;
mx[j + 1] = max(mx[j + 1], f[j]);
}
}
return mx[k + 1];
}
};
func maximumLength(nums []int, k int) int {
fs := map[int][]int{}
mx := make([]int, k+2)
for _, x := range nums {
if fs[x] == nil {
fs[x] = make([]int, k+1)
}
f := fs[x]
for j := k; j >= 0; j-- {
f[j] = max(f[j], mx[j]) + 1
mx[j+1] = max(mx[j+1], f[j])
}
}
return mx[k+1]
}
- 时间复杂度:$\mathcal{O}(nk)$,其中
是 的长度。 - 空间复杂度:$\mathcal{O}(nk)$。
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
欢迎关注 B站@灵茶山艾府