目标和 -「回溯」&「动规」

494. 目标和

 

本篇文章将介绍 目标和,但又不仅仅只介绍该题目,而是从「回溯」和「动规」的角度分析该题目,同时也对「回溯」和「动规」进行了一波对比!血赚!!

暴力回溯

本小节主要分析如何通过回溯暴力求解,至于如何优化放在下一小节。关于回溯详细讲解可见 回溯 (DFS) 算法框架

如果单纯直接暴力解决该问题,其实不算难,因为这个问题就是一个很基础的回溯模版题

我们每一次的选择无非就是「+ / -」,而结束条件就是「递归完数组后,满足目标和 或 不满足目标和」

很容易可以写出代码:

是不是很 easy,完全就是套模版。但是时间直接爆炸 (虽然过了,但是很不满意!!)

4

回溯 + 备忘录

其实「回溯」的优化方法只有一种,就是判断是否存在「重叠子结构」。如果存在,就用「备忘录」记录子结构的结果,避免重复计算

显然这个问题是存在「重叠子结构」滴!但是怎么找出来呢??我们把上面的结构稍微优化一下,方便看出「重叠子结构」

现在我们极端假设nums[index] = 0,是不是这两个递归就变成一模一样的了,这就是「重叠子结构」

那我们如何存储「重叠子结构」的结果呢??我们就需要思考如何才能描述一个「子结构」

假设[1,2,3,...,i,...,n-1], target = 10

如果现在我们已经计算出一种组合使得[1,2,3,...,i] = 5,那么我们现在只需要计算[i+1,...,n-1] = 10 - 5存在多少种组合就可以

如果后面我们又计算出另外一种组合使得[1,2,3,...,i] = 5,我们现在依旧需要知道[i+1,...,n-1] = 10 - 5的组合数

所以我们如果把[i+1,...,n-1] = 10 - 5的组合数记录下来,下次遇到就可以直接用了,我们可以用一个Map<Integer, Integer>[]来记录

经过上面的分析,就可以很容易写出优化后的代码:

经过一波优化后,现在的执行时间如下:(是不是又感觉自己可以了,但是我还是不满意!!!)

5

动态规划

先来分析一下这个问题。从另一个角度看问题,你会发现新世界的大门!!

说白了,这个题目就是把nums[]划分为两个部分A && B,对A集合内的元素执行+操作,对B集合内的元素执行-操作。所以有sum(A) - sum(B) = target

基于上面的前提,我们来一波转换:

所以现在我们的问题转换成「能否在集合nums中找到恰好和为sum(A)的组合」。这不就变成了题目 分割等和子集 嘛!!关于这个题目的详细分析可见 经典动态规划:子集背包问题

关于dp[][]数组的定义有一点不一样,本问题的定义如下:对于前i个元素,当前背包容量j,这种情况下背包可以恰好装满的所有组合数

与此同时,base case 也有一点不一样!!dp[0][...] = 0显然都是没有满足要求的组合滴,但是dp[0][0] = 1是个例外。需要注意的是,dp[...][0]并不能算进 base case 中

至此,我们可以写出代码:

下面看看动规的执行时间吧!!(是不是再次感觉自己又可以了,这次貌似真的可以了hhhhh)

6

动态规划优化

dp 二维 -> 一维

先留个坑,以后再补!!!!

今天 (2022-09-10 22:09:45) 来补上这个坑!!

关于「二维 -> 一维」的优化可以先看 动态规划之最长回文子序列「dp 空间优化」

注:第二重循环需要反向遍历

如果正向遍历,新算出来的dp[j]会覆盖掉上一行的值,随着不断的向右,会用到左边的值,所以造成了上一行值的丢失

而如果反向遍历,随着不断的向左,一定不会用到右边的值,所以可以覆盖!!

具体如下图所示:

71

此时dp[j - nums[i - 1]]的值已经被覆盖了,所以正向遍历会出现问题!!