leetcode
leetcode 1601 ~ 1650
N 次操作后的最大分数和

N 次操作后的最大分数和

难度:

标签:

题目描述

代码结果

运行时间: 756 ms, 内存: 16.5 MB


/*
思路:
1. 计算数组中所有可能的两两元素的最大公约数,并存储在一个表中。
2. 使用回溯法选择不同的元素对来计算得分,并记录最大得分。
3. 每次操作时,我们选择剩余元素中的最大公约数对,计算得分并删除这些元素。

Java Stream 版本:利用递归生成所有的子集组合,计算最大得分。
*/

import java.util.*;
import java.util.stream.IntStream;

public class MaxScoreStream {
    public int maxScore(int[] nums) {
        int n = nums.length / 2;
        return maxScore(nums, (1 << nums.length) - 1, new int[1 << nums.length]);
    }

    private int maxScore(int[] nums, int mask, int[] dp) {
        if (dp[mask] != 0) return dp[mask];
        int pairs = Integer.bitCount(mask) / 2;
        return dp[mask] = IntStream.range(0, nums.length).filter(i -> (mask & (1 << i)) != 0)
            .boxed().flatMap(i -> IntStream.range(i + 1, nums.length)
                .filter(j -> (mask & (1 << j)) != 0)
                .mapToObj(j -> new int[]{i, j}))
            .mapToInt(pair -> pairs * gcd(nums[pair[0]], nums[pair[1]]) + maxScore(nums, mask ^ (1 << pair[0]) ^ (1 << pair[1]), dp))
            .max().orElse(0);
    }

    private int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
}

解释

方法:

该题解采用了动态规划与预处理的最大公约数(GCD)的方法。首先,数组被排序,然后计算并存储任意两个元素之间的GCD到一个二维数组中,以减少后续计算中的重复工作。接着,定义一个dp数组,其索引代表一个二进制数,二进制数的每一位表示数组中对应位置的元素是否已经被选择(1代表选择,0代表未选择)。dp数组中的值存储到目前为止可获得的最大分数。对于dp数组的每个状态,如果选取的元素个数是偶数(即可以完全成对),则尝试所有可能的配对方式,计算可能的分数并更新dp状态。最后,dp数组的最后一项(所有元素都被选择时的状态)就是最大分数。

时间复杂度:

O(2^(2n) * n^2)

空间复杂度:

O((2n)^2 + 2^(2n))

代码细节讲解

🦆
如何确定初始化`gcds`二维数组时,使用的循环边界确实涵盖了所有必要的元素对?
在初始化`gcds`数组时,外层循环遍历数组`nums`的所有元素,内层循环从外层循环的当前元素开始至数组末尾。这种方式确保了每一对元素(x, y),其中x <= y,都被考虑到了。由于`gcd(x, y)`与`gcd(y, x)`的结果相同,这种方法避免了重复计算,并且保证了所有可能的元素对都被计算了一次。
🦆
在动态规划的实现中,为什么选择使用二进制数来代表数组中元素的选取状态?
使用二进制数来表示元素的选取状态是一种高效的方法,可以直接通过位操作来快速检查、更新和枚举子集。在这种表示方法中,如果一个二进制数的第i位是1,则表示数组中的第i个元素被选取了;如果是0,则表示未被选取。这使得动态规划的状态转移和子状态的检索变得更加直接和高效。
🦆
在题解中提到的`dp[m]`数组更新过程中,`cnt`的计算是如何确保每次都是在正确的基础上增加新的分数的?
在更新`dp[m]`时,`cnt`的计算考虑了从状态m移除两个已选元素x和y的子状态`dp[m ^ (1<>1) * gcds[x][y]`。这样做确保了每次更新是在正确的基础上增加新的分数,因为它建立在之前已经计算好的最优子问题解的基础上。
🦆
在计算`(t>>1) * gcds[x][y]`时,`t>>1`代表了什么含义,它是如何与操作次数`i`联系起来的?
`t`是当前状态m中选取的元素个数的二进制表示中1的个数。`t>>1`是`t`右移一位的结果,即`t`除以2的整数结果,这代表了已经进行的配对次数(因为每次配对需要两个元素)。每次配对的分数是当前配对次数乘以这对元素的最大公约数。这里的操作次数`i`实际上是通过`t>>1`来计算的,`i`表示第i次配对,从1开始计数。

相关问题