这道题的核心思路是排序 + 记忆化搜索(Memoization DFS)。

题目思路

关键观察
- 子序列的能量 = 子序列中任意两个元素差值绝对值的最小值
- 子序列的顺序不影响结果,所以可以先排序
- 排序后,子序列的能量一定等于相邻两个元素的差值的最小值

DFS 状态设计
定义 `dfs(i, j, k, mi)`:
- `i`:当前处理到第 `i` 个元素
- `j`:上一个选择的元素的下标(`j = n` 表示还没选过)
- `k`:还需要选多少个元素
- `mi`:当前已选元素中的最小差值

转移
- 不选第 i 个:`dfs(i+1, j, k, mi)`
- 选第 i 个:
  - 如果 `j == n`(第一次选):`dfs(i+1, i, k-1, mi)`
  - 否则更新最小差值:`dfs(i+1, i, k-1, min(mi, nums[i] - nums[j]))`

记忆化
用 `HashMap` 缓存状态,key 编码为 `mi << 18 | i << 12 | j << 6 | k`。

Java 实现

```java
class Solution {
    private Map<Long, Integer> memo = new HashMap<>();
    private final int MOD = (int) 1e9 + 7;
    private int[] nums;
    private int n;

    public int sumOfPowers(int[] nums, int k) {
        Arrays.sort(nums);
        this.nums = nums;
        this.n = nums.length;
        return dfs(0, n, k, Integer.MAX_VALUE);
    }

    /**
     * @param i  当前处理到第 i 个元素
     * @param j  上一个选择的元素的下标(n 表示还没选过)
     * @param k  还需要选 k 个元素
     * @param mi 当前已选元素中的最小差值
     * @return 从当前状态出发,所有合法子序列的能量和
     */
    private int dfs(int i, int j, int k, int mi) {
        // 所有元素处理完毕
        if (i >= n) {
            // 如果恰好选了 k 个,返回当前能量;否则返回 0(不合法)
            return k == 0 ? mi : 0;
        }
        // 剪枝:即使剩下的元素全选也不够 k 个
        if (n - i < k) {
            return 0;
        }

        // 编码状态为 key
        long key = ((long) mi << 18) | ((long) i << 12) | ((long) j << 6) | k;
        if (memo.containsKey(key)) {
            return memo.get(key);
        }

        // 不选第 i 个元素
        long ans = dfs(i + 1, j, k, mi);

        // 选第 i 个元素
        if (j == n) {
            // 第一次选,没有前一个元素,不更新 mi
            ans += dfs(i + 1, i, k - 1, mi);
        } else {
            // 更新最小差值
            ans += dfs(i + 1, i, k - 1, Math.min(mi, nums[i] - nums[j]));
        }

        ans %= MOD;
        memo.put(key, (int) ans);
        return (int) ans;
    }
}
```

复杂度分析

指标    复杂度    
时间    O(n⁵),状态数为 O(n⁴),每个状态 O(1) 转移    
空间    O(n⁵),记忆化存储的状态数    

其中 `n ≤ 50`,所以实际运行完全可行。

关键点总结

1. 先排序:排序后子序列的能量只与相邻元素的差值有关
2. 状态压缩 key:`mi << 18 | i << 12 | j << 6 | k`,用位运算编码四个状态变量
3. 剪枝:`n - i < k` 时直接返回 0
4. 边界处理:`k == 0` 时返回当前能量 `mi`;`j == n` 表示还没选过元素
5. 取模:每次加法后都要 `% MOD` 防止溢出

 

更多推荐