【问题标题】:Print all subsets of an array with sum equal to the target sum打印总和等于目标总和的数组的所有子集
【发布时间】:2021-01-09 05:56:22
【问题描述】:

给定一个包含 N 个元素的数组,找到总和等于目标值的数组的所有子集。
我已经看到该站点上所有与子集总和相关的旧问题,但没有一个适用我。

输入格式

  • 输入的第一行包含一个整数 N 大小的数组
  • 第二行包含用空格分隔的数组元素
  • 目标总和值

输出格式

  • 打印所有子数组(元素的索引)。

我的代码适用于小输入,但 N >150 需要很长时间。 有没有其他有效的算法可以做到这一点。 请告诉我如何针对更大的输入优化此代码。
这是我的代码

from collections import deque

class Pair:
    def __init__(self, i, j, path_set):
        self.i = i
        self.j = j
        self.path_set = path_set

def get_subsets(arr, n, value): 
    """
    This function appends all the possible paths in result list for the given target sum
    Arguments:
        arr = A list of numbers
        n = number of elements in that list arr
        value = Target sum for which we want to generate table
    """
    # return immediately if there is no possible subset in arr whose sum is equal to value
    if dp[n][value] == False:
        return
        
    queue = deque()
    queue.append(Pair(n, value, set()))

    while len(queue) > 0:
        pair = queue.popleft()
        if pair.i == 0 or pair.j == 0:
            result.append(pair.path_set)
        else:
            exclude = dp[pair.i - 1][pair.j]
            if exclude:
                queue.append(Pair(pair.i-1, pair.j, pair.path_set))

            if pair.j >= arr[pair.i-1]:
                include = dp[pair.i - 1][pair.j - arr[pair.i -1]]
                if include:
                    b = pair.path_set.copy()
                    b.add(pair.i - 1)
                    queue.append(Pair(pair.i - 1, pair.j-arr[pair.i-1], b))
            

def make_dp(arr, n, value):
    """
    This function makes a table of target sum equal to the value
    Arguments:
        arr = A list of numbers
        n = number of elements in that list arr
        value = Target sum for which we want to generate table
    Returns:
        dp = A 2D boolean table
    """
    dp = [[False for i in range(value+1)] for j in range(n+1)]
    
    for i in range(n+1):
        for j in range(value+1):
            if j ==0:
                dp[i][j] = True
            elif i == 0:
                dp[i][j] = False
            else:
                if dp[i-1][j]:
                    dp[i][j] = True
                elif j >=arr[i-1]:
                    if dp[i-1][j-arr[i-1]]:
                        dp[i][j] = True
    return dp

if __name__ == '__main__':
    n = int(input())
    arr = list(map(int, input().split()))
    value = int(input())
    dp = make_dp(arr, n, value)
    result = []
    get_subsets(arr, n, value)
    print(result)

花费大量时间的输入:

200
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
200

请优化此代码或告诉我任何其他方法来做同样的事情。 提前致谢。

【问题讨论】:

    标签: python dynamic-programming cp subset-sum


    【解决方案1】:

    您可能会发现使用 itertools 和组合更有效。代码也简单多了。

    from itertools import chain, combinations
    
    li = [1,2,3,4,5,6]
    s=12
    
    itr=chain.from_iterable(combinations(li, n) for n in range(len(li)+1))
    
    result = [el for el in itr if sum(el)==s]
    
    print(result)
    

    输出:

    [(1, 5, 6), (2, 4, 6), (3, 4, 5), (1, 2, 3, 6), (1, 2, 4, 5)]
    

    【讨论】:

    • 感谢您的友好回复,但您的代码仅适用于较小的输入,对于较大的 N 值将花费大量时间(TLE)。您也可以尝试我的测试用例已经提供了我的问题。
    【解决方案2】:

    您可以通过创建指向其各自索引的累积和字典在 O(n) 时间内获得此结果。当字典中存在一个总和 s+T 与总和 s 时,您的范围加起来为 T

    from itertools import accumulate
    
    A      = list(range(1,201))
    T      = 200
    sums   = {s:i for i,s in enumerate(accumulate(A)) }
    result = [ [*range(i+1,sums[s+T]+1)] for s,i in sums.items() if s+T in sums ]
    
    print(result)
    # [[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], 
    #  [37, 38, 39, 40, 41], 
    #  [199]]
    

    即使对于列表中的 100 万个值,这也只需要不到一秒钟的时间。

    请注意,这假设数组中的所有元素都 > 0。

    只需进行一些更改即可支持零值和负值:

    from itertools import accumulate
     
    A      = [*range(-10,11)]
    T      = 20
    
    sums   = dict()
    for i,s in enumerate(accumulate(A)):
        sums.setdefault(s,[]).append(i)
        
    result = []
    for cum,starts in sums.items():
        if cum+T not in sums: continue
        result.extend( [*range(s+1,e+1)] for s in starts 
                                         for e in sums[cum+T] if s<e )
    
    print(A)
    # [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    print(result)  
    # [[9, 10, 11, 12, 13, 14, 15, 16], [12, 13, 14, 15, 16]]
    

    对于包含 100 万个值的列表,这需要 2-3 秒,但可能会更长,具体取决于结果的大小。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-29
      • 2019-06-27
      • 1970-01-01
      • 1970-01-01
      • 2016-04-12
      相关资源
      最近更新 更多