0%

805. 数组的均值分割

805.数组的均值分割

题目描述:

给定你一个整数数组 nums

我们要将 nums 数组中的每个元素移动到 A 数组 或者 B 数组中,使得 A 数组和 B 数组不为空,并且 average(A) == average(B)

如果可以完成则返回true , 否则返回 false

注意:对于数组 arr , average(arr)arr 的所有元素的和除以 arr 长度。

数据范围:

$1 \le nums.len \le 30; 0 \le nums[i] \le 10^4$

题解:

假设 $A$ 数组的和为 $s_1$ ,则 $\frac{s_1}{k} = \frac{s}{n - k}$ ,可以得到 $s_1 = \frac{sum}{n}\times k$ 。也就是 $s_1$ 等于平均值乘以数量。

动态规划:

使用动态规划算法,从 $n$ 中取出 $k$ 个数,看能否凑出 $s_1$ 。如果没有取出个数的限制,就可以直接使用可行性背包判断能否凑出来 $s_1$ 。但是存在个数限制,可以把个数也设计成状态。

原可行性背包问题 $dp[i][j]$ 表示从前 $i$ 个物品中取出一些物品,能否装满 $j$ 容量的背包。这里需要加上个数状态,即 $dp[i][k][j]$ 表示从前 $i$ 个物品中取出 $k$ 个,是否能装满 $j$ 。

原状态转移方程为 dp[i][j] = dp[i][j] | dp[i-1][j - nums[i]]。可以优化为一维 dp[j] |= dp[j - nums[i]], $j$ 需要从后往前遍历。

加了状态 $k$ 之后的状态转移方程 d[i][k][j] = dp[i][k][j] | dp[i-1][k-1][j - nums[i]]。优化为一维 dp[k][j] = dp[k][j] | dp[k-1][j - nums[i]], $j$ 需要从后往前遍历, $k \in [1, n]$ ,但是需要遍历 $i \in [0, n], j \in [0, sum], k\in[1, n)$ ,复杂度比较高。

状态压缩优化:

因为 $n$ 的范围非常小,所以可以使用状态压缩,使用一个整数的每一位 $0/1$ 来表示一维空间。具体可以使用 $dp[j]$ 的第 $i$ 位 $0/1$ 表示 $dp[i][j]$ 的值。然后转移的时候 dp[i][j] = dp[i][j] | dp[i - 1][j - nums[i]] 可以变为 dp[j] = dp[j] | (dp[j - nums[i]] << 1),需要左移一位。

折半查找+二进制枚举:

因为 $s_1 = \frac{sum}{n} \times k$ ,考虑每个数都减去平均值,那么目标就是找出一个子序列和为 $0$ 。但是由于平均值可能为小数,所以直接将平均值扩大 $n$ 倍,也就是将里面的每个数扩大 $n$ 倍。 $x = x \times n - sum$ ,目标就是从找出一个子序列和为 $0$ 。可以直接使用二进制枚举枚举子序列,但是复杂度比较高,复杂度为 $2^{30} \approx 10^9$ 。可以考虑将数组分为两半枚举,先枚举左半边,如果出现为 $0$ 的直接返回 true。否则的话记录一下枚举出来的值,存起来。然后枚举右半边,如果出现了为 $0$ 直接返回 true。如果在左边找到了当前和的相反数也可以直接返回。但是需要注意的是不能全选了,全选了 $B$ 就没了。可以特判一下,是否是满状态。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
auto optimize_cpp_stdio = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
class Solution
{
public:
const static int maxn = 1e5 + 10;
const static int maxm = 1e5 + 10;
const int INF = 0x3f3f3f3f;
bool splitArraySameAverage(vector<int> &nums)
{
int sum = 0;
int n = nums.size();
for (auto &x : nums)
sum += x;
// sumA / k = (sum - sumA) / (n - k);
// sumA = sum * k / n
vector<vector<int>> dp(n + 1, vector<int>(sum + 1));
dp[0][0] = true;
for (int i = 0; i < n; ++i)
{
for (int j = sum; j >= nums[i]; --j)
{
for (int k = 1; k <= n; ++k)
{
dp[k][j] = dp[k][j] | dp[k - 1][j - nums[i]];
}
}
}
for (int i = 1; i < n; ++i)
{
if (sum * i % n == 0 && dp[i][sum * i / n])
{
return true;
}
}
return false;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
auto optimize_cpp_stdio = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
class Solution
{
public:
const static int maxn = 1e5 + 10;
const static int maxm = 1e5 + 10;
const int INF = 0x3f3f3f3f;
bool splitArraySameAverage(vector<int> &nums)
{
int sum = 0;
int n = nums.size();
for (auto &x : nums)
sum += x;
// sumA / k = (sum - sumA) / (n - k);
// sumA = sum * k / n
vector<int> dp(sum + 1);
// dp[j] 第 i 位 1 表示 dp[i][j] true
dp[0] = 1; // dp[0][0] = true
for (int i = 0; i < n; ++i)
{
for (int j = sum; j >= nums[i]; --j)
{
dp[j] |= (dp[j - nums[i]] << 1);
}
}
for (int i = 1; i < n; ++i)
{
if (sum * i % n == 0 && (dp[sum * i / n] >> i) & 1)
{
return true;
}
}
return false;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
auto optimize_cpp_stdio = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
class Solution
{
public:
const static int maxn = 1e5 + 10;
const static int maxm = 1e5 + 10;
const int INF = 0x3f3f3f3f;
bool splitArraySameAverage(vector<int> &nums)
{
int sum = 0;
int n = nums.size();
if(n == 1) return false;
for (auto &x : nums)
sum += x;
// sumA / k = (sum - sumA) / (n - k);
// sumA = sum * k / n
// 找一个子数组,和是 sumA
// 每个数均减去平均值,也就是 sumA = 0
// sumA = sum * n * k / n = sum * k
// 每个数减去sum,就是 sumA = 0
for (auto &x : nums)
x = x * n - sum;
int m = n >> 1;
unordered_set<int> s;
for (int i = 1; i < (1 << m); ++i)
{
// [1, (1 << m) - 1]
int tmp = 0;
for (int j = 0; j < m; ++j)
{
if ((i >> j) & 1)
tmp += nums[j];
}
if (tmp == 0)
return true;
s.insert(tmp);
}
for (int i = 1; i < (1 << (n - m)); ++i)
{
int tmp = 0;
for (int j = m; j < n; ++j)
{
if ((i >> (j - m)) & 1)
tmp += nums[j];
}
if (tmp == 0)
return true;
if (s.count(-tmp) && i != (1 << (n - m)) - 1)
return true;
}
return false;
}
};