6.leetcode--动态规划

模版:

穷举 「状态转移方程」「备忘录」

明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义

背包 状态有两个,就是「背包的容量」和「可选择的物品

# 自底向上迭代的动态规划
# 初始化 base case
dp[0][0][...] = base case
# 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)
  1. 最长递增子序列

题目要求:

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]

输出:4

解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

难度:🌟🌟

解答:

public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp,1);
        for (int i =0;i<nums.length;i++){
            for (int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    dp[i]=Math.max(dp[i],dp[j]+1);
                }
            }
        }
        int res = 0;
        for (int i = 0; i < dp.length; i++) {
            res = Math.max(res, dp[i]);
        }
        return res;
    }

2.最长公共子序列

题目要求:

给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

输入:text1 = "abcde", text2 = "ace"

输出:3

解释:最长公共子序列是 "ace" ,它的长度为 3 。

难度:🌟🌟

解答:子序列可以是不连续的;子数组(子字符串)需要是连续的

dp[i] 定义为 nums[0:i] 中想要求的结果;当两个数组或者字符串要用动态规划时,可以把动态规划定义成两维的 dp[i][j] ,其含义是在 A[0:i] 与 B[0:j] 之间匹配得到的想要的结果。

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        //dp[i][j] 表示 text1[0:i-1] 和 text2[0:j-1] 的最长公共子序列
        int n1=text1.length();
        int n2=text2.length();
        int[][] dp = new int[n1+1][n2+1];
        for (int i=1;i<=n1;i++){
            for (int j=1;j<=n2;j++){
                if(text1.charAt(i-1) == text2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1]+1;
                }else {
                    dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]);
                }
            }
        }
        return dp[n1][n2];
    }
}

3.分割等和子集 子集背包问题

题目要求:

给你一个 只包含正整数 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]

输出:true

解释:数组可以分割成 [1, 5, 5] 和 [11] 。

难度:🌟🌟

解答:

  • 转化为0-1背包问题

  • 被包容量 sum/2 boolean[][] dp = new boolean[n + 1][sum + 1];

public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num:nums) {
            sum+=num;
        }
        if (sum % 2 != 0) return false;
        int n = nums.length;
        sum = sum / 2;

        boolean[][] dp = new boolean[n + 1][sum + 1];
        for (int i = 0; i <= n; i++)
            dp[i][0] = true;

        for (int i=1;i<=n;i++){
            for (int j=1;j<=sum;j++){
                if(j-nums[i-1]<0){ //容量不足
                    dp[i][j]=dp[i-1][j];
                }else {
                    dp[i][j]=dp[i-1][j] || dp[i][j-nums[i-1]];
                }
            }
        }
        return dp[n][sum];
    }

4.零钱兑换 完全背包问题

题目要求:

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]

输出:4

解释:有四种方式可以凑成总金额:

5=5

5=2+2+1

5=2+1+1+1

5=1+1+1+1+1

难度:🌟🌟

解答:

  • 若只使用 coins 中的前 i 个(i 从 1 开始计数)硬币的面值,若想凑出金额 j,有 dp[i][j] 种凑法

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        for (int i = 0; i <= n; i++)
            dp[i][0] = 1;

        for (int i= 1;i<=n;i++){
            for (int j=1;j<=amount;j++){
                if(j-coins[i-1]>=0){
                    dp[i][j] = dp[i-1][j]+dp[i][j-coins[i-1]];
                }else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[n][amount];
    }
}

5.目标和 回溯

回溯模版

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

题目要求:

给你一个整数数组 nums 和一个整数 target

向数组中的每个整数前添加 '+''-' ,然后串联起所有整数,可以构造一个 表达式

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1"

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3

输出:5

解释:一共有 5 种方法让最终目标和为 3 。

-1 + 1 + 1 + 1 + 1 = 3

+1 - 1 + 1 + 1 + 1 = 3

+1 + 1 - 1 + 1 + 1 = 3

+1 + 1 + 1 - 1 + 1 = 3

+1 + 1 + 1 + 1 - 1 = 3

难度:🌟🌟

解答:回溯

int result = 0;
    public int findTargetSumWays(int[] nums, int target) {
        if (nums.length == 0) return 0;
        backtrack(nums, 0, target);
        return result;
    }

    public  void backtrack(int[] nums, int i, int remain){
        if (i==nums.length){
            if(remain == 0){
                result++;
            }
            return;
        }

        //选择-号
        remain=remain-nums[i];
        backtrack(nums,i+1,remain);
        remain=remain+nums[i];

        //选择+号
        remain=remain+nums[i];
        backtrack(nums,i+1,remain);
        remain=remain-nums[i];
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值