求出所有子序列的能量和

2024-07-23

题目:

给你一个长度为 n 的整数数组 nums 和一个 整数 k 。一个 子序列的 能量 定义为子序列中 任意 两个元素的差值绝对值的 最小值

请你返回 nums 中长度 等于 k所有 子序列的 能量和 。由于答案可能会很大,将答案对 109 + 7 取余 后返回。

  • 示例 1:

    输入:nums = [1,2,3,4], k = 3

    输出:4

    解释:

    nums 中总共有 4 个长度为 3 的子序列:[1,2,3][1,3,4][1,2,4][2,3,4] 。能量和为 |2 - 3| + |3 - 4| + |2 - 1| + |3 - 4| = 4

  • 示例 2:

    输入:nums = [2,2], k = 2

    输出:0

    解释:

    nums 中唯一一个长度为 2 的子序列是 [2,2] 。能量和为 |2 - 2| = 0

  • 示例 3:

    输入:nums = [4,3,-1], k = 2

    输出:10

    解释:

    nums 总共有 3 个长度为 2 的子序列:[4,3][4,-1][3,-1] 。能量和为 |4 - 3| + |4 - (-1)| + |3 - (-1)| = 10

提示:

  • 2 <= n == nums.length <= 50
  • -108 <= nums[i] <= 108
  • 2 <= k <= n

暴力,但没过全部

思路:

我们可以直接暴力求解,就是复杂度高一点

回溯算法求出所有长度为 k 的子序列,然后对于这些子序列求他们的能力,然后相加就行

代码:

class Solution {
public:
    vector<int> path;
    int ans = 0;
    int mod = 1e9 + 7;
    int getMinAbs() {
        int res = INT_MAX;
        vector<int> temp = path;
        sort(temp.begin(), temp.end());
        for (int i = 0; i < temp.size() - 1; i++) {
            res = min(res, temp[i + 1] - temp[i]);
        }
        return res;
    }
    void backTracking(vector<int>& nums, int k, int i) {
        if (path.size() == k) {
            int power = getMinAbs();
            ans = (ans + power) % mod;
            return;
        }
        for (int j = i; j < nums.size(); j++) {
            path.push_back(nums[j]);
            backTracking(nums, k, j + 1);
            path.pop_back();
        }
    }
    int sumOfPowers(vector<int>& nums, int k) {
        // 回溯,枚举子序列
        // 保留所有子序列的和
        backTracking(nums, k, 0);
        return ans;
    }
};

动态规划,但看不懂

思路:

由于子序列的能量为任意两个元素的差值绝对值的最小值,因此对子序列排序后,其能量为两两相邻元素的差值绝对值的最小值。在对原数组 nums 排序后,我们在计算子序列能量时只需考虑相邻元素之间的差值即可。

采用求解数组最长上升子序列的思想,我们从小到大枚举 i 作为子序列中的最后一个元素,再从 0 到 i − 1 去枚举 j 作为 i 的前置元素,这样一来产生差值 diff =∣nums[i] − nums[j]∣。接着我们再枚举子序列的长度 p,然后考虑从「以 j 结尾且长度为 p − 1 的子序列」等一系列状态进行转移求解。

设 d[i][p][v] 表示以 i 为结尾时,有多少个长度为 p 且能量为 v 的子序列。这样一来,就有如下转移方程:

dp[i][p][v] = sum(d[j][p - 1][v])  for v < diff 
              sum(d[j][p - 1][diff] + d[j][p - 1][diff + 1] + ...) for v = diff
              0  for v > diff
              sum 代表求取0 - i - 1 所有的j

在实现时,我们只需枚举所有 d[j][p−1][v],然后更新到 d[i][p][min(diff,v)] 即可。
最后,我们枚举所有长度为 k 的状态,并将 v * d[i][k][v] 累加到答案中,即可得到 nums 中长度等于 k 的所有子序列的能量和。

细节
	长度为 n 的整数数组,两两元素之间的差值种类数不会超过 n * (n - 1) / 2,因此状态表示中 v 的数量级大致在 n^2 量级。具体到每个状态 d[i][p][v],实际可取的 v 更少。因此,在代码实现时可以将 d[i][p] 定义为字典(哈希表),来减少状态枚举数量,并减少代码量。
	将 d[i][1][inf] 初始化为 1,方便后续状态转移,其中 inf 表示无穷大。

代码:

class Solution {
public:
    using ump = unordered_map<int, int>;
    static constexpr int mod = 1e9 + 7;
    static constexpr int inf = 0x3f3f3f3f;
    int sumOfPowers(vector<int>& nums, int k) {
        int n = nums.size();
        int res = 0;
        vector<vector<ump>> d(n, vector<ump>(k + 1));
        sort(nums.begin(), nums.end());
        for (int i = 0; i < n; i++) {
            d[i][1][inf] = 1;
            for (int j = 0; j < i; j++) {
                int diff = abs(nums[i] - nums[j]);
                for (int p = 2; p <= k; p++) {
                    for (auto &[v, cnt] : d[j][p - 1]) {
                        d[i][p][min(diff, v)] = (d[i][p][min(diff, v)] + cnt) % mod;
                    }
                }
            }

            for (auto &[v, cnt] : d[i][k]) {
                res = (res + 1ll * v * cnt % mod) % mod;
            }
        }
        return res;
    }
};