一、前言
动态规划是算法中一个非常重要的部分,它可以代替回溯算法等解决问题,而且只要合理的得出状态转移方程,此类题目一般都可以较为简单的写出来,但是关键点就在于如何构造出状态转移方程。最近更新的这些题目都是我在力扣刷题是觉得较好的题目,希望和大家一起分享。
二、题目描述
三、题目分析
我觉得在明确知道这题可以用动态规划解决的话,首先应该思考如何构造出状态转移方程。首先我们的目的是将数组分为相等的两份,那么如果数组和为奇数,则直接返回false
int sum=0;
for(int i : nums)
sum+=i;
if(sum%2!=0)
return false;
在sum为奇数之后,我们令target=sum/2,那么只要我们在nums数组中找到几个数的和等于target,则可以返回true了。那么我们的状态转移方程可以设为dp[ i] [ j],就是指前i个数之和为j。
可知i应该为nums.length+1(将第0个数省去了,dp[0][0]不考虑),j为target+1。最后返回的应该是dp[nums,length][target]==target,它的意思是,如果前i个数的和为target,则返回true,否则返回false。
那么状态转移方程应该是什么呢?
dp[i][j]=?,试想应该存在两种情况,因为dp[i][j]是从dp[i-1][j]中过来的,如果j>nums[i-1](前面说了0不算,所以此处i-1),那么这个第i个数选不选都可以,所以
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-nums[i]+nums[i]);
反之,如果j<nums[i-1],那么这个第i个数一定不可以选 ,因为一旦选了,所选部分的和加起来就大于j了,那么最后的结果也就大于target,所以
dp[i][j]=dp[i-1][j];
得到了状态转移方程,代码就很容易得出了
public boolean canPartition(int[] nums) {
if(nums.length<2)
return false;
int sum=0;
for(int i : nums)
sum+=i;
if(sum%2!=0)
return false;
int target=sum/2;
int [][]dp=new int[nums.length+1][target+1];
for(int i=1;i<=nums.length;i++)
{
for(int j=1;j<=target;j++)
{
if(j>=nums[i-1])
dp[i][j]= Math.max(dp[i - 1][j], dp[i-1][j - nums[i - 1]] + nums[i - 1]);
else
dp[i][j]=dp[i-1][j];
}
}
return dp[nums.length][target]==target;
}
四、额外解法
在写完之后,我也在评论区看到了一些其他的解法,例如DFS、位运算等等...,位运算相对难以理解,我就简单说一下DFS的大概思路。
DFS(深度优先搜索)的方法大概是这样的,首先写一个dfs函数,里面传入一颗顺序二叉树(nums),target(跟上面一样,是数组和的一半),index(当前所在的树的层次)。
(图非原创,侵删)
第一层是空的,从第一层开始,左子树是数组的某个值,按照树的层数依次为1,2,3,5。右子树都是0。每次递归,如果遍历左子树,则递归中传入的target需要减去左子树上对应的值,index需要加一,如果遍历右子树则不需要对target做出改变,只需将index加一。直到!target=0,返回true结束,或者target<0或者index等于数组长度时返回false结束。
private boolean dfs(int[] nums, int target, int index) {
//targe等于0,说明存在一些元素的和等于sum/2,直接返回true
if (target == 0)
return true;
//如果数组元素都找完了,或者target小于0,直接返回false
if (index == nums.length || target < 0)
return false;
//选择当前元素和不选择当前元素两种情况
return dfs(nums, target - nums[index], index + 1)|| dfs(nums, target, index+1);
这个方法代码量真的很少,也很方便,理解起来也容易..除了通不过,超时了。所以这种方法我也只是单纯提出来作为一种技巧吧。
另外我立个flag,每日一题!!!