一种时间复杂度为O(2ⁿ)、空间复杂度为O(n)的子集和问题的算法

news2025/1/11 0:05:48

子集和问题(Subset-Sum Problem, SSP)是说给定一个自然数集合 S = { a 1 , a 2 , ⋯   , a n } S=\{a_1,a_2,\cdots,a_n\} S={a1,a2,,an},它含有 n n n个元素,现在又给定一个自然数 s s s,问是否存在 S S S的一个子集 T T T使得 T T T的所有元素之和等于 s s s

t t t等于 S S S中所有元素之和( t = ∑ j = 1 n a j t=\sum\limits_{j=1}^{n}a_j t=j=1naj)。显然,当 s > t s>t s>t时,满足要求的子集 T T T不存在,直接输出False;当 s = t s=t s=t时, T = S T=S T=S就是满足条件的子集,直接输出True;当 s = 0 s=0 s=0时,因为空集是 S S S的子集且空集的元素之和为 0 0 0,也直接输出True。因此,我们只需要考虑 0 < s < t 0<s<t 0<s<t的情况。

目前有两种流行的解法,一种是搜索(时间复杂度 O ( 2 n ) O\left(2^n\right) O(2n)、空间复杂度 O ( n ) O(n) O(n)),另一种是动态规划(把问题归约为背包问题,时间复杂度 O ( n t ) O(nt) O(nt),空间复杂度 O ( n t ) O(nt) O(nt))。前者的优点是时间复杂度不依赖于 t t t的大小,但是最坏情况下的时间复杂度是指数级的;后者的优点是当 t t t比较小( t < 2 n t<2^n t<2n)时比搜索要快,缺点是当 t ≥ 2 n t\ge 2^n t2n时比搜索还慢,而且占用的空间非常大。本文给出了一种能够替代动态规划的策略,使得时间复杂度为 O ( n t ) O(nt) O(nt)的同时空间复杂度只有 O ( n ) O(n) O(n)。当 t t t较大时,我们仍然采用搜索;当 t t t较小时,我们采用这种新的策略。这样,面对 t t t较小的情况时,我们就有了一种高效的且占用空间较少的方法。

我们的新策略的主要思想是利用复数单位根的性质。在我前面的一篇文章里我介绍了如何用单位根巧妙地解决一个数学问题(关于单位根的性质及证明请参见那篇文章),那么在本文中我们将用这个思想给出一个子集和问题的算法。

考虑函数 f ( x ) = ∏ j = 1 n ( 1 + x a j ) f(x)=\prod\limits_{j=1}^{n}\left(1+x^{a_j}\right) f(x)=j=1n(1+xaj)将它展开为多项式 f ( x ) = c 0 + c 1 x + c 2 x 2 + ⋯ + c t x t f(x)=c_0+c_1x+c_2x^2+\cdots+c_tx^t f(x)=c0+c1x+c2x2++ctxt其中每项的系数 c p c_p cp等于集合 S = { a 1 , a 2 , ⋯   , a n } S=\{a_1,a_2,\cdots,a_n\} S={a1,a2,,an}的所有和为 p p p的子集的个数( c p = ∣ { T ∣ T ⊆ S ,   s u m ( T ) = p } ∣ c_p=\left|\left\{T|T\subseteq S,\,\mathrm{sum}(T)=p\right\}\right| cp={TTS,sum(T)=p})。那么我们要求解的问题本质上就是判断 c s c_s cs是否等于 0 0 0。将 f ( x ) f(x) f(x)除以 x s x^s xs f ( x ) x s = c 0 x − s + c 1 x 1 − s + ⋯ + c s + c s + 1 x + ⋯ + c t x t − s \frac{f(x)}{x^s}=c_0x^{-s}+c_1x^{1-s}+\cdots+c_s+c_{s+1}x+\cdots+c_t x^{t-s} xsf(x)=c0xs+c1x1s++cs+cs+1x++ctxts现在我们在复数域上考虑问题。令 z ∈ C z\in\mathbb{C} zC,代入上式得 z − s f ( z ) = ∑ j = 0 t c j z j − s z^{-s}f(z)=\sum\limits_{j=0}^{t}c_j z^{j-s} zsf(z)=j=0tcjzjs下面考虑利用单位根的性质。设 ω = e 2 π i m = cos ⁡ 2 π m + i sin ⁡ 2 π m \omega=e^{\frac{2\pi i}{m}}=\cos\frac{2\pi}{m}+i\sin\frac{2\pi}{m} ω=em2πi=cosm2π+isinm2π m m m次单位根,满足 ω m = 1 \omega^m=1 ωm=1。我们又知道 ∑ k = 0 m − 1 ( ω k ) u = { 0 , u 不是 m 的倍数 m , u 是 m 的倍数 \sum\limits_{k=0}^{m-1}\left(\omega^k\right)^u=\begin{cases} 0,&u\text{不是}m\text{的倍数}\\ m,&u\text{是}m\text{的倍数} \end{cases} k=0m1(ωk)u={0,m,u不是m的倍数um的倍数对于任何整数 u u u(不论正负)都成立。这给了我们分离出系数 c s c_s cs的可能。考虑求和 ∑ k = 0 m − 1 ( ω k ) − s f ( ω k ) = ∑ j = 0 t [ ∑ k = 0 m − 1 c j ( ω k ) j − s ] \sum\limits_{k=0}^{m-1}{\left(\omega^k\right)}^{-s}f\left(\omega^k\right)=\sum\limits_{j=0}^{t}\left[\textcolor{orange}{\sum\limits_{k=0}^{m-1}c_j{\left(\omega^k\right)}^{j-s}}\right] k=0m1(ωk)sf(ωk)=j=0t[k=0m1cj(ωk)js]我们希望在 j − s ≠ 0 j-s\ne 0 js=0方括号里的值 0 0 0,仅当 j − s = 0 j-s=0 js=0 j = s j=s j=s时不为 0 0 0,这样上式的值就会变成 m c s mc_s mcs,我们从而可以求出 c s c_s cs的值。而 j − s j-s js的跨度是从 − s -s s t − s t-s ts,我们希望 − s > − m -s>-m s>m t − s < m t-s<m ts<m,这样除了 j = s j=s j=s时以外方括号里的值都一定为 0 0 0
m的范围
因此有 m > s m>s m>s m > t − s m>t-s m>ts。为了方便计算, m m m应越小越好,因此取 m = max ⁡ ( s , t − s ) + 1 m=\max(s,t-s)+1 m=max(s,ts)+1。这样,我们就能计算出 c s = 1 m ∑ k = 0 m − 1 ω − k s f ( ω k ) = 1 m ∑ k = 0 m − 1 e − 2 π i s k m f ( e 2 π i k m ) \begin{aligned} c_s&=\frac{1}{m}\sum\limits_{k=0}^{m-1}\omega^{-ks}f\left(\omega^k\right)\\ &=\textcolor{dodgerblue}{\frac{1}{m}\sum\limits_{k=0}^{m-1}e^{-\frac{2\pi isk}{m}}f\left(e^{\frac{2\pi ik}{m}}\right)} \end{aligned} cs=m1k=0m1ωksf(ωk)=m1k=0m1em2πiskf(em2πik)计算 f ( e 2 π i k m ) f\left(e^{\frac{2\pi ik}{m}}\right) f(em2πik)需要 O ( n ) O(n) O(n)的时间,求和需要 O ( m ) O(m) O(m)的时间,所以总共需要花费 O ( n m ) O(nm) O(nm)的时间。又 m ≤ t m\le t mt,故最坏情况下需要花费 O ( n t ) O(nt) O(nt)的时间。计算过程中我们只花了 O ( 1 ) O(1) O(1)的空间,所以程序花费的总空间是存储 a 1 , a 2 , ⋯   , a n a_1,a_2,\cdots,a_n a1,a2,,an所用的 O ( n ) O(n) O(n)的空间。这样,相比于背包问题的动态规划解法,我们节约了大量的空间。

因此,我们的算法是这样设计的:首先特判 s = 0 s=0 s=0 s = t s=t s=t s > t s>t s>t的情况。接下来求出 m = max ⁡ ( s , t − s ) + 1 m=\max(s,t-s)+1 m=max(s,ts)+1的值,若 m > 2 n m>2^n m>2n调用搜索算法求解,若 m ≤ 2 m m\le 2^m m2m调用蓝色式子求解。在计算复数的过程中我们反复利用了欧拉公式 e i θ = cos ⁡ θ + i sin ⁡ θ e^{i\theta}=\cos\theta+i\sin\theta eiθ=cosθ+isinθ。计算函数 f f f时我的思路是传入 θ = 2 π k m \theta=\frac{2\pi k}{m} θ=m2πk作为参数(而不是直接传入复数 z = e i θ z=e^{i\theta} z=eiθ),而 f ( z ) = ∏ j = 1 n ( 1 + z a j ) f(z)=\prod\limits_{j=1}^{n}\left(1+z^{a_j}\right) f(z)=j=1n(1+zaj),所以 z a j = cos ⁡ a j θ + i sin ⁡ a j θ z^{a_j}=\cos a_j\theta+i\sin a_j\theta zaj=cosajθ+isinajθ,这样就避免了求复数 z z z a j a_j aj次方,从而降低了误差。最终,我们得到 c s c_s cs后,理论上来讲如果和为 s s s的子集不存在那么 c s c_s cs应该为 0 0 0,但是因为计算过程中有舍入误差的存在,所以我们把子集不存在的条件放宽为 ∣ c s ∣ < 1 2 |c_s|<\frac{1}{2} cs<21,这样可以很大程度上避免误差带来的问题。同时,当 c s c_s cs计算的比较精确时,它实际上就等于和为 s s s的子集的个数。

完整的Python代码如下:

# encoding: utf-8

import math
from typing import *

class SubsetSumSolver:
    def __init__(self, a: List[int], s: int):
        self.a = a
        self.s = s
        self.n = len(a)
        self.t = sum(a)
    def search(self, u: int, m: int) -> bool: # search method (m>2^n)
        if m == 0:
            return True
        if u == 0:
            return False
        if m >= self.a[u - 1] and self.search(u - 1, m - self.a[u - 1]):
            return True
        return self.search(u - 1, m)
    def f(self, theta: float) -> complex:
        r = 1.
        for h in self.a: # h: a_j
            arg = h * theta # a_j*θ
            r  *= 1 + math.cos(arg) + 1j * math.sin(arg) # r*=(1+e^(i*a_j*θ))
        return r
    def complex_method(self) -> complex:
        m = max(self.s, self.t - self.s) + 1
        r = 0 # result
        for k in range(m):
            theta = 2 * math.pi * k / m
            f_result = self.f(theta) # f(e^(2πik/m))
            theta *= -self.s
            zs = math.cos(theta) + 1j * math.sin(theta) # e^(2πisk/m)
            r += zs * f_result
        r /= m
        return r
    def solve(self) -> bool:
        if self.s > self.t:
            return False
        if self.s == self.t:
            return True
        if self.s == 0:
            return True
        if max(self.s, self.t - self.s) + 1 > 2 ** self.n: # see if m>2^n
            return self.search(self.n, self.s)
        else:
            return abs(self.complex_method()) > 0.5

a = [73383, 66729, 31459, 76611, 70029, 11389, 10089, 63531, \
    87311, 64114, 1566, 30601, 45294, 92796, 57129, 18475, 17759, \
    25253, 93402]
s = 242514 # test data
solver = SubsetSumSolver(a, s)
print(solver.solve())

这个算法在绝大多数情况下都可以正常运行,除非出现一些特别极端的情况,比如 f ( z ) f(z) f(z)的模长特别大(接近 2 n 2^n 2n),则会放大舍入误差,造成结果不准确。不过经过我的测试,这种情况出现的概率微乎其微(我的测试数据还没有出现过这种情况),所以不必担心。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/127619.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript 隐秘者 | Console.xxx竟然如此好用

JavaScript 隐秘者 | Console.xxx竟然如此好用 文章目录JavaScript 隐秘者 | Console.xxx竟然如此好用一、控制台调试二、对象方法 &#x1f356;1)、.assert() 条件断言2)、.clear() 清空控制台3)、.count() 计算调用数 ⭕4)、.countReset() 重置计数器5)、.debug() 调试消息6…

文献翻译 (3):非支配排序遗传算法 (Non-dominated Sorting Genetic Algorithm, NSGA-II)

文章目录1 引入2 多目标优化3 更多的定义3.1 支配3.2 非支配集3.3 全局Pareto最优集4 NSGA-II1 引入 本文主要介绍多目标优化的基本概念以及NSGA-II。 2 多目标优化 多目标优化的优化目标之间存在一定的冲突&#xff0c;例如一个目标增长&#xff0c;导致另一个减少。因此这…

Vue2 新手上路无处不在的特殊符号,让人傻傻分不清 “:”、“.”、“@”、“#” 、“{{}}“ 、“$“

刚刚学vue没多久&#xff0c;经常分不清情况什么时候用什么符号&#xff1a; “:” 是指令 “v-bind”的缩写 “.”是修饰符 “”是指令“v-on”的缩写 &#xff0c;它用于监听 DOM 事件 “#”是v-slot的缩写&#xff1b; "{{}}" 插值语法 "$" &#…

智慧工厂在线云平台,助力企业降本增效!

随着传统制造企业规模的扩大&#xff0c;人工管理方法不可避免的产生延时、错误和矛盾&#xff0c;人工管理方法已经无法对生产管理实施有效的控制, 随着设备类型、数量不断增加&#xff0c;人工管理的方式已经无法满足生产过程中人、机、料、法、环、测的有效管理。如何将这些…

2022年——一个老老老程序员的杭州折腾之旅

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;无尽的折腾后&#xff0c;终于又回到…

[Android]视图的控触操作-MotionEvent

引入 对屏幕的任何操作&#xff0c;系统都会创建一个触摸事件的对象MotionEvent来应对这个操作。当点击手机屏幕的某一个视图时&#xff0c;最先感应到的是屏幕&#xff0c;因为Activity系统是分层的结构&#xff0c;底层是一些驱动&#xff0c;所以驱动就会得到信息并且把信息…

分布式共识算法——Paxos、ZAB、Raft

分布式算法 01 分布式基本理论 CAP理论 1998年&#xff0c;加州大学的计算机科学家 Eric Brewer 提出&#xff0c;分布式系统有三个指标。 一致性&#xff08;C&#xff09;&#xff1a;在分布式系统中的所有数据备份&#xff0c;在同一时刻是否同样的值&#xff0c;即写操…

第008课 - linux安装docker

文章目录 linux安装docker安装docker启动docker检查dockerdocker设置开机自启动linux安装docker 每一种容器都是一个完整的运行环境,容器之间互相隔离的。 windows的ghost工具就是类似docker。 从网上获取镜像,基于镜像,docker可以启动一个容器。 所以,我们以后想要装某种…

elasticsearch在linux环境安装遇到问题

es在linux环境安装遇到问题 1、启动失败日志 ERROR: [1] bootstrap checks failed [1]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be con…

对某颜色站的一次渗透实战

1. 前提 1.1 某颜色cms弱口令原理 去年的时候&#xff0c;在网上看到有些师傅在打击颜色站&#xff0c;当时自己也摸索着试试&#xff0c;利用一个叫做jiuse cms的站来进行批量测试。 这个cms的特点是非常的小&#xff0c;前台除了xss之外&#xff0c;基本上漏洞都在后台&…

区块链+游戏:未来真的有未来吗?

今年以来&#xff0c;伴随着元宇宙与P2E的火热浪潮&#xff0c;区块链以其强大的叙事能力势如破竹的切入到传统游戏领域&#xff0c;顶级风投、知名巨企纷纷入局&#xff0c;迸发出极大的经济潜能&#xff0c;引发了社会的广泛关注。 一方面&#xff0c;区块链技术在游戏的运用…

【漏洞复现】多语言文件包含漏洞分析

漏洞描述&#xff1a; ThinkPHP在开启多语言功能的情况下存在文件包含漏洞&#xff0c;攻击者可以通过get、header、cookie等位置传入参数&#xff0c;实现目录穿越文件包含&#xff0c;通过pearcmd文件包含这个trick即可实现RCE。 影响版本&#xff1a; 6.0.1 < ThinkPH…

【TypeScript】TS交叉类型联合类型(四)

&#x1f431;个人主页&#xff1a;不叫猫先生 &#x1f64b;‍♂️作者简介&#xff1a;前端领域新星创作者、华为云享专家、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &#x1f4ab;系列专栏&#xff…

Mybatis_Plus_@TableName,@TableField

思考一个问题:为啥继承BaseMapper< POJO >&#xff0c;能直接找到Mysql的表 默认情况下:mp根据BaseMapper泛型POJO类取数据库底下找与POJO类型一致的表 思考一个问题:如果把表user改成tb_user那么我们需要怎么解决 使用TableName注解 TableField 思考一个问题:我们新增…

学习.NET MAUI Blazor(五)、修改窗口标题

由于Blazor属于SPA&#xff08;single-page application&#xff09;&#xff0c;所以页面标题需要使用PageTitle组件来实现。但是在MAUI Blazor中&#xff0c;Blazor所在的位置是WebView&#xff0c;而标题是属于window。所以在MAUI Blazor中&#xff0c;使用PageTitle是无效的…

vue+bpmn-js 示例/基础入门/动态创建流程节点

先附一个完整的示例&#xff0c;下面做具体介绍 <template><div classcontainers><el-button click"saveXml"></el-button><div class"canvas" ref"canvas"></div><div class"properties" id…

YOLO系列中Anchor Based和Anchor Free的相爱相杀

前言 我们都知道按照是否出现RPN可将目标检测算法分为two-stage和one-stage&#xff0c;其中one-stage的一个主要代表便是YOLO系列&#xff0c;而根据是否存在先验锚框的定义我们也可以将其分为Anchor based和Anchor free两类&#xff0c;关于这两种也是各有优劣&#xff0c;但…

物联网终端的防护体系

针对漏洞的恶意行为分析 我们共捕获到 4 种针对 UPnP 漏洞的利用行为 1&#xff0c;如表 4.7 所示。从中可以看出&#xff0c;这些漏洞均为远程 命令执行类漏洞。另外我们也发现&#xff0c;当漏洞出现在特定端口时&#xff0c;攻击者一般不会经过 UPnP 的发现阶段&#xff0c…

携手用友低代码开发,德鑫物联将RFID融入企业服务大生态

如今&#xff0c;消费者凭借一部手机就可以轻松鉴别名酒的真伪&#xff1a;一些名酒在瓶盖的封膜下已经植入了RFID芯片&#xff0c;消费者用带有NFC功能的手机并安装相应的APP&#xff0c;只需靠近RFID芯片&#xff0c;即可获取产品品名、规格、生产日期等验证信息。 不只是名…

论文阅读:人机情绪的趋同、循环与溢出——基于 Twitter 涉中议题的数据分析

论文链接&#xff1a;https://shimo.im/files/vVqRVZGEXgcZbRqy/ 《人机情绪的趋同、循环与溢出...tter涉中议题的数据分析_黄阳坤.pdf》&#xff0c;可复制链接后用石墨文档 App 打开 摘要&#xff1a; 在传播研究情感转向的背景下&#xff0c;社交媒体上人机用户间的情绪互动…