leetcode(494). Target Sum

来源:互联网 发布:云计算与大数据 idc 编辑:程序博客网 时间:2024/06/01 09:20

problem

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.

Note:
The length of the given array is positive and will not exceed 20.
The sum of elements in the given array will not exceed 1000.
Your output answer is guaranteed to be fitted in a 32-bit integer.

分析

这个问题可以使用穷举法来求解,但是时间复杂度为指数级,代码如下:

class Solution(object):    def findTargetSumWays(self, nums, S):        """        :type nums: List[int]        :type S: int        :rtype: int        """        if len(nums) == 0:            return 0        elif len(nums) == 1:            if nums[0] == S and nums[0] == -S:                return 2            elif nums[0] == S or nums[0] == -S:                return 1            else:                return 0        else:            return self.findTargetSumWays(nums[1:], S-nums[0])+self.findTargetSumWays(nums[1:], S+nums[0])

但是注意题目中要求所有的数字都是非负整数,数组长度不超过20,最大的和不超过2000,这样就可以使用动态规划来求解。

动态规划解法

动态规划的本质就是把求解原问题转化为求解规模由小到大的子问题,而且这些子问题之间还有联系,也就是大的问题可以利用上小的问题的答案,这样就从初始子问题逐步递推为原始问题了。

那么如何对这个问题进行子问题划分呢?这里把把子问题设置为前i个数字所有可能组合的和的个数,也就是f(i, m)表示前i个数字可能结果为m的组合数,这样f(n, target)就是原始问题的解。

ps:这个问题很像求解图的最短路径问题,即使只需要两点的最短路径还是要求所有点的最短路径。

时间复杂度:一共有n个子问题,每个子问题最大可能数为4000,T(n)=O(nl)l表示target的可能数。这个算法的改进是建立在所有的输入都是整数,且范围固定,这保证了l不会无限从而时间复杂度超过指数级。

ps:发现在二维dp问题中使用hash表可以节省一些空间和不必要的判断,只不过在代码上有一些处理,例如需要判断之前是否存在这个key,不能直接全都初始化。

下面是使用hash表的二维dp代码,超越了68%的提交。(注意在dict中整数可能为+0和-0都映射到一个key上)

class Solution(object):    def findTargetSumWays(self, nums, S):        """        :type nums: List[int]        :type S: int        :rtype: int        """        n = len(nums)        d = [{} for _ in range(n)]        d[0][nums[0]] = 1        if -nums[0] not in d[0]:            d[0][-nums[0]] = 1        else:            d[0][-nums[0]] += 1        for i, num in enumerate(nums[1:], start=1):            for pre in d[i-1].keys():                if pre + num not in d[i]:                    d[i][pre+num] = d[i-1][pre]                else:                    d[i][pre+num] += d[i-1][pre]                if pre - num not in d[i]:                    d[i][pre-num] = d[i-1][pre]                else:                    d[i][pre-num] += d[i-1][pre]        return d[n-1][S] if S in d[n-1] else 0

下面是使用数组而不是hash表的代码,可以看到由于全部初始化所以不用像dict一样需要考虑key是否存在直接赋值即可,并且由于python支持负数索引所以不用像Java和c++一样进行索引的映射,所以python实现的非常简洁,不过由于之前所提到的,使用数组实现的话需要一些不必要的比较,所以只超过了10%的提交。

class Solution(object):    def findTargetSumWays(self, nums, S):        """        :type nums: List[int]        :type S: int        :rtype: int        """        n = len(nums)        d = [[0 for _ in range(2001)] for _ in range(20)]        d[0][nums[0]] += 1        d[0][-nums[0]] += 1        for i, num in enumerate(nums[1:], start=1):            for j in range(-1000, 1001):            #直接使用负数索引即可,非常方便                if d[i-1][j] > 0:                    d[i][j + num] += d[i-1][j]                    d[i][j - num] += d[i-1][j]        if S>1000 or S<-1000:            return 0        return d[n-1][S]

总结

对于这种解空间有限制的题目可以使用二维动态规划解法(解空间有限制保证了dp可以优于暴力解法,如果是浮点数的话也可以使用dict,dp方法来求解,但是时间复杂度也是指数级,其实就相当于把递归算法调过来),另外对于二维规划问题在规模较小时使用list(dict)可能会好于二维list。

原创粉丝点击