前言
讲这个之前,先举几个例子以及一些解决方法。 比如:一个长度为1000的数组中,我想要从数组中分成两个数组(不要求两个数组大小相同),使得两个数 组的绝对值之差最小。 这道题在不要求数组中存储值的范围是很难做的,如果范围很大,那么只能依靠dfs暴力搜索答案,哪怕是 加上一些剪枝优化,复杂度也是相当的惊人。 如果范围很小,比如 sum/2 在10^4左右,这个问题还是很好解的,这就是很常见的背包问题 伪代码:
for i in nums
for j=sum/2, j>nums[i], j--
dp[j] = max(dp[j], dp[j-nums[i]] + nums[i]);
很显然答案是 abs(sum - 2*dp[sum/2]);
再比如:我有一个长度为16的数组,但是我数组中存储的值特别大,能不能解? 当然这非常好解,为什么? 因为它的长度才16,2^16次方才多大,轻轻松松的暴力即可。
进入正题:
如果我有一个长度为32的数组呢,能解么?
这个时候估计就有些傻眼了,2^32次方这么高的复杂度,这谁能抗的住。
这个时候我们想到,能把数组分成两个子数组分别枚举么?
好的,有了这个想法,那具体怎么做呢?
一步一步来
我们能够得到什么?
我们能分别二进制枚举两个子数组,拿到所有的结果,并用两个集合存储下来。
我们要求解什么?
获取数组中一些元素减去另一些元素的绝对值差最小。
从我们得到的与想要的结果进行问题演化。
答案为min(abs(oneResults[x] + twoResults[y])), 这个答案越接近0是不是越符合答
案,哇塞,现在是否看到了解决这个问题的希望。
对twoResults集合进行排序,遍历oneResults,每次二分去twoResults中查找。
最后取最小值就是答案。
最后我们来分析一下复杂度
一开始,我们是要2^32复杂度,现在呢?
大概是2 * O(2^16) + 2 * O(2^16) * O(16)
程序实现
/**
* 最后在来一道例题
* 将nums数组分成两个大小相等数组,要求两个数组的绝对值之差最小
* 数据范围: 1 <= n <= 15; nums = 2 * n; -10^7 <= nums[i] <= 10^7
* 解题思路: 将问题转化成对半暴力枚举,也就是将数组分成左右两边,分别二进制枚举,保存取x个整数
* 会有结果集,然后对其中一个存储的集合进行排序便于二分查找
* @param nums 传入的数组
* @return 返回最后的结果
*/
public static int minimumDifference(int[] nums) {
int n = nums.length / 2, sum = 0;
int[] a = new int[n];
int[] b = new int[n];
/**
* oneMap/twoMap: 用于保存左/右半边的结果集
* key: 整数数量
* value: 存储左/右半边取key个正整数加起来的和有多少种结果
*/
Map<Integer, List<Integer>> oneMap = new HashMap<>();
Map<Integer, List<Integer>> twoMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (i < n) {
a[i] = nums[i];
oneMap.put(i, new ArrayList<>());
continue;
}
b[i - n] = nums[i];
twoMap.put(i - n, new ArrayList<>());
}
oneMap.put(n, new ArrayList<>());
twoMap.put(n, new ArrayList<>());
for (int i = 0; i < (1 << n); i++) {
int cnt = 0, ansOne = 0, ansTwo = 0;
for (int j = 0; j < n; j++) {
if (((1 << j) & i) > 0) {
ansOne += a[j];
ansTwo += b[j];
cnt++;
}
}
oneMap.getOrDefault(cnt, new ArrayList<>()).add(ansOne);
twoMap.getOrDefault(cnt, new ArrayList<>()).add(ansTwo);
}
twoMap.forEach((key, value) -> twoMap.put(key, value.stream().sorted().collect(Collectors.toList())));
// oneMap.forEach((key, value) -> System.out.println("key: " + key + " value: " + value));
// twoMap.forEach((key, value) -> System.out.println("key: " + key + " value: " + value));
AtomicInteger ans = new AtomicInteger(Integer.MAX_VALUE);
for (int i = 0; i <= n; i++) {
int finalI = i;
int finalSum = sum;
oneMap.get(i).forEach(bean -> {
int two = (finalSum / 2) - bean;
List<Integer> twoList = twoMap.get(n - finalI);
int l = 0, r = twoList.size() - 1, pos = twoList.size();
while (l <= r) {
int mid = (l + r) >> 1;
if (twoList.get(mid) > two) {
pos = mid;
r = mid - 1;
continue;
}
l = mid + 1;
}
if (pos != twoList.size()) {
ans.set(Math.min(ans.get(), Math.abs(finalSum - 2 * (bean + twoList.get(pos)))));
}
if (pos > 0) {
ans.set(Math.min(ans.get(), Math.abs(finalSum - 2 * (bean + twoList.get(pos - 1)))));
}
});
}
return ans.get();
}