1755. 最接近目标值的子序列和
给你一个整数数组 nums 和一个目标值 goal 。
你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal 。也就是说,如果子序列元素和为 sum ,你需要 最小化绝对差 abs(sum - goal) 。
返回 abs(sum - goal) 可能的 最小值 。
注意,数组的子序列是通过移除原始数组中的某些元素(可能全部或无)而形成的数组。
示例 1:
输入:nums = [5,-7,3,5], goal = 6
输出:0
解释:选择整个数组作为选出的子序列,元素和为 6 。
子序列和与目标值相等,所以绝对差为 0 。
示例 2:
输入:nums = [7,-9,15,-2], goal = -5
输出:1
解释:选出子序列 [7,-9,-2] ,元素和为 -4 。
绝对差为 abs(-4 - (-5)) = abs(1) = 1 ,是可能的最小值。
示例 3:
输入:nums = [1,2,3], goal = -7
输出:7
提示:
1 <= nums.length <= 40-107 <= nums[i] <= 107-109 <= goal <= 109
思路:
由于输入规模为40,直接枚举整个数组显然会超时,但是如果只有一半规模就没有问题!
故考虑将整个数组分成两半来考虑。
折半 + 二进制枚举 + 排序 + 二分
class Solution {public int minAbsDifference(int[] nums, int goal) {int n = nums.length / 2, m = nums.length - n;List<Integer> list = new ArrayList<>();for (int i = 0; i < 1 << n; i++) {int sum = 0;for (int j = 0; j < n; j++) {if ((i >> j & 1) == 1)sum += nums[j];}list.add(sum);}Collections.sort(list);int min = Integer.MAX_VALUE;for (int i = 0; i < 1 << m; i++) {int sum = 0;for (int j = 0; j < m; j++) {if ((i >> j & 1) == 1)sum += nums[j + n];}int l = 0, r = list.size() - 1;while (l < r) {int mid = l + (r - l >> 1);if (list.get(mid) + sum < goal)l = mid + 1;elser = mid;}if (list.get(l) + sum - goal == 0)return 0;min = Math.min(min, Math.abs(list.get(l) + sum - goal));if (l - 1 >= 0)min = Math.min(min, Math.abs(list.get(l - 1) + sum - goal));}return min;}}
805. 数组的均值分割
给定的整数数组 A ,我们要将 A数组 中的每个元素移动到 B数组 或者 C数组中。(B数组和C数组在开始的时候都为空)
返回true ,当且仅当在我们的完成这样的移动后,可使得B数组的平均值和C数组的平均值相等,并且B数组和C数组都不为空。
示例:
输入:
[1,2,3,4,5,6,7,8]
输出: true
解释: 我们可以将数组分割为 [1,4,5,8] 和 [2,3,6,7], 他们的平均值都是4.5。
注意:
A数组的长度范围为[1, 30].A[i]的数据范围为[0, 10000].
思路:
其它做法
1755的进阶,需要转换一下思路
| 总和 | 个数 | |
|---|---|---|
| A | S | N |
| B | S1 | l1 |
| C | S2 | l2 |
S / N = S1 / l1 = s2 / l2 = avg
数组A中每个数都减去一个 avg 有 sum(A) = avg(A) = 0
但是 avg 可能不是整数怎么办?
将数组中的每一个元素乘以数组长度 N 之后再减去 sum(A) 即可
这样问题就转换成了找数组的某个子集,使其和为0,这里因为要将数组分为两部分,所以不能选空集,也不能选全集,特殊处理一下即可。
class Solution {public boolean splitArraySameAverage(int[] nums) {int n = nums.length;if (n == 1)return false;int sum = 0;for (int x : nums)sum += x;for (int i = 0; i < n; i++)nums[i] = nums[i] * n - sum;List<Integer> list = new ArrayList<>();int m = n - n / 2;n /= 2;for (int i = 0; i < 1 << n; i++) {int all = 0;for (int j = 0; j < n; j++)if ((i >> j & 1) == 1)all += nums[j];list.add(all);}Collections.sort(list);for (int i = 1; i < 1 << m - 1; i++) {int all = 0;for (int j = 0; j < m; j++)if ((i >> j & 1) == 1)all += nums[j + n];int idx = binarySearch(list, -all);if (list.get(idx) == -all)return true;}// 右半部分啥也不选,左半部分不能取list.get(0)int idx = binarySearch(list, 0);if (idx + 1 < list.size() && list.get(idx + 1) == 0)return true;int sa = 0;for (int i = n; i < n + m; i++)sa += nums[i];//右半部分全选,左半部分不能全选idx = binarySearch(list, -sa);if (idx + 1 < list.size() && list.get(idx + 1) == -sa)return true;return false;}int binarySearch(List<Integer> list, int x) {int l = 0, r = list.size() - 1;while (l < r) {int mid = l + (r - l >> 1);if (list.get(mid) < x)l = mid + 1;elser = mid;}return l;}}
方法2:DP解法
二维费用,01背包,恰好,恰好,是否可行
class Solution {public boolean splitArraySameAverage(int[] nums) {int n = nums.length;int sum = 0;for (int x : nums)sum += x;boolean[][] f = new boolean[n + 1][sum + 1];f[0][0] = true;for (int x : nums) {for (int j = n; j >= 1; j--) {for (int k = sum; k >= x; k--) {f[j][k] = (f[j][k] || f[j - 1][k - x]);}}}boolean res = false;for (int i = 1; i < n; i++) {if ((sum * i) % n == 0) {res = res || f[i][sum * i / n];}}return res;}}
2035. 将数组分成两个数组并最小化数组和的差
给你一个长度为 2 * n 的整数数组。你需要将 nums 分成 两个 长度为 n 的数组,分别求出两个数组的和,并 最小化 两个数组和之 差的绝对值 。nums 中每个元素都需要放入两个数组之一。
请你返回 最小 的数组和之差。
示例 1:
输入:nums = [3,9,7,3]
输出:2
解释:最优分组方案是分成 [3,9] 和 [7,3] 。
数组和之差的绝对值为 abs((3 + 9) - (7 + 3)) = 2 。
示例 2:
输入:nums = [-36,36]
输出:72
解释:最优分组方案是分成 [-36] 和 [36] 。
数组和之差的绝对值为 abs((-36) - (36)) = 72 。
示例 3:
输入:nums = [2,-1,0,4,-2,-9]
输出:0
解释:最优分组方案是分成 [2,4,-9] 和 [-1,0,-2] 。
数组和之差的绝对值为 abs((2 + 4 + -9) - (-1 + 0 + -2)) = 0 。
提示:
1 <= n <= 15nums.length == 2 * n- `-107 <= nums[i] <= 107
`
思路:
与1755的区别在于1755是找一个和目标值绝对差最小的子序列
而本题是将整个数组分为两个子序列,考虑他们之间的绝对差最小值
依旧是折半 + 二进制枚举 + 排序 + 二分
