这个问题可以通过将数组分成两半并枚举所有子序列和,然后排序和二分查找来高效解决,时间复杂度为 O(2^{n/2} \cdot n)。

算法思路

1. 将数组 nums 分成两部分 left 和 right,长度分别为 n/2 和 n - n/2。
2. 分别枚举两部分的所有子序列(包括空序列),得到两个和列表 sumsL 和 sumsR。
3. 对 sumsR 排序。
4. 遍历 sumsL 中的每个和 sumL,在 sumsR 中二分查找最接近 goal - sumL 的值,更新最小绝对差。
5. 返回最小绝对差。

代码实现

```java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Solution {
    public int minAbsDifference(int[] nums, int goal) {
        int n = nums.length;
        // 分成两半
        int[] left = new int[n / 2];
        int[] right = new int[n - n / 2];
        System.arraycopy(nums, 0, left, 0, left.length);
        System.arraycopy(nums, n / 2, right, 0, right.length);
        
        // 生成所有子序列和
        List<Integer> sumsL = generateSubsetSums(left);
        List<Integer> sumsR = generateSubsetSums(right);
        
        // 排序右半部分
        Collections.sort(sumsR);
        
        int ans = Integer.MAX_VALUE;
        for (int sumL : sumsL) {
            int target = goal - sumL;
            // 二分查找最接近 target 的值
            int idx = Collections.binarySearch(sumsR, target);
            if (idx >= 0) {
                // 精确匹配,绝对差为 0
                return 0;
            }
            // 插入点:-idx-1
            int insertPos = -idx - 1;
            // 检查插入点左边的元素
            if (insertPos > 0) {
                int sumR = sumsR.get(insertPos - 1);
                ans = Math.min(ans, Math.abs(sumL + sumR - goal));
            }
            // 检查插入点右边的元素
            if (insertPos < sumsR.size()) {
                int sumR = sumsR.get(insertPos);
                ans = Math.min(ans, Math.abs(sumL + sumR - goal));
            }
        }
        return ans;
    }
    
    // 枚举数组所有子序列的和(包括空序列)
    private List<Integer> generateSubsetSums(int[] arr) {
        List<Integer> sums = new ArrayList<>();
        int m = arr.length;
        // 使用位掩码枚举所有子集
        for (int mask = 0; mask < (1 << m); mask++) {
            int sum = 0;
            for (int i = 0; i < m; i++) {
                if ((mask & (1 << i)) != 0) {
                    sum += arr[i];
                }
            }
            sums.add(sum);
        }
        return sums;
    }
}
```

复杂度分析

· 时间复杂度:生成子集和需要 O(2^{n/2}),排序右半部分 O(2^{n/2} \log(2^{n/2})),遍历左半部分时每次二分查找 O(\log(2^{n/2})),总体为 O(2^{n/2} \cdot n)。
· 空间复杂度:存储两个子集和列表,O(2^{n/2})。

优化建议

· 若数组长度较小(如 ≤ 20),可直接枚举所有子序列,无需分半。
· 生成子集和时可用递归或迭代优化常数,但位掩码法已足够清晰。

 

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐