Ksum 与 Uncertain sum (子集和问题 Subset sum )

上一篇博客【Leetcode】从 2sum 到 3sum, 4sum, Ksum 中介绍了Ksum,就是 “一个数组中的K个数之和等于给定数” 的问题。如果进一步来说,给定一个数组(元素不重复),请问是否存在某些数之和等于给定数?

Ksum 是固定了元素的数量为K个,很容易去思考,至少知道一个暴力解的K层循环。但是如果并不给定K,即参与求和的元素个数不确定(uncertain),只是某些元素和为给定的 target,这又该怎么做呢?其实这就是很经典的 “子集和问题”。
(注: 可参考 Leetcode中的 Combination sum I II III IV(Leetcode 39, 40,216, 377)

子集和问题
已知集合 S = { a 1 , a 2 , a 3 , ⋯   , a n } S=\{a_1,a_2,a_3,\cdots,a_n\} S={a1,a2,a3,,an} 中的元素都是正整数且互不相同,给定另外一个正整数 M M M,试问是否存在 S S S 的一个子集使得其中的元素之和等于 M M M?

对于 “子集和问题”,常规的做法就是动态规划法和递归法,下面详细叙述一下这两种方法。

用动态规划法 (DP) 解 “子集和问题”

我们定义一个布尔变量 dp[ i i i][ j j j],它表示集合 S S S 中前 i i i 个元素里 (包括第 i i i 个) 是否存在某些元素之和等于 j j j。若存在,则 dp[ i i i][ j j j] = 1 =1 =1;若不存在,则 dp[ i i i][ j j j] = 0 =0 =0

先考虑边界情况,即 i = 0 i=0 i=0 j = 0 j=0 j=0 的情况。
(1) 对于 j = 0 j=0 j=0,此处规定空集的元素和为0,而每个集合都有子集为空集,所以一定有 dp[ i i i][ 0 0 0] = 1;
(2) 对于 i = 0 i=0 i=0,它对应的是前 0 个元素,即空集。那么只会有 dp[ 0 0 0][ 0 0 0] = 1,其余 j j j 的取值都是得到: dp[ 0 0 0][ j j j] = 0 ( j ≠ 0 j\neq 0 j̸=0)

再考虑常规情况,即 dp[ i i i][ j j j] ( i ≠ 0 i\neq 0 i̸=0 j ≠ 0 j\neq 0 j̸=0)。
首先,如果 dp[ i − 1 i-1 i1][ j j j] = 1,那么必有 dp[ i i i][ j j j] = 1。因为前 i − 1 i-1 i1 个元素存在子集和为 j j j 的话,那么前 i i i 个元素肯定存在子集和为 j j j 的子集,原因是前 i i i 个元素包含前 i − 1 i-1 i1 个元素。
那么,如果 dp[ i − 1 i-1 i1][ j j j] = 0 呢?此种情况下,需要考虑到第 i i i 个元素值 a i a_i ai。为什么这么说呢?因为 dp[ i − 1 i-1 i1][ j j j] = 0 表示前 i − 1 i-1 i1 个元素里面没有子集和为 j j j 的子集存在,但是如果前 i − 1 i-1 i1 个元素里面有子集和为 j − a i j-a_i jai 的子集存在的话,那么前 i i i 个元素中不就有了子集和为 j j j 的子集存在了吗?

将上面的讨论情况总结一下,可以写成下面的公式:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] , 如 果   c o n d i t i o n 1 d p [ i − 1 ] [ j ] ∣ ∣ d p [ i − 1 ] [ j − a i ] ,   如 果   c o n d i t i o n 2 dp[i][j]=\left\{ \begin{array}{l} dp[i-1][j], \hspace{2.8cm}如果 \ condition1 \\ dp[i-1][j] || dp[i-1][j-a_i], \ \hspace{0.14cm} 如果\ condition2 \end{array} \right. dp[i][j]={dp[i1][j], condition1dp[i1][j]dp[i1][jai],  condition2
上面式子中的符号 “||” 是真假值运算的 “或” , c o n d i t i o n 1 condition1 condition1 是指的是 “前 i i i 个元素中和为 j j j的子集不包括第 i i i 个元素”, c o n d i t i o n 2 condition2 condition2 是指的是 “前 i i i 个元素中和为 j j j的子集包括第 i i i 个元素”。

Python 代码如下:

# nums是给定的集合(这里用数组形式表示),其中每个元素都是正整数
# M是给定的正整数,需要找出nums中的子集和等于它
def func(nums, M):
    dp = [[0 for _ in range(M+1)] for _ in range(len(nums)+1)]
    for i in range(len(nums)+1):
        dp[i][0] = True
    for j in range(1,M+1):
        dp[0][j] = False
    for i in range(1,len(nums)+1):
        for j in range(M+1):
            dp[i][j] = dp[i-1][j]
            if j>= nums[i-1]: # 注:由于上面做了padding,所以此处的第i个元素即为nums[i-1]
                dp[i][j]=dp[i-1][j]|dp[i-1][j-nums[i-1]]
    return dp[len(nums)][M]

当然,上面这个动态规划也有个不好的地方,就是要创建一个记忆性的 dp 二维数组,用于存储数据,可以知道这个 dp 二维数组的维度是 (len(nums)+1) × \times × (M+1) 维的。当给定的 M 非常大时,那么这个二维数组将非常占用内存空间。

用递归法解 “子集和问题”

用递归法求解,其思路与上面一样,不同的是递归法是 “自上而下”,而动态规划法是 “自下而上”。

Python 代码如下:

def func(nums, M):
    return isSubset(nums, len(nums), M)
    
def isSubset(nums, k, c):
    if c==0:
        return True
    if k==0 and c!=0:
        return False
    if c>=nums[k-1]:
        return isSubset(nums, k-1, c)|isSubset(nums, k-1, c-nums[k-1])
    else:
        return isSubset(nums, k-1, c)

通过上面的总结,可以知道虽然 Ksum 和 Uncertain sum 的题目求解问题描述差不多,但是解法上却相差很多(Ksum 的解法可以参见我上一篇博客【Leetcode】从 2sum 到 3sum, 4sum, Ksum)。

经常总结,经常思考,这样才会慢慢有进步。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值