想看动态规划详细思路直接往下面翻,在最下面
题目:
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
解法1:递归
不出所料,超时;
class Solution {
public int combinationSum4(int[] nums, int target) {
if(target == 0) {
return 1;
}
if(target < 0) {
return 0;
}
int ans = 0;
for(int num:nums) {
ans +=combinationSum4(nums, target-num);
}
return ans;
}
}
上面的递归方法之所以会超时,是因为有重复计算,比如计算 dp(4) 的时候计算了 dp(2),而计算 dp(3) 的时候会再次计算 dp(2),这样大量的重复计算导致了超时的产生。如果在递归的过程中,把已经计算了的结果放在数组中保存,那么下次需要再次计算相同的值的时候,直接从数组中读取同样的计算结果,就能省下大量重复的计算。
那么,接下来采用记忆化递归的方式;
解法二(优化DFS):
(代码在图下面)
class Solution{
public int combinationSum4(int[] nums, int target) {
int arr[] = new int[target + 1];
Arrays.fill(arr, -1);
arr[0] = 1;
return search(nums, target,arr);
}
private int search(int[] nums, int target,int arr[]) {
if (arr[target] != -1) {
return arr[target];
}
int ans = 0;
for (int num : nums) {
if (target >= num) {
ans += search(nums, target - num,arr);
}
}
arr[target] = ans;
return ans;
}
}
没什么好讲的,和第一种递归思路类似;有不懂的地方请在评论区留言,作者会回复
3.重点: dp解法(代码和思路在下面):
代码非常简练,但是思路值得推敲
这里new的dp数组中dp[ i ]的含义,是代表有i种组合方案的时候使用nums中的元素能组成的组成数的数量
也就是说可以理解为下面的公式,也就是一维情况的转移方程
dp[i]=dp[i-nums[0]]+dp[i-nums[1]]+dp[i-nums[2]]+…+dp[i-nums[target]]
这一步是一种dp技巧,需要多加练习
举例:
比如给定了nums = [1,3,5],target = 9;
相当于得到了dp[9] = dp[8] + dp[6] + dp[4];
意思是提出三种情况,分别是:
dp[9] = dp[8]+1;//降模后dp[8]+1种方案,下面同理
dp[9] = dp[6]+3;
dp[9] = dp[4]+5;
至于为什么dp[] = new int[target+1]而不是int[target];
是因为要考虑一种算上自己的情况:(举例如下)
dp[1] = dp[0]+0=1;
关于两层for循环交换的问题:
在我的做法中我将dp数组压缩成一维的数组,两次for循环不能交换位置;
如果交换位置,仅会出现小数在前的情况,例如只有1,1,2而缺少1,2,1这样的方案
但是如果展开为二维数组,那么交换两层for循环的顺序是可行的;可以自己试一下。
下面是代码:
class Solution {
public int combinationSum4(int[] nums, int target) {
int dp[] = new int[target+1];
dp[0]=1;
for(int i=1;i <= target;i++)
{
for(int num:nums)
{
if(i >= num)
{
dp[i] += dp[i-num];
}
}
}
return dp[target];
}
}