The Sejolyn Memo

Back

题目:2786.访问数组中的位置使分数最大

记忆化搜索#

dfs(i,j)dfs(i, j)表示:当前考虑到下标 ii,且上一个选中的数奇偶性为 jj 时,从 iin1n - 1 能获得的最大额外分数。

此时,对于 v=nums[i]v = nums[i],它的奇偶性为 curr=vmod2curr = v \bmod 2

  1. curr==jcurr == j奇偶性相同
    • 选:既然奇偶性相同,不需要减 xx。那么选了肯定比不选好,因为 v>0v > 0 且没有改变后续的奇偶性
    • 决策:v+dfs(i+1,j)v + dfs(i + 1, j)
  2. currjcurr \ne j奇偶性不同
    • 不选:维持现状,继续往后看。结果为 dfs(i+1,j)dfs(i + 1, j)
    • 选:获得了 vv 的分数,但是要扣除 xx,且状态改变了,从此往后”上一个数”的奇偶性变成了 currcurr(即 j1j \oplus 1
    • 决策:vx+dfs(i+1,j1)v - x + dfs(i + 1, j \oplus 1)

所以整体的递归思路应该是:

dfs(i,j)={v+dfs(i+1,j)if (vmod2==j)max(dfs(i+1,j),vx+dfs(i+1,j1))if (vmod2j)dfs(i, j) = \begin{cases} v + dfs(i+1, j) & \text{if } (v \bmod 2 == j) \\ \max(dfs(i+1, j), v - x + dfs(i+1, j \oplus 1)) & \text{if } (v \bmod 2 \ne j) \end{cases}

int[] nums;
long[][] memo;
int x;
public long maxScore(int[] nums, int x) {
	this.nums = nums;
	this.x = x;
	int n = nums.length;
	memo = new long[n][2];
	for (long[] row : memo) {
		Arrays.fill(row, -1);
	}
	return dfs(0, nums[0] % 2);
}

private long dfs(int i, int j) {
	if (i == nums.length) {
		return 0;
	}
	if (memo[i][j] != -1) {
		return memo[i][j];
	}

	int curr = nums[i] & 1;
	if (curr == j) {
		// 奇偶性相同必选
		return memo[i][j] = dfs(i + 1, j) + nums[i];
	} else {
		// 奇偶性不同,选或不选
		return memo[i][j] = Math.max(dfs(i + 1, j),  nums[i] - x + dfs(i + 1, j ^ 1));
	}
}
java

递推#

理解了上面的状态转移后,递推就非常简单了:

  • f[i][0]f[i][0] 代表在 [i,n1][i, n - 1] 中以偶数开头的子序列的最大得分;
  • f[i][1]f[i][1] 代表在 [i,n1][i, n - 1] 中以奇数开头的子序列的最大得分。
public long maxScore(int[] nums, int x) {
	int n = nums.length;
	long[][] f = new long[n + 1][2];

	for (int i = n - 1; i >= 0; --i) {
		int v = nums[i];
		int r = v & 1;
		// 相同必选
		f[i][r] = v + f[i + 1][r];
		// 不同,选或不选
		f[i][r ^ 1] = Math.max(f[i + 1][r ^ 1], f[i + 1][r] + v - x);
    }

	return f[0][nums[0] % 2];
}
java

迭代 DP#

倒推#

在递推中,我们只会访问 f[i+1]f[i + 1] 的值,所以没有必要全部维护。

public long maxScore(int[] nums, int x) {
	long[] f = new long[2];
	for (int i = nums.length - 1; i >= 0; --i) {
		int v = nums[i];
		int r = v & 1;
		// 不同,选或不选
		f[r ^ 1] = Math.max(f[r ^ 1], f[r] + v - x);
		// 相同必选
		f[r] = v + f[r];
	}
	return f[nums[0] % 2];
}
java

注意:这里的更新顺序不能改变,即必须先更新 f[r1]f[r \oplus 1] 后更新 f[r]f[r](也可以用一个临时变量 tmptmp 先占位 f[r]f[r])。

正推#

由于第一个数必须选,因此正推的逻辑可能会更清晰。

正推逻辑:当前数 vv 的奇偶性为 rr

  • f[0]f[0] 代表以偶数结尾的子序列最大得分,f[1]f[1] 代表以奇数结尾的子序列最大得分
  • 只能更新 f[r]f[r],因为选了数 vv 后,结尾奇偶性一定为 rr
  • f[r1]f[r \oplus 1] 保持不变,因为当前数 vv 无法改变以异号结尾的子序列的最大分数

状态转移方程: f[r]=Math.max(f[r]+v,f[r1]+vx)f[r] = Math.max(f[r] + v, f[r \oplus 1] + v - x)

public long maxScore(int[] nums, int x) {
	long[] f = new long[2];
	Arrays.fill(f, Long.MIN_VALUE / 2); // 防止减x溢出
	f[nums[0] & 1] = nums[0];
	for (int i = 1; i < nums.length; ++i) {
		int v = nums[i];
		int r = v & 1;
		f[r] = Math.max(f[r] + v, f[r ^ 1] - x + v);
	}
	return Math.max(f[0], f[1]);
}
java

参考#

Leetcode:2786.访问数组中的位置使分数最大
https://sejolyn.fyi/blog/dsa/2786
Author Sejolyn
Published at December 21, 2025
Comment seems to stuck. Try to refresh?✨