今天开始进入DP的学习!新的一周,加油!
1.斐波那契数(力扣509)
本题虽然简单,但要按照解决DP问题的流程做。首先确定DP数组和下标的含义DP[i]即为第i个斐波那契数,接着找到递推公式:DP[i] = DP[i-1]+DP[i-2],递推公式初始化:DP[0]=0 DP[1]=1,举例推导:0,1,1,2,3,5,8,13.....
public int fib(int n) { if (n <= 1) return n; int[] arr = new int[n+1]; arr[0] = 0; arr[1] = 1; for (int i = 2; i <= n; i++) { arr[i] = arr[i-1] + arr[i-2]; } return arr[n]; }
2.爬楼梯(力扣70)
DP[i]表示爬到第i层的方法数,相当于从第i-1层走一步或者第i-2层走两步,DP[i] = DP[i-1]+DP[i-2],初始化DP[1]=1 DP[2]=2,举例推导:1,2,3,5....
public int climbStairs(int n) { if(n <= 2) return n; int[] arr = new int[n+1]; arr[1] = 1; arr[2] = 2; for (int i = 3; i < arr.length; i++) { arr[i] = arr[i-1] + arr[i-2]; } return arr[n]; }
3.使用最小花费爬楼梯(力扣746)
DP[i]表示爬到第i层的最小花费,DP[i] = min(DP[i - 1], DP[i - 2]) + cost[i],初始化DP[0] = cost[0],DP[1] = cost[1],最后取最后两层的最小值就是爬到楼顶的最小花费。
public int minCostClimbingStairs(int[] cost) { int[] arr = new int[cost.length]; arr[0] = cost[0]; arr[1] = cost[1]; for (int i = 2; i < arr.length; i++) { arr[i] = Math.min(arr[i-1],arr[i-2])+cost[i]; } return Math.min(arr[arr.length-1],arr[arr.length-2]); }
4.不同路径(力扣62)
由于小人只能向下或向右移动,那么状态转移只有两种可能,从上边来或从左边来,则DP[i][j] = DP[i -1][j]+ DP[i ][j-1],当i或j为零时说明在最上行或者最左列了,状态转移只有一种可能,则DP[i][0]=DP[0][j]=1。
public int uniquePaths(int m, int n) { int[][] arr = new int[m][n]; for (int i = 0; i < m; i++) { arr[i][0] = 1; } for (int j = 0; j < n; j++) { arr[0][j] = 1; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { arr[i][j] = arr[i-1][j]+arr[i][j-1]; } } return arr[m-1][n-1]; }
5.不同路径2(力扣62)
本题较上题多了障碍物,当在第一行或者第一列遇到障碍物时,则该行之后或该列之后都无法达到,注意break,遍历其他时如果遇到障碍物则DP[i][j] = 0,否则DP[i][j] = DP[i-1][j] + DP[i][j-1]。
public int uniquePathsWithObstacles(int[][] obstacleGrid) { int[][] dp = new int[obstacleGrid.length][obstacleGrid[0].length]; for (int i = 0; i < obstacleGrid.length; i++) { if(obstacleGrid[i][0] == 0) dp[i][0] = 1; //后面都到不了 else break; } for (int j = 0; j < obstacleGrid[0].length; j++) { if(obstacleGrid[0][j] == 0) dp[0][j] = 1; //后面都到不了 else break; } for (int i = 1; i < dp.length; i++) { for (int j = 1; j < dp[i].length; j++) { //如果是障碍物 if(obstacleGrid[i][j] == 1) dp[i][j] = 0; //正常情况 else { dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } } return dp[dp.length-1][dp[0].length-1]; }
6.整数拆分(力扣343)
数i的因子的乘积的最大值转化为dp[i] = max(dp[i],max((i-j)*j,dp[i-j]*j)),其中j为小于i的因子。(i-j)*j意味着将i分为两个整数的乘积,dp[i-j]*j意味着将i分为两个以上整数的乘积。dp[i]只保存所有情况的最大值。
public int integerBreak(int n) { int[] dp = new int[n+1]; dp[2] = 1; for (int i = 3; i <= n; i++) { for (int j = 1; j <= i-1; j++) { int max = Math.max((i-j)*j,dp[i-j]*j); dp[i] = Math.max(dp[i],max); } } return dp[n]; }
*7.不同的二叉搜索树(力扣96)
//首先对于本题给的整数n,从1到n每个节点都有机会当作根结点,当i作为根结点时, //其左边有i-1个节点,右边有n-i个节点,不同形的二叉搜索树有dp[i-1]*dp[n-i]种 public int numTrees(int n) { int[] dp = new int[n+1]; dp[0] = 1; dp[1] = 1; for (int i = 2; i <= n; i++) { for (int j = 1; j <= i; j++) { //累加,一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j dp[i] += dp[j-1] * dp[i-j]; } } return dp[n]; }
8.0-1背包问题举例
public static void bag() { int[] weight = new int[]{1,3,4}; int[] value = new int[]{15,20,30}; int bagweight = 4; int[][] dp = new int[weight.length][bagweight+1]; //初始化第一个物品的情况 for (int j = weight[0]; j <= bagweight; j++) { dp[0][j] = value[0]; } //从第二个物品开始 for (int i = 1; i < dp.length; i++) { //从最低的背包重量开始 for (int j = 0; j < dp[i].length; j++) { if(j<weight[i]) dp[i][j] = dp[i-1][j]; else dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]); } } for (int i = 0; i < dp.length; i++) { for (int j = 0; j < dp[0].length; j++) { System.out.print(dp[i][j]+" "); } System.out.println(); } }
9.分割等和子集(力扣416)
本题要将数组分为和相等的两个子集,也就是说每个子集的和应该为sum/2,相当于把数组中元素放入背包,背包容量为sum/2,物品重量和价值是数组元素本身,如果恰好放下,则证明一定有等和子集。分为二维方法和一维方法做。注意一维方法做时内层循环从后向前遍历,因为一个物品只能放一次。
public boolean canPartition(int[] nums) { int sum = 0; for (int i = 0; i < nums.length; i++) { sum += nums[i]; } if(sum%2 != 0) return false; Arrays.sort(nums); // //二维方法:dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-nums[i]]+nums[i]) // //01背包问题,背包容量为sum/2,物品重量和价值都是nums[i] // int[][] dp = new int[nums.length][sum/2+1]; // for (int j = nums[0]; j < dp[0].length; j++) { // dp[0][j] = nums[0]; // } // //先放物品 // for (int i = 1; i < dp.length; i++) { // for (int j = 0; j < dp[i].length; j++) { // if(j < nums[i]) // dp[i][j] = dp[i-1][j]; // else // dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-nums[i]]+nums[i]); // } // } // return dp[nums.length-1][sum/2] == sum/2; // //一维方法:dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]) int[] dp = new int[sum/2+1]; for (int i = 0; i < nums.length; i++) { for (int j = sum/2; j >=0 ; j--) { if(j >= nums[i]) dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]); } } return dp[sum/2] == sum/2; }
*10.最后一块石头的重量2(力扣1049)
本题与上题类似,尽可能将所有石头的重量分为相等的两组,仍然以sum/2作为背包容量,物品重量和价值是数组元素本身,用总和减去最后的结果两次就知道石头两两相碰后剩下的最小重量。分为二维方法和一维方法做。一维方法做时需要注意内层循环(遍历容量时)要从后向前遍历,因为一个物品只能放一次。
public int lastStoneWeightII(int[] stones) { int sum = 0; for (int i = 0; i < stones.length; i++) { sum += stones[i]; } // //二维方法 dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]) // int[][] dp = new int[stones.length][sum/2+1]; // for (int j = stones[0]; j < dp[0].length; j++) { // dp[0][j] = stones[0]; // } // //先放物品 // for (int i = 1; i < dp.length; i++) { // for (int j = 0; j < dp[i].length; j++) { // if(j < stones[i]) // dp[i][j] = dp[i-1][j]; // else // dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]); // } // } // //最后结果是总和减去两两能够抵消的 // return sum-dp[dp.length-1][dp[0].length-1]-dp[dp.length-1][dp[0].length-1]; //一维方法 dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i]) int[] dp = new int[sum/2+1]; for (int i = 0; i < stones.length; i++) { for (int j = sum/2; j >= 0 ; j--) { if(j>=stones[i]) dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i]); } } return sum-dp[sum/2]-dp[sum/2]; }