打卡代码随想录Day15

今天开始进入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];
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值