题目来自《代码随想录》
文章目录
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("*************");
}
}
背包问题基础——转为一维数组
注意容量的遍历顺序:
- 倒叙,防止同一物品多次插入
- 遍历到 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. 目标和
- dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
- 注意第一个数是0的情况。这时左上角应该为2。
- 注意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;
}
完全背包问题
- 二维数组
- 一维数组
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];
}