1. 程式人生 > >【leetcode】 target sum 與 Subset sum

【leetcode】 target sum 與 Subset sum

Leetcode中的 target sum 問題其實可以轉化為 Subset sum。關於Subset sum,可以參考我的前一篇部落格 Ksum 與 Uncertain sum (子集和問題 Subset sum )

先貼一下 Leetcode 中關於 target sum (Leetcode 494)的問題描述:

You are given a list of non-negative integers, a1, a2, …, an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol. Find out how many ways to assign symbols to make sum of integers equal to target S. Example 1:

Input: nums is [1, 1, 1, 1, 1], S is 3. Output: 5 Explanation: -1+1+1+1+1 = 3 +1-1+1+1+1 = 3 +1+1-1+1+1 = 3 +1+1+1-1+1 = 3 +1+1+1+1-1 = 3 There are 5 ways to assign symbols to make the sum of nums be target 3. Note: 1.The length of the given array is positive and will not exceed 20. 2.The sum of elements in the given array will not exceed 1000. 3.Your output answer is guaranteed to be fitted in a 32-bit integer.

這個問題是說給定一個數組 nums = [a1,a2, ,ana_1,a_2,\cdots,a_n] 和一個目標數 S,在 nums 中的每個數前面可以加上正號或符號,使得加上正負號之後的所有數之和等於目標數 S,試計算通過正負號組合再求和後得到 S 的組合數目有多少種。

這個題比較常規的解法是搜尋法和動態規劃法,其實還有種做法是將這個問題轉化為 “子集和問題”。下面先介紹後一種方法,再介紹前面兩種常規的方法。

轉化為 “子集和問題” 來求解

怎麼將問題轉化為子集和問題呢?可以這樣來思考: 根據題目意思, nums 陣列中的所有元素前面需要新增正號或者負號,那麼我們把添加了正號的所有元素之和記為 P

P,把所有添加了負號的元素之和記為 NN。容易知道,有下面兩個式子成立: P+N=sum(nums)PN=S P + N = sum(\text{nums}) \\ \hspace{-1.5cm} {P - N = S} 於是就可以得到:P=(sum(nums)+S)/2P = (sum(\text{nums})+S)/2 也就是說只需要在陣列 nums 中找到部分元素使得它們的和等於 (sum(nums)+S)/2(sum(\text{nums})+S)/2 就行了,這個就是 Leetcode 中的 Combination sum I II III IV(Leetcode 39, 40,216, 377),這跟之前在部落格 Ksum 與 Uncertain sum (子集和問題 Subset sum ) 中介紹的子集和問題有點不同的是,之前的部落格中的問題只需要判斷是否存在部分元素之和等於目標數值(不存在就是0,存在就是1),而這裡是需要計算有多少種組合能得到 P=(sum(nums)+S)/2P = (sum(\text{nums})+S)/2(不存在就是0,存在的話還需判斷多少種情況)。不過萬變不離其宗,其思路並沒有變化,在之前我的部落格 Ksum 與 Uncertain sum (子集和問題 Subset sum ) 裡所寫的原始問題的程式碼中,只需將 True 換成 1,False 換成 0,“或運算” 換成 “加法”,就可以得到這個問題的解了。

Python 程式碼 如下:

    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] = 1
        for j in range(1,M+1):
            dp[0][j] = 0
        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]
    
    
    def TargetSum(nums, S):
        if sum(nums)<S:
            return 0
        if int((S+sum(nums))%2)==1:
            return 0
        target = int((S+sum(nums))/2)
        return func(nums, target)

上面的做法中用了二維陣列,當然用一維陣列也可以做。令 dp[ii] 表示能夠組合成和為 ii 的組合數目,那麼需要求解的就是 dp[(sum(nums)+S)/2(sum(\text{nums})+S)/2] 的值了。之前在部落格 Ksum 與 Uncertain sum (子集和問題 Subset sum ) 中有說,規定空集的和為0,也就是說 dp[0]=1。將其作為邊界條件,那麼還需要知道在一般情形中的迭代公式。可以這樣來思考:

step1:如果要得到一個和為 TT 的組合數 dp[TT],那麼它肯定等於和為 TtermT-\text{term} 的累加,這裡的 term\text{term} 是 nums 中的元素,而且 term\text{term} 不能重複使用。 (注: TtermT-\text{term}\ge 0)

step2:如何實現 “和為 TtermT-\text{term} 的累加” 而且 “term\text{term} 不能重複使用” 呢?答案是 “自疊加” 和 “迴圈遍歷”。“自疊加” 是指的形如 “a=a+ba=a+b” 這樣的型別,即在原始 aa 的基礎上加個 bb 成為新的 aa。“迴圈遍歷” 是指的對 nums 中的每個 term\text{term} 進行遍歷,當取定一個 term\text{term} 時,計算 dp[TtermT-\text{term}], dp[T1termT-1-\text{term}],dp[T2termT-2-\text{term}],\cdots(只要滿足 \ge 0,可以一直進行下去)。迴圈遍歷 term 時,由於每次遍歷時 term 之間相互獨立,所以每個 term 只會被使用一次。

Python 程式碼如下:

    def TargetSum(nums, S):
        if sum(nums)<S:
            return 0
        if int((S+sum(nums))%2)==1:
            return 0
        target = int((S+sum(nums))/2)
        dp = [0]*(target+1)
        dp[0] = 1
        for term in nums:
            i = target
            while(i>=term):
                dp[i] = dp[i] + dp[i-term]
                i = i-1
        return dp[target]
通過搜尋法來求解

這個題目也可以通過搜尋法來做。如果對圖演算法比較熟悉的話,其實搜尋法應該是最容易想到解這個題的方法。

思路是將陣列中的每個元素看作一個節點,從第一個元素搜尋到最後一個元素為止。每經過一個元素,將其帶正負號的兩種情況分別都考慮,依次累加到最後一個元素。比如說對如陣列 [1, 2, 1],對元素加正負號之後,目標值是 -2,那麼搜尋可以形象地用下面地圖來描述:

可以看出有兩種方式能夠得到 -2,一種是 -1 + 2 + (-1) = -2,另一種是 1 + (-2) + (-1) = -2。用搜索的方法需要搜尋所有情況,雖然原理很簡單,但是時間複雜度較高,對較短的陣列比較適宜,但是對較長的陣列容易超時。

Pathon 程式碼如下:

    def TargetSum(nums, S):
        def dfs(depth, sums):
            if depth == len(nums):
                if sums == S:
                    return 1
                else:
                    return 0
            return dfs(depth+1, sums+nums[depth]) + dfs(depth+1, sums-nums[depth])
        return dfs(0, 0)
通過動態規劃法來求解

當然,這個題也可以用常規的動態規劃方法來求解。之前在將問題轉化為 “子集和問題” 後雖然用了動態規劃的方法,不過此處說的是針對原始問題的動態規劃法,設定的 dp 和構造的子結構不一樣。

對於這個題的分析,可以如下: 首先,對於給定的陣列 nums,不論將其怎麼組合都有個上下界,上界是 sum(nums)sum(\text{nums}),即 nums 中所有元素均加上正號;下界是 sum(nums)-sum(\text{nums}),即 nums 中所有元素均加上負號。那麼將 nums 中的元素新增正負號進行求和後,其和值必定落在 [sum(nums),sum(nums)][-sum(\text{nums}), sum(\text{nums})] 之內,也就是說和值的取值長度有 2sum(nums)2*sum(\text{nums})+1。

由於指標不能為負,所以需要將取值區間由 [sum(nums),sum(nums)][-sum(\text{nums}), sum(\text{nums})] 平移得到 [0,2sum(nums)[0,2*sum(\text{nums})+1]],那麼之前的 0 就被平移成了 sum(nums)sum(\text{nums}),記它為 center 值。

令 dp[ii][jj] 表示 nums 中前 ii 個元素組合得到 jj 的組合方法數,因為在平移前有 dp[0][0]=1,所以平移後應該是 dp[0][center] = 1,這是動態規劃的 base 邊界條件。下面來思考結構式子:

由於每一個 dp[ii][jj] 都可以寫成 dp[i1i-1][jaij-a_i] 和 dp[i1i-1][j+aij+a_i] 之和,其中 aia_i 表示 nums 中第 ii 個元素的值,寫成公式即: \hspace{4cm} dp[ii][jj] = dp[i1i-1][jaij-a_i] + dp[i1i-1][j+aij+a_i]

這個思維是指,dp[ii][jj] 會接到來自 dp[i1i-1][jaij-a_i] 和 dp[i1i-1][jaij-a_i] 的值。那麼,換個思維角度講, dp[ii][jj] 也會將自己的值傳遞給 dp[i+1i+1][jai+1j-a_{i+1}] 和 dp[i+1i+1][j+ai+1j+a_{i+1}] ,於是就有公式如下: \hspace{5cm} dp[i+1i+1][jai+1j-a_{i+1}] += dp[ii][jj] \hspace{5cm} dp[i+1i+1][j+ai+1j+a_{i+1}] += dp[ii][jj]

Python 程式碼如下:

    def TargetSum(nums, S):
        # dp[i][j] 表示nums中前i個元素組合得到j的方法數
        sums = sum(nums)
        # 易知,nums加符號後的組合,其和一定在 [-sums, sums]之間
        if sums < S:  #  當總和要小於給定數S,那麼不可能有組合使得和為S,返回0種方法
            return 0
        dp = [[0 for _ in range(2*sums+1)] for _ in range(len(nums)
            
           

相關推薦

leetcode target sum Subset sum

Leetcode中的 target sum 問題其實可以轉化為 Subset sum。關於Subset sum,可以參考我的前一篇部落格 Ksum 與 Uncertain sum (子集和問題 Subset sum )。 先貼一下 Leetcode 中關於 ta

leetcode327. Count of Range Sum

htm color keys clas .html enum equals code range 題目如下:解題思路:本題是 560. Subarray Sum Equals K 的升級版,可以參見560的解題思路。唯一的區別是560只給了一個精確的和K,而本題是給了一個和

leetcode64. (Medium) Minimum Path Sum

解題思路: DP 提交程式碼: class Solution { public int minPathSum(int[][] grid) { int row=grid.length,column=grid[0].length; int[][] dp=new

LeetCode209. Minimum Size Subarray Sum 解題報告(Python)

題目描述: Given an array of n positive integers and a positive integer s, find the minimal length of a

LeetCode931. Minimum Falling Path Sum 解題報告(Python)

目錄題目描述題目大意解題方法動態規劃相似題目參考資料日期 題目描述 Given a square array of integers A, we want the minimum sum of a falling path through A. A fal

LeetCode862. Shortest Subarray with Sum at Least K 解題報告(C++)

作者: 負雪明燭 id: fuxuemingzhu 個人部落格: http://fuxuemingzhu.cn/ 目錄 題目描述 題目大意 解題方法 佇列 日期 題目

leetcode129.(Medium)Sum Root to Leaf Numbers

解題思路: 思路一: 使用DFS,然後用一個list(記為curPath)來記錄當前路徑的所有數值,當訪問到根節點時將curPath中的數值轉換為一個整型數值,計入到結果(res)中。 提交程式碼: class Solution { public int sum

LeetCode771. 寶石石頭

給定字串J 代表石頭中寶石的型別,和字串 S代表你擁有的石頭。 S 中每個字元代表了一種你擁有的石頭的型別,你想知道你擁有的石頭中有多少是寶石。 J 中的字母不重複,J 和 S中的所有字元都是字母。字母區分大小寫,因此"a"和"A"是不同型別的石頭。 示例 1: 輸入:

LeetCode211. 新增搜尋單詞

題目描述: 設計一個支援以下兩種操作的資料結構: void addWord(word) bool search(word) search(word) 可以搜尋文字或正則表示式字串,字串只包含字母 . 或 a-z 。 . 可以表示任何一個字母。 示例: addWor

LeetCode01 Two SumHashMap

給定一個整數陣列和一個目標值,找出陣列中和為目標值的兩個數。 你可以假設每個輸入只對應一種答案,且同樣的元素不能被重複利用。 示例: 給定 nums = [2, 7, 11, 15], target = 9 因為 nums[0] + nums[1] = 2 +

LeetCode040. Combination Sum II

log bsp for ont end ati 無法 clas class 題目: Given a collection of candidate numbers (C) and a target number (T), find all unique combinatio

LeetCode064. Minimum Path Sum

ive rom right ott path sum 處理 tom ber its 題目: Given a m x n grid filled with non-negative numbers, find a path from top left to bottom ri

LeetCode039. Combination Sum

set sha leet delet als unique ati solution gin 題目: Given a set of candidate numbers (C) (without duplicates) and a target number (T), fin

LeetCode167. Two Sum II - Input array is sorted

etc 想法 ice ted integer turned abs ascend 要求 原題鏈接:https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/tabs/description/ 題目要求:Gi

Leetcode113Path Sum II

imp col public 集合 註意 integer print body 元素 Given a binary tree and a sum, find all root-to-leaf paths where each path‘s sum equals the gi

LeetCode107.Combination Sum II

題目描述(Medium) Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates 

LeetCode106.Combination Sum

題目描述(Medium) Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique co

LeetcodeDFS 112. Path Sum / 路經總和

Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.

LeetcodeDP-二維陣列 64. Minimum Path Sum / 最小路徑和

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum

LeetCode127.Minimum Path Sum

題目描述(Medium) Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes