Trick | 如何高效求有序子集和

一、起因

群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;
    }
}
相关推荐
我笑了OvO20 天前
常见位运算及其经典算法题(1)
c++·算法·算法竞赛
_OP_CHEN1 个月前
【算法基础篇】(六十)Nim 博弈超全解析:从基础原理到经典变种,玩转多堆取石子问题
算法·蓝桥杯·c/c++·博弈论·算法竞赛·acm、icpc·nim博弈
_OP_CHEN1 个月前
【算法基础篇】(五十九)巴什博弈 (Bash Game) 超详解:从原理到实战,搞定经典取石子问题
算法·蓝桥杯·c/c++·博弈论·算法竞赛·acm/icpc·bash博弈
_OP_CHEN1 个月前
【算法基础篇】(五十六)容斥原理指南:从集合计数到算法实战,解决组合数学的 “重叠难题”!
算法·蓝桥杯·c/c++·组合数学·容斥原理·算法竞赛·acm/icpc
_OP_CHEN1 个月前
【算法基础篇】(五十五)卡特兰数封神之路:从括号匹配到二叉树构造,组合数学的万能钥匙!
算法·蓝桥杯·c/c++·组合数学·卡特兰数·算法竞赛·acm/icpc
_OP_CHEN1 个月前
【算法基础篇】(五十四)解析错排问题:从信封错位到编程实战,一次性搞懂排列组合中的 “反常识” 难题!
算法·蓝桥杯·c/c++·组合计数·算法竞赛·acm/icpc·错排问题
_OP_CHEN2 个月前
【算法基础篇】(四十九)数论之中国剩余定理终极指南:从孙子算经到算法竞赛
算法·蓝桥杯·数论·中国剩余定理·算法竞赛·乘法逆元·acm/icpc
_OP_CHEN2 个月前
【算法基础篇】(四十八)突破 IO 与数值极限:快速读写 +__int128 实战指南
c++·算法·蓝桥杯·算法竞赛·快速读写·高精度算法·acm/icpc
_OP_CHEN2 个月前
【算法基础篇】(四十七)乘法逆元终极宝典:从模除困境到三种解法全解析
c++·算法·蓝桥杯·数论·算法竞赛·乘法逆元·acm/icpc