动态规划(持续更新。。。

文章思路来源公众号:labuladong
借助labuladong整理自己DP算法

动态规划一般是求最优问题
  1. 先确定「状态」,也就是原问题和子问题中变化的变量
  2. 状态转移方程,你把 f(n) 想做一个状态 n,这个状态 n 是由状态 n - 1 和状态 n - 2 相加转移而来,这就叫状态转移
  3. 然后确定「选择」并择优,也就是对于每个状态,可以做出什么选择改变当前状态

1、0-1 背包问题

2、#416. 分割等和子集

0-1背包问题的变体
在这里插入图片描述

class Solution {
    /**
     *  416. 分割等和子集 :给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
     *      每个数组都是sum/2
     *      类似于背包问题:给一个可装载重量为sum/2的背包和N个物品,每个物品的重量为nums[i]。现在让你装物品,是否存在一种装法,能够恰好将背包装满?
     *         状态:dp[i][j] i:当前重量,j:当前容量
     *         选择:如果当前i选择不装 dp[i][j] = dp[i-1][j]
     *              装 ; dp[i][j] = dp[i-1][j-nums[i]]
     * @param nums
     * @return
     */
    public boolean canPartition(int[] nums) {
        if (nums.length<=0) return false;
        int sum=0;
        for (int i : nums) sum+=i;//对数组元素求求和
        if (sum%2==1) return false;//如果和是奇数,不可以
        sum/=2;
        int n = nums.length;//共有n个数1.2.3...n
        boolean[][] dp = new boolean[n+1][sum+1];
//        dp[0][...] = false;dp[..][0]=true;
        for (int i = 0;i<=n;i++){
            dp[i][0] = true;
        }
        //dp[0][...] = false不需要初始化
        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-1][j-nums[i-1]];
                }
            }
        }
        return dp[n][sum];
    }
}

3、#518.零钱兑换 II

完全背包问题
在这里插入图片描述
若只使用coins中的前i个硬币的面值,若想凑出金额j,有dp[i][j]种凑法。

如果你不把这第i个物品装入背包,也就是说你不使用coins[i]这个面值的硬币,那么凑出面额j的方法数dp[i][j]应该等于dp[i-1][j],继承之前的结果。

如果你把这第i个物品装入了背包,也就是说你使用coins[i]这个面值的硬币,那么dp[i][j]应该等于dp[i][j-coins[i-1]]。

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[][] dp = new int[n+1][amount+1];
        //dp[0][..]=0;dp[..][0]=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];
    }
}

4、详解一道腾讯面试题:编辑距离

5、#887. 鸡蛋掉落

对我来说,难,先放在这里,还不会做

打家劫舍系列问题

6、198. 打家劫舍

在这里插入图片描述

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if(n==0) return 0;
        int[] dp = new int[n+2];
        for (int i=n-1;i>=0;i--){//从后开始往前抢
            /* 状态只和当前第 i 和房子有关
            每次选择到第i个房子,nums[i]代表第i个房子的现金
            假设第i个房子抢劫,那么 i+1 个房子不抢 dp[i]=dp[i+2]+nums[i-1];
            第i个房子不抢, dp[i]=dp[i+1];
             */
            dp[i] = Math.max(dp[i+1],dp[i+2]+nums[i]);
        }
        return dp[0];
    }
}

7、213. 打家劫舍 II

在这里插入图片描述

class Solution {
    public int rob(int[] nums) {
        //分为两种情况,要么第一家不偷,要么最后一家不偷
        int n = nums.length;
        if(n==0) return 0;
        if(n==1) return nums[0];
        /*
            最后一家不偷:从 1 到 n-1
            dp[i] = Math.max(dp[i-2]+nums[i-1],dp[i-1]);
         */
        /*
            第一家不偷:从 2 到 n
         */
        return Math.max(robHelp(nums,0,n-2),robHelp(nums,1,n-1));

    }
    public int robHelp(int[] nums,int start,int end) {
        int[] dp = new int[nums.length+2];
        for (int i=end;i>=start;i--){
            dp[i] = Math.max(dp[i+1],dp[i+2]+nums[i]);
        }
        return dp[start];
    }
}

8、337. 打家劫舍 III

在这里插入图片描述
在这里插入图片描述

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    Map<TreeNode,Integer> map = new HashMap<>();
    public int rob(TreeNode root) {
            //回溯:如果根访问了,就不能访问他的儿子,只能访问孙子;
            //如果根没有访问,可以访问他的儿子
            if (root==null) return 0;
            if (map.containsKey(root)){
                return map.get(root);
            }
            int doIt = root.val + (root.left==null?0:rob(root.left.left)+rob(root.left.right))
                        +(root.right==null?0:rob(root.right.left)+rob(root.right.right));
            int notDo = rob(root.left)+rob(root.right);

            int res = Math.max(doIt,notDo);
            map.put(root,res);
            return res;//这样做相同的的节点做了重复的运算,可以采用备忘录消除重叠子问题 map<>
        }
}

动态规划之 KMP 算法详解

原文:https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247484731&idx=2&sn=d9d6b24c7f94d5e43e08666e82251984&chksm=9bd7fb33aca0722548580dd27eb49880dc126ef87aeefedc33aa0f754f54691af6b09b41f45f&scene=21#wechat_redirect
先在开头约定,本文用pat表示模式串,长度为M,txt表示文本串,长度为N。KMP 算法是在txt中查找子串pat,如果存在,返回这个子串的起始索引,否则返回 -1
在这里插入图片描述
在这里插入图片描述
KMP 算法永不回退txt的指针i,不走回头路(不会重复扫描txt),而是借助dp数组中储存的信息把pat移到正确的位置继续匹配,时间复杂度只需 O(N),用空间换时间
在这里插入图片描述

KMP kmp = new KMP("aaab");
int pos1 = kmp.search("aaacaaab"); //4
int pos2 = kmp.search("aaaaaaab"); //4
public class KMP {
    private int[][] dp;
    private String pat;

    public KMP(String pat) {
        this.pat = pat;
        int M = pat.length();
        // dp[状态][字符] = 下个状态
        dp = new int[M][256];
        // base case
        dp[0][pat.charAt(0)] = 1;
        // 影子状态 X 初始为 0
        int X = 0;
        // 构建状态转移图(稍改的更紧凑了)
        for (int j = 1; j < M; j++) {
            for (int c = 0; c < 256; c++)
                dp[j][c] = dp[X][c];
            dp[j][pat.charAt(j)] = j + 1;
            // 更新影子状态
            X = dp[X][pat.charAt(j)];
        }
    }

    public int search(String txt) {
        int M = pat.length();
        int N = txt.length();
        // pat 的初始态为 0
        int j = 0;
        for (int i = 0; i < N; i++) {
            // 计算 pat 的下一个状态
            j = dp[j][txt.charAt(i)];
            // 到达终止态,返回结果
            if (j == M) return i - M + 1;
        }
        // 没到达终止态,匹配失败
        return -1;
    }
}

股票买卖问题

在这里插入图片描述

121. 买卖股票的最佳时机

在这里插入图片描述

/** 只能买一支股票
     *  2个状态:第i天,,是否持有股票0,1,
     * 选择:
     * @param prices
     * @return
     */
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int k=1;
        int[][] dp = new int[n+1][2];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        for (int i=1;i<=n;i++){
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i-1]);//i-1天没有股票,i-1天有股票,第i天卖掉了
            dp[i][1] = Math.max(dp[i-1][1],-prices[i-1]);//第i天手上有股票,要么是i-1天的股票,要么是第i天买的股票
        }
        return dp[n][0];
    }

122. 买卖股票的最佳时机 II

在这里插入图片描述

int n = prices.length;
        int k=1;
        int[][] dp = new int[n+1][2];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        for (int i=1;i<=n;i++){
            //i-1天没有股票,i-1天有股票,第i天卖掉了
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i-1]);
            //第i天手上有股票,要么是i-1天的股票,要么是i-1天没有股票,i天买的股票
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i-1]);
        }
        return dp[n][0];
根据题目的意思,当天卖出以后,当天还可以买入,所以其实可以第三天卖出,第三天买入,第四天又卖出((5-1+6-5=== 6 - 1)。所以算法可以直接简化为只要今天比昨天大,就卖出。
public int maxProfit(int[] prices) {
        int ans = 0;
        for (int i=1;i<prices.length;i++){
            if (prices[i]>prices[i-1]){
                ans+=(prices[i]-prices[i-1]);
            }
        }
        return ans;
    }

123. 买卖股票的最佳时机 III

在这里插入图片描述

class Solution {
    /**
     *  你最多可以完成 两笔 交易。
     * 3个状态:第i天,第k次交易,是否持有股票0,1,
     * @param prices
     * @return
     */
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int K = 2;
        int[][][] dp = new int[n+1][K+1][2];
        for (int k=0;k<=K;k++){
            dp[0][k][1] = Integer.MIN_VALUE;
        }
        for (int i=1;i<=n;i++){
            for (int k=1;k<=K;k++){
                //第i天第k次交易手上没有股票,,,记买入股票为一次交易
                dp[i][k][0] = Math.max(dp[i-1][k][0],dp[i-1][k][1]+prices[i-1]);
                dp[i][k][1] = Math.max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i-1]);
            }
        }
        return dp[n][K][0];
    }
}

309. 最佳买卖股票时机含冷冻期

在这里插入图片描述

/**
     *  你可以尽可能地完成更多的交易(多次买卖一支股票)
     *  卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
     * @param prices
     * @return
     */
    public int maxProfit4(int[] prices) {
        int n = prices.length;
        int k=1;
        int[][] dp = new int[n+2][2];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        for (int i=1;i<=n;i++){
            //i-1天没有股票,i-1天有股票,第i天卖掉了
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i-1]);
            //第i天手上有股票,要么是i-1天的股票,要么是i-2天没有股票,i天买的股票
            //dp[i][1] = Math.max(dp[i-1][1],dp[i-2][0]-prices[i-1]);
            if (i-2<0){
                dp[i][1] = Math.max(dp[i-1][1],0-prices[i-1]);
            }else {
                dp[i][1] = Math.max(dp[i-1][1],dp[i-2][0]-prices[i-1]);
            }

        }
        return dp[n][0];
    }

188. 买卖股票的最佳时机 IV

在这里插入图片描述

class Solution {
     /**
        当k大于等于数组长度一半时, 问题退化为贪心问题此时采用 买卖股票的最佳时机 II
        的贪心方法解决可以大幅提升时间性能, 对于其他的k, 可以采用 买卖股票的最佳时机 III
        的方法来解决, 在III中定义了两次买入和卖出时最大收益的变量, 在这里就是k租这样的
        变量, 即问题IV是对问题III的推广, t[i][0]和t[i][1]分别表示第i比交易买入和卖出时
        各自的最大收益
        **/
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if (k>n/2){
            return greedy(prices);
        }
        int[][][] dp = new int[n+1][k+1][2];
        for (int j=1;j<=k;j++){
            dp[0][j][1] = Integer.MIN_VALUE;
        }
        for (int i=1;i<=n;i++){
            for (int j=1;j<=k;j++){
                //第i天第k次交易手上没有股票,,,记买入股票为一次交易
                dp[i][j][0] = Math.max(dp[i-1][j][0],dp[i-1][j][1]+prices[i-1]);
                dp[i][j][1] = Math.max(dp[i-1][j][1],dp[i-1][j-1][0]-prices[i-1]);
            }
        }
        return dp[n][k][0];
    }

    public int greedy(int[] prices) {
        int ans = 0;
        for (int i=1;i<prices.length;i++){
            if (prices[i]>prices[i-1]){
                ans+=(prices[i]-prices[i-1]);
            }
        }
        return ans;
    }
}

714. 买卖股票的最佳时机含手续费

在这里插入图片描述

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        int k=1;
        int[][] dp = new int[n+1][2];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        for (int i=1;i<=n;i++){
            //i-1天没有股票,i-1天有股票,第i天卖掉了
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i-1]);
            //第i天手上有股票,要么是i-1天的股票,要么是i-1天没有股票,i天买的股票
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i-1]-fee);
        }
        return dp[n][0];
    }
}

子序列问题

300. 最长上升子序列

在这里插入图片描述

class Solution {
    /**
     *  300. 最长上升子序列
     *   输入: [10,9,2,5,3,7,101,18]
     *  输出: 4
     *  解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
     *   dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。
     *   我们只要找到前面那些比nums[i]小的子序列,然后把 nums[i]接到最后,就可以形成一个新的递增子序列,而且这个新的子序列长度加一。
     * @param nums
     * @return
     */
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp,1);
//        先求出每一个dp[i],最后求出最大dp
//        当前nums[i]的dp值为比nums[i]小的num值得dp+1;dp[i] = max(dp[i],dp[j]+1)
        int res=0;
        for (int i=0;i<n;i++){
            for (int j=0;j<i;j++){
                if (nums[j]<nums[i]){
                    dp[i]=Math.max(dp[i],dp[j]+1);
                }
            }
            res = Math.max(res,dp[i]);
        }
        return res;
    }
}

1143.最长公共子序列

在这里插入图片描述
dp[i][j]的含义是:对于s1[1…i]和s2[1…j],它们的 LCS 长度是dp[i][j]。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        //dp[i][j]表示第i个,第j个字符的最长公共子序列长
        int m = text1.length();
        int n = text2.length();
        int [][] dp = new int[m+1][n+1];
        for (int i=1;i<=m;i++) {
            for (int j = 1; j <= n; 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 - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[m][n];
    }
}

516. 最长回文子序列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];
        for(int i=0;i<n;i++) {
            dp[i][i] = 1;
        }
        for (int i=n-1;i>=0;i--){
            for (int j=i+1;j<n;j++){
                if (s.charAt(i)==s.charAt(j)){
                    dp[i][j]=dp[i+1][j-1]+2;
                }else {
                    dp[i][j] = Math.max(dp[i][j-1],dp[i+1][j]);
                }
            }
        }
        return dp[0][n-1];
    }
}

354. 俄罗斯套娃信封问题(最长上升子序列之信封嵌套)添加链接描述

在这里插入图片描述

class Solution {
    /*
    354. 俄罗斯套娃信封问题
    输入: envelopes = [[5,4],[6,4],[6,7],[2,3]]
    输出: 3
    解释: 最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
    先对宽度w进行升序排序,如果遇到w相同的情况,则按照高度h降序排序。之后把所有的h作为一个数组,在这个数组上计算 LIS 的长度就是答案。
     */
    public int maxEnvelopes(int[][] envelopes) {
        if (envelopes.length==0){
            return 0;
        }
        Arrays.sort(envelopes, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0]==o2[0]?o2[1]-o1[1]:o1[0]-o2[0];
            }
        });
        int n = envelopes.length;
        int[] high = new int[n];
        for (int i=0;i<n;i++){
            high[i] = envelopes[i][1];
        }
        return lengthOfLIS(high);
    }
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp,1);
//        先求出每一个dp[i],最后求出最大dp
//        当前nums[i]的dp值为比nums[i]小的num值得dp+1;dp[i] = max(dp[i],dp[j]+1)
        int res=0;
        for (int i=0;i<n;i++){
            for (int j=0;j<i;j++){
                if (nums[j]<nums[i]){
                    dp[i]=Math.max(dp[i],dp[j]+1);
                }
            }
            res = Math.max(res,dp[i]);
        }
        return res;
    }
}

博弈问题

正则表达式

在这里插入图片描述

class Solution {
    public boolean isMatch(String s, String p) {
        //分三种情况:
        /*
       1: 第 i 个字符 和 j 字符相等时,跳过 i和j
       2: j = ‘.' ,跳过 i ,j
       3: j+1 字符为 ’*’ 时, 因为 * 表示匹配 0 个或者 多个,所以可能有两种情况: 匹配 0 次;的情况或者 根据 j 个字符匹配多次的情况
         */
        int m = s.length();
        int n = p.length();
        boolean[][] dp = new boolean[m+2][n+2];
        dp[m][n] = true;
        for (int i= m;i>=0;i--){
            for (int j=n-1;j>=0;j--){
                boolean first = i<m && (p.charAt(j)== s.charAt(i))||(p.charAt(j)=='.');
                if (j+1<p.length()&&p.charAt(j+1)=='*'){
                    dp[i][j] = (dp[i][j+2] || (first && dp[i+1][j]));//当j=n-1时,当前i=m
                }else {
                    dp[i][j] = first&&dp[i+1][j+1];
                }
            }
        }
        return dp[0][0];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值