代码随想录——动态规划(一)

题目来自《代码随想录》

509. 斐波那契数

https://leetcode-cn.com/problems/fibonacci-number/

	/*
	 * 1. 平什么秒过版,递归
	 */
	public int fib(int n) {
		if( n <= 1 ) return n;	
		return fib(n-1) + fib(n-2);
	}

	/*
	 * 2. 动规版,时间复杂度:$O(n)$,快很多
	 */
	public int fib2(int n) {
		if( n <= 1 ) return n;
		int[] dp = new int[2];
		dp[0] = 0;
		dp[1] = 1;
		for( int i=2; i<=n; i++) {
			int sum = dp[0] + dp[1];
			dp[0] = dp[1];
			dp[1] = sum;
		}			
		return dp[1];
	}

70. 爬楼梯

https://leetcode-cn.com/problems/climbing-stairs/

	/*
	 * dp[i]就是 dp[i - 1]与dp[i - 2]之和
	 * 1 <= n <= 45
	 */
	public int climbStairs(int n) {
		if( n<=2 ) return n;
		int[] dp = new int[2];
		dp[0] = 1; //第一层台阶有一种上法
		dp[1] = 2; //第二层台阶有两种上法
		for(int i=3; i<=n; i++) { //从第三层开始
			int sum = dp[0] + dp[1];
			dp[0] = dp[1];
			dp[1] = sum;
		}
		return dp[1];
	}

746. 使用最小花费爬楼梯

https://leetcode-cn.com/problems/min-cost-climbing-stairs/

	/*
	 * 1. 平什么一遍版本
	 * 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
	 */
	public int minCostClimbingStairs(int[] cost) {
		int[] dp = new int[2];
		dp[0] = cost[0]; //第一层台阶的消耗
		dp[1] = cost[1]; //第二层台阶的消耗
		for(int i=2; i<=cost.length-1; i++) {
			int step = Math.min(dp[0], dp[1]) + cost[i];
			dp[0] = dp[1];
			dp[1] = step;
		}
		return Math.min(dp[0], dp[1]);
	}

62. 不同路径

https://leetcode-cn.com/problems/unique-paths/

	/*
	 * 1. 平什么一遍速杀版
	 * 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
	 * 内存消耗:38.4 MB, 在所有 Java 提交中击败了14.72%的用户
	 */
	public int uniquePaths(int m, int n) {
		if( m==1 || n==1) return 1; //有一个是1的话,答案就是1
		
		//存两个数,左和上
		//不能只存两个数!
		
		int[][] dp = new int[m][n];
		for(int i=0; i<m; i++){
			for(int j=0; j<n; j++){
				if( i==0 || j==0) {
					dp[i][j] = 1;
				}else{
					dp[i][j] = dp[i][j-1] + dp[i-1][j];
				}
			}
		}		
		return dp[m-1][n-1];		
	}

63. 不同路径 II

https://leetcode-cn.com/problems/unique-paths-ii/

	public int uniquePathsWithObstacles(int[][] obstacleGrid) {
		if(obstacleGrid[0][0] == 1) return 0;
		int m = obstacleGrid.length;
		int n = obstacleGrid[0].length;
		int[][] dp = new int[m][n];
		
		//边缘存在石头要仔细判断
		for(int i=0; i<m; i++){
			for(int j=0; j<n; j++){
				if( i==0 && j==0) {//起始点
					dp[i][j]=1;continue;
				}
				if(obstacleGrid[i][j] == 1) {//碰到石头就是0
					dp[i][j]=0;continue;
				}
				if( i==0 ) { //第一排的点
					dp[i][j] = dp[i][j-1];continue;
				}
				if( j==0 ) { // 第一列的点
					dp[i][j] = dp[i-1][j];continue;
				}

				dp[i][j] = dp[i][j-1] + dp[i-1][j];//大部分情况
			}
		}
		show(dp);
		return dp[m-1][n-1];
	}
	
	public void show(int[][] num) {
		int m = num.length;
		int n = num[0].length;
		for(int i=0; i<m; i++){
			for(int j=0; j<n; j++){
				System.out.print(num[i][j] + " ");				
			}
			System.out.print("\n");
		}	
	}

343. 整数拆分

https://leetcode-cn.com/problems/integer-break/

	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-j; j++) {//最大就是i-j,要有等于号!!!在这错了!!!
				/*
				 * 1. 迭代的前一个结果dp[i]
				 * 2. 拆成两个数:j*i-j
				 * 3. 拆成多个数:j*dp[i-j]
				 * 上面三个取最大值
				 */
				dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
			}
		}	
		return dp[n];
	}

96. 不同的二叉搜索树

https://leetcode-cn.com/problems/unique-binary-search-trees/

	public int numTrees(int n) {
		if( n<=1 ) return n;
		int[] dp = new int[n+1];
		dp[0] = 1;
		dp[1] = 1;
		
		for(int m=2; m<=n; m++){//总共有n种情况
			System.out.println("----" + m + "----");
			int sum = 0;
			for(int i=0; i<=m-1; i++) {//i是左子树的结点数
				int j=m-i-1;//j是右子树的结点数
				System.out.println("左树" + i + "个点,右树" + j + "个点时,可能结果有" + (dp[i] * dp[j]));
				sum += (dp[i] * dp[j]);
			}
			dp[m] = sum;
		}	
		show(dp);
		return dp[n];
	}

	public void show(int[] num){
		System.out.print("dp数组为:");
		for(int i=0; i<num.length; i++) {
			System.out.print(num[i] + " ");
		}
		System.out.print("\n");
	}

背包问题基础

package part3;

import org.junit.Test;

public class 背包问题基础 {

	@Test
	public void test(){
		int[] weight = new int[]{1, 3, 4};
		int[] value = new int[]{15, 20, 30};
		int bagsize = 4;
		bagproblem(weight, value, bagsize);
	}
	
	/**
	 * dp[i][j]从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少!!
	 * @param weight:每件物品的重量
	 * @param value:每件物品的价值
	 * @param bagsize:背包最大容量
	 */
	public void bagproblem(int[] weight, int[] value, int bagsize) {
		int num = weight.length; //物品的数量
		int[][] bp = new int[num][bagsize+1];
		
		for(int i=0; i<num; i++){ // 遍历物品			
			for(int j=0; j<bagsize+1; j++){ // 遍历背包容量
				if(j == 0){ //初始化bp的第一列都为0,容积为0啥也装不了
					bp[i][j] = 0; continue;
				}
				if(i == 0){ //初始化bp第一行是第一个物品的价值
					bp[i][j] = value[0]; continue;
				}
				System.out.println("当前i=" + i + ",当前j=" + j);
				/*
				 * 1. 如果当前物品放不进去,bp[i-1][j],就是上一行同位置的值
				 * 2. 如果当前物品放的进去,max(bp[i-1][j], (bp[i-1][j-weight[i]]+value[i]))
				 */
				if(weight[i] <= j){//1.放得进去
					System.out.println("当前weight[i]=" + weight[i] + "放得进去当前j=" + j);
					bp[i][j] = Math.max(bp[i-1][j], bp[i-1][j-weight[i]]+value[i]);
				}else{//2.放不进去
					System.out.println("当前weight[i]=" + weight[i] + "放不进去当前j=" + j);
					bp[i][j] = bp[i-1][j];
				}
				show(bp);
			}
			
		}	
		show(bp);
	}
	
	public void show(int[][] num){
		System.out.println("*************");
		for(int i=0; i<num.length; i++){
			for(int j=0; j<num[0].length; j++){
				System.out.print(num[i][j] + " ");
			}
			System.out.print("\n");
		}
		System.out.println("*************");
	}
}

背包问题基础——转为一维数组

注意容量的遍历顺序:

  1. 倒叙,防止同一物品多次插入
  2. 遍历到 j>=weight[i] 即可停止
package part3;

import org.junit.Test;

public class 背包问题基础优化一维数组 {
	
	@Test
	public void test(){
		int[] weight = new int[]{1, 3, 4};
		int[] value = new int[]{15, 20, 30};
		int bagsize = 4;
		bagproblem(weight, value, bagsize);
	}
	
	/**
	 * dp[i][j]从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少!!
	 * @param weight:每件物品的重量
	 * @param value:每件物品的价值
	 * @param bagsize:背包最大容量
	 */
	public void bagproblem(int[] weight, int[] value, int bagsize) {
		int num = weight.length; //物品的数量
		int[] bp = new int[bagsize+1]; //一维数组
		
		for(int i=0; i<num; i++){ //遍历物品
			for(int j=bagsize; j>=weight[i]; j--){ 
				/*
				 * 遍历背包容量两个需要注意的点!!!!
				 * 1. 倒序遍历是为了保证物品i只被放入一次!
				 * 2. 只遍历到weight[i]就可以了
				 */
				bp[j] = Math.max(bp[j], bp[j-weight[i]]+value[i]);
				System.out.println("i=" + i + ", j=" + j);
				show(bp);
			}
		}	
	}
	
	public void show(int[] num){
		for(int i:num) System.out.print(i + " ");
		System.out.println("\n");
	}
}

416. 分割等和子集

https://leetcode-cn.com/problems/partition-equal-subset-sum/

package part3;

import org.junit.Test;

public class No416_分割等和子集 {
	
	
	@Test
	public void test(){
		int[] nums = new int[]{1, 5, 11, 5};
		System.out.print(canPartition(nums));
	}
	/*
	 * 背包的体积为sum / 2
	 * 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
	 * 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
	 * 背包中每一个元素是不可重复放入。
	 */
	public boolean canPartition(int[] nums) {
		int sum = 0;
		for(int i:nums) sum+=i;
		if(sum%2 == 1) return false;//如果总和是奇数,肯定分不了,直接false
		int bagsize = sum/2; //背包的体积为sum / 2
	
		
		int[][] bp = new int[nums.length][bagsize+1];
		for(int i=0; i<nums.length; i++) bp[i][0] = 0;//初始化第一列
		for(int i=0; i<bagsize+1; i++){// 初始化第一行
			if(i >= nums[0]){
				bp[0][i] = nums[0];
			}else{
				bp[0][i] = 0;
			}
		}
		show(bp);
		
		for(int i=1; i<nums.length; i++){
			for(int j=1; j<bagsize+1; j++){
				System.out.println("i:" + i + ", j:" + j);
				if(nums[i] > j){//如果背包装不下
					System.out.println("num[" + i + "]的值为:" + nums[i] 
										+ ",当前容量为" + j + "的包装不下");
					bp[i][j] = bp[i-1][j];
				}else{//如果背包装得下
					System.out.println("num[" + i + "]的值为:" + nums[i] 
							+ ",当前容量为" + j + "的包装得下");
					bp[i][j] = Math.max(bp[i-1][j], bp[i-1][j-nums[i]]+nums[i]);
				}
				show(bp);
				if(bp[i][j] == bagsize) return true;
			}
		}
		return false;
	}
	
	public void show(int[][] num){
		System.out.println("*************");
		for(int i=0; i<num.length; i++){
			for(int j=0; j<num[0].length; j++){
				System.out.print(num[i][j] + " ");
			}
			System.out.print("\n");
		}
		System.out.println("*************");
	}

}

//一维数组版本
class Solution {
    public boolean canPartition(int[] nums) {
        if(nums == null || nums.length == 0) return false;
        int n = nums.length;
        int sum = 0;
        for(int num : nums){
            sum += num;
        }
        //总和为奇数,不能平分
        if(sum % 2 != 0) return false;
        int target = sum / 2;
        int[] dp = new int[target + 1];
        for(int i = 0; i < n; i++){
            for(int j = target; j >= nums[i]; j--){
                //物品 i 的重量是 nums[i],其价值也是 nums[i]
                dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
            }
        }
        return dp[target] == target;
    }
}

698. 划分为k个相等的子集

https://leetcode-cn.com/problems/partition-to-k-equal-sum-subsets/
这题没法通过上面416的方法改一下得到。

473. 火柴拼正方形

和上面一样,看着相似,但是应该用回溯。

1049. 最后一块石头的重量 II

public int lastStoneWeightII(int[] stones) {
		int sum = 0;
		for(int i:stones) sum+=i;
		int bagsize = sum/2; //背包的体积为sum / 2,向下取整
		
		int[][] bp = new int[stones.length][bagsize+1];
		for(int i=0; i<stones.length; i++) bp[i][0] = 0;//初始化第一列
		for(int i=0; i<bagsize+1; i++){// 初始化第一行
			if(i >= stones[0]){
				bp[0][i] = stones[0];
			}else{
				bp[0][i] = 0;
			}
		}
		
		for(int i=1; i<stones.length; i++){
			for(int j=1; j<bagsize+1; j++){
				//System.out.println("i:" + i + ", j:" + j);
				if(stones[i] > j){//如果背包装不下
					//System.out.println("num[" + i + "]的值为:" + nums[i] + ",当前容量为" + j + "的包装不下");
					bp[i][j] = bp[i-1][j];
				}else{//如果背包装得下
					//System.out.println("num[" + i + "]的值为:" + nums[i] + ",当前容量为" + j + "的包装得下");
					bp[i][j] = Math.max(bp[i-1][j], bp[i-1][j-stones[i]]+stones[i]);
				}
				//show(bp);
			}
		}
		return (sum - bp[stones.length-1][bagsize]) - bp[stones.length-1][bagsize];
	}

494. 目标和

  1. dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
  2. 注意第一个数是0的情况。这时左上角应该为2。
  3. 注意Math.abs(target) > sum
/*
	 * 目标:装满(sum+target)/2的背包有几种方法,组合问题
	 * dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
	 */
	public int findTargetSumWays(int[] nums, int target) {
		int sum = 0;
		for(int i: nums) sum+=i; //nums中所有数的总和
		if( (sum+target)%2 != 0 || Math.abs(target) > sum) return 0; //sum+target为奇数 或 target更大 直接false
		int bagsize = (sum+target)/2; //背包目标和
		
		int[][] dp = new int[nums.length][bagsize+1];
		//for(int i=0; i<nums.length; i++) dp[i][0] = 1;//第一列初始化为1
		for(int i=0; i<bagsize+1; i++) {
			if(i == nums[0]) dp[0][i] = 1; //第一行刚好能装进去的初始化1
		}
		dp[0][0] = 1;
		if(nums[0] == 0) dp[0][0]+=1;
		show(dp);
		
		for(int i=1; i<nums.length; i++) {
			for(int j=0; j<bagsize+1; j++) {
				/*
				 * 1. (如果放的进去)算上当前数刚好装满背包的方法个数:dp[i-1][j-num[i]]
				 * 2. 不算上当前数刚好装满背包的方法个数:dp[i-1][j]
				 * 计算二者之和
				 */
				dp[i][j] = dp[i-1][j];
				if(j>=nums[i]) dp[i][j] += dp[i-1][j-nums[i]];
				show(dp);
			}
		}
		return dp[nums.length-1][bagsize];
	}

474. 一和零

https://leetcode-cn.com/problems/ones-and-zeroes/
本质还是01背包问题,但是物品的价值有两个维度

	/*
	 * 
	 * 经典的背包问题可以使用二维动态规划求解,两个维度分别是物品和容量。
	 * 这道题有两种容量,因此需要使用三维动态规划求解,三个维度分别是字符串、0 的容量和 1 的容量。
	 */
	public int findMaxForm(String[] strs, int m, int n) {
		int length = strs.length;
        int[][][] dp = new int[length + 1][m + 1][n + 1];
        for (int i = 1; i <= length; i++) {
        	int[] zerosOnes = getZerosOnes(strs[i - 1]);//遍历每个字符
            int zeros = zerosOnes[0], ones = zerosOnes[1];//得到字符的01个数
            for (int j = 0; j <= m; j++) {
                for (int k = 0; k <= n; k++) {//分别从01个数两个角度判断
                    dp[i][j][k] = dp[i - 1][j][k];//当前满足01数量的子集数量就是上一层相同位置的子集数量
                    if (j >= zeros && k >= ones) {//如果当前要求的01数量比当前单词的01数量多,那就算上当前单词的往前面找
                        dp[i][j][k] = Math.max(dp[i][j][k], dp[i - 1][j - zeros][k - ones] + 1);
                    }
                }
            }
        }	
		return dp[length][m][n];
	}
	
	public int[] getZerosOnes(String str) {//获取字符串中0和1的数量
        int[] zerosOnes = new int[2];
        int length = str.length();
        for (int i = 0; i < length; i++) {
            zerosOnes[str.charAt(i) - '0']++;
        }
        return zerosOnes;
    }

完全背包问题

  1. 二维数组
  2. 一维数组
package part4;

import org.junit.Test;

public class 完全背包基础 {
	/*
	 * 完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
	 */
	@Test
	public void test(){
		int[] weight = new int[]{1, 3, 4};
		int[] value = new int[]{15, 20, 30};
		int bagsize = 4;
		bagproblem(weight, value, bagsize);
	}
	
	/**
	 * dp[i][j]从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少!!
	 * @param weight:每件物品的重量
	 * @param value:每件物品的价值
	 * @param bagsize:背包最大容量
	 */
	public void bagproblem(int[] weight, int[] value, int bagsize) {
		int num = weight.length; //物品的数量
		int[][] dp = new int[num][bagsize+1];
		
		for(int i=0; i<bagsize; i++) dp[0][i] = 0;//背包容积为0的时候什么都装不下为0
		show(dp);
		
		for(int i=0; i<num; i++) {
			for(int j=1; j<bagsize+1; j++) { 
				int in = 0;   //当前数放进去的结果
				int noin = 0; //当前数不放进去的结果
				
				//1. 不放进去,上一行同位置的数
				if(i != 0) in = dp[i-1][j];
				//2. 如果能放进去,同一行减去这个数的结果
				if(j >= weight[i]) noin = dp[i][j-weight[i]] + value[i];			
				//3. 最终这个位置的dp是上面两个的max
				dp[i][j] = Math.max(in, noin);
			}
		}		
		show(dp);
	}
	
	public void show(int[][] num){
		System.out.println("*************");
		for(int i=0; i<num.length; i++){
			for(int j=0; j<num[0].length; j++){
				System.out.print(num[i][j] + " ");
			}
			System.out.print("\n");
		}
		System.out.println("*************");
	}
}

package part4;

import org.junit.Test;

public class 完全背包一维数组 {
	
	@Test
	public void test(){
		int[] weight = new int[]{1, 3, 4};
		int[] value = new int[]{15, 20, 30};
		int bagsize = 4;
		bagproblem(weight, value, bagsize);
	}
	
	/**
	 * dp[i][j]从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少!!
	 * @param weight:每件物品的重量
	 * @param value:每件物品的价值
	 * @param bagsize:背包最大容量
	 */
	public void bagproblem(int[] weight, int[] value, int bagsize) {
		int[] dp = new int[bagsize+1]; 
		for(int i=0; i<weight.length; i++){
			System.out.println("当前i=" + i);
			for(int j=weight[i]; j<bagsize+1; j++){
				//1. 不放进去dp[j]
				//2. 放进去dp[j-weight[i]]+value[i]
				dp[j] = Math.max(dp[j], dp[j-weight[i]]+value[i]);
				show(dp);
			}
		}
	}
	
	public void show(int[] num){
		for(int i:num) System.out.print(i + " ");
		System.out.println("\n");
	}

}

518. 零钱兑换 II

https://leetcode-cn.com/problems/coin-change-2/

	//dp[j]:凑成总金额j的货币组合数为dp[j]
	public int change(int amount, int[] coins) {
		int[] dp = new int[amount+1]; 
		dp[0] = 1;
		for(int i=0; i<coins.length; i++){
			System.out.println("当前i=" + i);
			for(int j=coins[i]; j<amount+1; j++){
				dp[j] += dp[j - coins[i]];
				show(dp);
			}
		}
		return dp[amount];
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平什么阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值