2786、访问数组中的位置使得分数最大

  1. 题目
    1. 示例 1:
    2. 示例 2:
  2. 审题
    1. 深度优先搜索 + 记忆化
    2. 代码实现
    3. 动态规划
    4. 代码实现
    5. 动态规划 优化

题目

给你一个下标从 0 开始的整数数组 nums 和一个正整数 x 。

你 一开始 在数组的位置 0 处,你可以按照下述规则访问数组中的其他位置:

如果你当前在位置 i ,那么你可以移动到满足 i < j 的 任意 位置 j 。
对于你访问的位置 i ,你可以获得分数 nums[i] 。
如果你从位置 i 移动到位置 j 且 nums[i] 和 nums[j] 的 奇偶性 不同,那么你将失去分数 x 。
请你返回你能得到的 最大 得分之和。

注意 ,你一开始的分数为 nums[0] 。

示例 1:

输入:nums = [2,3,6,1,9,2], x = 5
输出:13
解释:我们可以按顺序访问数组中的位置:0 -> 2 -> 3 -> 4 。
对应位置的值为 2 ,6 ,1 和 9 。因为 6 和 1 的奇偶性不同,所以下标从 2 -> 3 让你失去 x = 5 分。
总得分为:2 + 6 + 1 + 9 - 5 = 13 。

示例 2:

输入:nums = [2,4,6,8], x = 3
输出:20
解释:数组中的所有元素奇偶性都一样,所以我们可以将每个元素都访问一次,而且不会失去任何分数。
总得分为:2 + 4 + 6 + 8 = 20 。

提示:

  • $2 <= nums.length <= 10^5$
  • $1 <= nums[i], x <= 10^6$

审题

每一步有多个选择,到有每一步都有case需要处理进一步求最优解,动态规划!!!

那么咱们先从深度优先搜索 + 剪枝的方式入手。枚举全部case,并求最大值。

深度优先搜索 + 记忆化

$ T_n=O(n) \ C_n=O(n)$

子问题:在下标[i, n - 1]中选择一个子序列,其第一个数的奇偶性为same (也就是摸2的结果为same)时的最大得分,对应于dfs(i,same)

  • 如过 v mod 2 != same,由于奇偶性不一致,有负向收益(减去x),可以不选(不选可能是最优解的路线),那么:问题进一步变成:在下标[i+1, n - 1]中选择一个子序列,其第一个数的奇偶性为same (也就是摸2的结果为same)时的最大得分。即:dfs(i,same) = dfs(i+1, same)
  • 如过 v mod 2 = same,奇偶性一致,收益是正向的,必须选择。那么有两个case:
    • case1: 在下标[i+1, n - 1]中选择一个子序列,其第一个数的奇偶性为same (也就是摸2的结果为same)时的最大得分。即:dfs(i,same) = dfs(i+1, same) + v
    • case2: 在下标[i+1, n - 1]中选择一个子序列,其第一个数的奇偶性为 same ^ 1 (奇偶性相反的case)时的最大得分。即:dfs(i,same) = dfs(i+1, same ^ 1) + v
    • 这两种取最优解,得到dfs(i,same)
      • dfs(i,same) = max(dfs(i+1, same) + v,dfs(i+1, same ^ 1)) + v

递归边界:dfs(n,same) = 0,因为n没有后续元素了。

递归入口:dfs(0, nums[0] mod 2),是最终的答案(递归归到了dfs(0, nums[0] mod 2)
记忆化:将dfs(i,same)的结果用数组存起来 memo[n][2]

代码实现

class Solution {
    public long maxScore(int[] nums, int x) {
        int n = nums.length;
        long[][] memo = new long[n][2];

        for (int i = 0; i < n; i++) {
            Arrays.fill(memo[i], -1);
        }

        return dfs(0, nums[0] % 2, nums, x, memo);
    }

    private long dfs(int i, int same, int[] nums, int x, long[][] memo) {
        if (i == nums.length) {
            return 0;
        }

        if (memo[i][same] != -1) {
            return memo[i][same];
        }

        if (nums[i] % 2 != same) {
            // pass nums[i] try find max score
            return memo[i][same] = dfs(i + 1, same, nums, x, memo);
        }

        long chooseSame = dfs(i + 1, same, nums, x, memo);
        long chooseNotSame = dfs(i + 1, same ^ 1, nums, x, memo);
        return memo[i][same] = Math.max(chooseSame, chooseNotSame - x) + nums[i];
    }
}

动态规划

$ T_n=O(n) \ C_n=O(n)$

(实现递归的归流程)

定义dp[i][same] 表示在下标[i,n-1]中选一个子序列,起第一个数字的奇偶性为same(也就是摸2的结果为same)时的最大得分。
v = nums[i],相应的递推式如下:

$$
dp[i][same] =
\begin{cases}
dp[i + 1][same] & v % 2 \neq same, \
max(dp[i + 1][same], dp[i + 1][same⌖1] - x) + v & v % 2 = same.
\end{cases}
$$

代码实现


class Solution {
    public long maxScore(int[] nums, int x) {
        int n = nums.length;
        long[][] dp = new long[n + 1][2];

        for (int i = n - 1; i >= 0; i--) {
            int v = nums[i];
            int same = v % 2;
            dp[i][same ^ 1] = dp[i + 1][same ^ 1];// case 跳过 nums[i]
            // case 可以选,要么无损,要么损失 x
            dp[i][same] = Math.max(dp[i + 1][same], dp[i + 1][same ^ 1] - x) + v;

        }

        return dp[0][nums[0] % 2];
    }

}

动态规划 优化

$ T_n=O(n) \ C_n=O(1)$

优化空间复杂度

注意到,状态转移只和相邻元素有关系,我们可以以除掉各个位置的奇偶情况的最优解,只用一个长度为2的数组来复用。

状态转移方程改为:

$$
dp[same] = max(dp[same],dp[same ⌖ 1] - x) + v , v % 2 = same

$$

v % 2 != same 无需计算,因为去掉第一维度,化简后是恒等式。

初始值:dp[same] = 0same = nums[0] % 2

class Solution {
    public long maxScore(int[] nums, int x) {
        int n = nums.length;
        long[] dp = new long[2];

        for (int i = n - 1; i >= 0; i--) {
            int v = nums[i];
            int same = v % 2;
            // case 可以选,要么无损,要么损失 x
            dp[same] = Math.max(dp[same], dp[same ^ 1] - x) + v;

        }

        return dp[nums[0] % 2];
    }

}

// n & 1 = n % 2,使用 & 速度更快!!!
class Solution {
    public long maxScore(int[] nums, int x) {
        int n = nums.length;
        long[] dp = new long[2];

        for (int i = n - 1; i >= 0; i--) {
            int v = nums[i];
            int same = v & 1;
            // case 可以选,要么无损,要么损失 x
            dp[same] = Math.max(dp[same], dp[same ^ 1] - x) + v;

        }

        return dp[nums[0] & 1];
    }

}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1056615746@qq.com

💰

Title:2786、访问数组中的位置使得分数最大

Count:1.4k

Author:攀登

Created At:2024-06-14, 17:40:25

Updated At:2024-06-15, 15:52:32

Url:http://jiafeimao-gjf.github.io/2024/06/14/2786%E3%80%81%E8%AE%BF%E9%97%AE%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E4%BD%8D%E7%BD%AE%E4%BD%BF%E5%BE%97%E5%88%86%E6%95%B0%E6%9C%80%E5%A4%A7/

Copyright: 'Attribution-non-commercial-shared in the same way 4.0' Reprint please keep the original link and author.

×

Help us with donation