leetcode-494 目标和,cache的选择
题目如上。
我们假定,求从 nums 数组第 flag 位置开始目标和为 S 的组合数的函数为:G(nums, S, flag) 。
那么状态转移方程的表示为:G(nums, S, flag) = G(nums,S-nums[flag],flag+1) + G(nums,S+nums[flag],flag+1) 。一个背包类问题。
递归表示很好实现:
public int recursion(int[] nums, int S, int flag){ if (flag == nums.length) { if (S == 0) { return 1; } return 0; } return recursion(nums,S-nums[flag],flag+1)+recursion(nums,S+nums[flag],flag+1); }
相应的,指数级时间复杂度,回归过程中存在非常多的重复计算。需要用缓存优化掉这些重复计算。
问题就在于,如果要建立缓存,缓存需要有两个维度:S 和 flag 。我们需要沿着这两个维度前进推演最终结果,但不能用简单的二维数组建立缓存。
因为 S 的取值范围是不固定的,最大值为 S 与 nums 中所有元素的和,最小值为 nums 中所有元素的差,且可能存在负值。
官方题解中,将 S 加 1000,使 S 全部映射到正数空间中。
但我认为,无论问题规模的大小,直接申请长度为 1000 的连续数组空间比较浪费,更倾向于一维数组结合哈希表的方式构建缓存。
nums 由一维数组存储,存储的元素为一张哈希表,哈希表中存储 nums 对应的 S 的解:
public int findTargetSumWays(int[] nums, int S) { if (nums == null) { return 0; } Map<Integer, Integer>[] dpCache = new Map[nums.length]; return dp(nums, S, 0, dpCache); } /** * @Author Nxy * @Date 2020/4/28 * @Description G(nums, S, flag) * G(nums,S-nums[flag],flag+1)+G(nums,S+nums[flag],flag+1) */ public int dp(int[] nums, int S, int flag, Map<Integer, Integer>[] dpCache) { if (flag == nums.length) { if (S == 0) { return 1; } return 0; } if (dpCache[flag] != null) { Map<Integer, Integer> map = dpCache[flag]; if (map.keySet().contains(S)) { return map.get(S); } int tempRe = dp(nums, S - nums[flag], flag + 1, dpCache) + dp(nums, S + nums[flag], flag + 1, dpCache); map.put(S, tempRe); return tempRe; } dpCache[flag] = new HashMap<Integer, Integer>(); int tempRe = dp(nums, S - nums[flag], flag + 1, dpCache) + dp(nums, S + nums[flag], flag + 1, dpCache); dpCache[flag].put(S, tempRe); return tempRe; }
当你看清人们的真相,于是你知道了,你可以忍受孤独