leetcode1755 题解
题目
给你一个整数数组 n u m s nums nums 和一个目标值 g o a l goal goal 。
你需要从 n u m s nums nums 中选出一个子序列,使子序列元素总和最接近 g o a l goal goal 。也就是说,如果子序列元素和为 s u m sum sum ,你需要 最小化绝对差 a b s ( s u m − g o a l ) abs(sum - goal) abs(sum−goal) 。
返回 a b s ( s u m − g o a l ) abs(sum - goal) abs(sum−goal) 可能的 最小值 。
注意,数组的子序列是通过移除原始数组中的某些元素(可能全部或无)而形成的数组。
提示:
1 < = n u m s . l e n g t h < = 40 1 <= nums.length <= 40 1<=nums.length<=40
− 107 < = n u m s [ i ] < = 107 -107 <= nums[i] <= 107 −107<=nums[i]<=107
− 109 < = g o a l < = 109 -109 <= goal <= 109 −109<=goal<=109
题解
看到题目中nums
长度为0~40,可以通过暴力枚举的方法解决这道题。首先将数组一分为二,这样的话数组前半部分和后半部分的最大长度不会超过20。假设其中一个数组的长度为n,那么数组可能的子序列的排列会有
2
n
2^n
2n种。显然区间
[
0
,
2
n
)
[0, 2^n)
[0,2n)中的每一个整数代表子序列的一种排列,因为假设
0
<
=
i
<
2
n
0<=i<2^n
0<=i<2n的二进制表示为
b
1
,
b
2
,
⋅
⋅
⋅
,
b
n
b_1, b_2, ···,b_n
b1,b2,⋅⋅⋅,bn,则
b
i
b_i
bi为1表示选择数组中的元素i,为0表示不选择数组中的元素i。因此我们可以通过以下代码计算nums
所有子序列的和:
public static int[] sum(int[] nums) {
int[] ans = new int[1 << nums.length];
for ( int i = 0; i < ans.legth; i++) {
for(int j = 0; j < nums.length; j++) {
if(i & (1 << j) == 1) ans[i] += nums[j];
}
}
return ans;
}
求出了nums
前半部分数组和后半部分数组的子序列和之后,满足abs(sum - goal)
的子序列有三种情况:
- 在
nums
前半部分数组中;- 在
nums
后半部分数组中;- 横跨整个
nums
数组;
假设nums
数组前半部分子序列和保存在left
中,后半部分子序列和保存在right
中,我们对right
进行排序,之后对于left
中的每一个元素,在right
中寻找与之相加等于goal
的元素;
public int minAbsDifference(int[] nums, int goal) {
int[] left = sum(Arrays.copyOfRange(nums, 0, nums.length / 2);
int[] right = sum(Arrays.copyOfRange(nums, nums.length / 2, nums.length);
Arrays.sort(right);
int ans = Integer.MAX_VALUE;
for (int i = 0; i < left.length; i++) {
int target = goal - f1[i];
int index = Arrays.binarySearch(right, target);
if(index > 0) return 0;
else index = -index - 1;
if (index < right.length) {
ans = min(ans, abs(left[i] + left[index] - goal));
}
if (index - 1 >= 0) {
ans = min(ans, abs(right[i] + right[index - 1] - goal));
}
}
}