一、起因
群u扔了道meet in middle 的模板题1755. 最接近目标值的子序列和 - 力扣(LeetCode),最近没什么时间写算法题手痒,想着随手写个 O ( n 2 n / 2 ) O(n2^{n/2}) O(n2n/2)
的做法水一下,看到了 Heltion 佬的 O ( 2 n / 2 ) O(2^{n/2}) O(2n/2)做法,学到了一个 O ( 2 n ) O(2^{n}) O(2n) 得到有序子集和的做法。
1.1 暴力做法
C#
int[] Get(ReadOnlySpan<int> a) {
int n = a.Length;
int U = 1 << n;
int[] f = new int[U];
for (int i = 0; i < n; ++i) f[1 << i] = a[i];
for (int s = 1; s < U; ++s) {
int hi = 1 << BitOperations.Log2((uint)s);
f[s] = f[s ^ hi] + f[hi];
}
Array.Sort(f);
return f;
}
暴力枚举子集然后排序:O(n2^n)
1.2 双指针优化做法
考虑前 i 个数得到的有序子集和为 f,那么考虑加入 a[i + 1],相当于将 f[] 和 f[] + a[i + 1] 两个长度相等的数组做合并
这就相当于线性合并两个有序数组
C#
List<int> Get(ReadOnlySpan<int> a) {
List<int> f = [0];
foreach (var x in a) {
int n = f.Count;
List<int> g = [];
for (int i = 0, j = 0; i < n || j < n;) {
if (i == n) {
g.Add(f[j++] + x);
} else if(j == n) {
g.Add(f[i++]);
} else if(f[i] < f[j] + x) {
g.Add(f[i++]);
} else {
g.Add(f[j++] + x);
}
}
f = g;
}
return f;
}
时间复杂度 : O ( 1 + 2 + . . . + 2 n − 1 = 2 n ) O(1 + 2 + ... + 2^{n - 1} = 2^n) O(1+2+...+2n−1=2n)
1.3 随便水个题(C#)
1755. 最接近目标值的子序列和 - 力扣(LeetCode)
C#
public class Solution {
public int MinAbsDifference(int[] nums, int goal) {
int n = nums.Length;
var f = Get(nums.AsSpan(0, n / 2));
var g = Get(nums.AsSpan(n / 2, n - n / 2));
int ans = int.MaxValue;
for (int i = 0, j = g.Count - 1; i < f.Count; ++i) {
while (j > 0 && f[i] + g[j] > goal) {
--j;
}
ans = Math.Min(ans, Math.Abs(goal - f[i] - g[j]));
if (j + 1 < g.Count) {
ans = Math.Min(ans, Math.Abs(goal - f[i] - g[j + 1]));
}
}
return ans;
}
List<int> Get(ReadOnlySpan<int> a) {
List<int> f = [0];
foreach (var x in a) {
int n = f.Count;
List<int> g = [];
for (int i = 0, j = 0; i < n || j < n;) {
if (i == n) {
g.Add(f[j++] + x);
} else if(j == n) {
g.Add(f[i++]);
} else if(f[i] < f[j] + x) {
g.Add(f[i++]);
} else {
g.Add(f[j++] + x);
}
}
f = g;
}
return f;
}
}
