0802—动态规划1

0.前言

动态规划的步骤是什么?
1.找到状态、选择。状态,是在变化的东西;选择,是你可以选择的东西。
2.明确dp数组的含义
3.寻找状态转移方程
4.如何选择遍历方向?遍历时的2个注意事项:(1)、所需状态必须是已经计算出来的;(2)、遍历的终点必须是存储返回结果的位置。

1.最长递增子序列

在这里插入图片描述
1.题目分析

(1).明确dp[]数组的含义:dp[i]表示当数组以nums[i]结尾时,最长的递增子序列是多少;
(2).确定base case:dp[]数组初始化为1,因为就算只取nums[i]这1个元素,也至少是1;
(3).状态转移方程:j < i,当nums[j] < nums[i]时,dp[i] = max(dp[i], dp[j] + 1).

2.代码

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null || nums.length==0)
            return 0;
        int len = nums.length;
        int res = 1;
        1.定义动态数组
        int[] dp = new int[len];
        2.确定base case
        Arrays.fill(dp, 1);
        3.状态转移方程
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < i; j++) {
                 当后比前大时
                if (nums[i] > nums[j]){
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
                else if (nums[i] <= nums[j]){
                     此时不更新dp[i]
                }
                动态获取最大值
                res = Math.max(res, dp[i]);
            }
        }
        return res;
    }
}

2. 俄罗斯套娃信封问题

在这里插入图片描述
1.问题分析

  • 信封嵌套要求什么?后1个信封的长、宽都要严格大于前1个信封。
    (1).把信封排序,优先按长度升序排列,长度相同把高度降序排列;
    (2).在高度序列的一维数组中,找出最长递增子序列,它的长度就是目标求的信封的长度;
  • 为什么要那样排序?
    因为降序排列的高度,在取最长递增子序列时,可以保证相同长度的信封只取到1个。长度相同的信封中,最高的那个排在最前边,因为是取高度的最长递增子序列,所以他后边的不会被取到。

2.代码

class Solution {
    public int maxEnvelopes(int[][] envelopes) {
        先按第1个元素升序排列,前者相同则按第2个元素降序排列
        Arrays.sort(envelopes, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                                      对第2个元素降序排列  对第1个元素升序排列
                return o1[0] == o2[0] ? o2[1] - o1[1] : o1[0] - o2[0];
            }
        });
        
        int len = envelopes.length;
        int[] width = new int[len];
        for (int i = 0; i < len; i++) {
            width[i] = envelopes[i][1];
        }
        调用上1个函数,求最长递增子序列
        return lengthOfLIS(width);
    }
}

3.剑指 Offer 42. 连续子数组的最大和

在这里插入图片描述
1.题目分析

  • 1.动态数组dp[],dp[i]表示nums[i]及其之前的最大子数组和是多少;
  • 2.base case,dp[0] = nums[0];
  • 3.状态转移方程,从前向后遍历,dp[i] = Math.max(0,dp[i-1]) + nums[i]。

2.代码

class Solution {
    public int maxSubArray(int[] nums) {
        
        int len = nums.length;
        1.定义dp数组, dp[i]表示nums[i]及其之前的最大子数组和是多少?
        int[] dp = new int[len];
        
        2.base case 先跳过
        dp[0] = nums[0];
        int res = nums[0];
        
        3.状态方程
        for (int i = 1; i < len; i++) {
            如果第i个元素之前的和比0都小,那直接不要了
            dp[i] = Math.max(0,dp[i-1]) + nums[i];
            动态更新res返回值
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

4.剑指 Offer II 095. 最长公共子序列

在这里插入图片描述
1.题目分析

  • 1.dp[][]数组: dp[i][j]表示 text1前i个字符(相当于到nums[i-1]处) 与 text2前j个字符(相当于到nums[j-1]处) 最长公共子序列是多少

  • 2.base case: dp[][0]==0 表示text1 与text2空字符串 没公共部分;
    dp[0][]==0 表示text2 与text1空字符串 没公共部分。

  • 3.正向遍历,状态转移方程: if(text1[i-1] == text2[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
    else if(text1[i-1] != text2[j-1]) dp[i][j] = max(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])。

2.代码

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        if (text1==null || text2==null)
            return 0;
        int olen = text1.length();
        int tlen = text2.length();
        
        1.定义dp数组, 各自+1,是为了最前边第0行、0列的空串的情况
        dp[i][j]表示 text1前i个字符 与 text2前j个字符最长公共子序列是多少
        int[][] dp = new int[olen+1][tlen+1];
        
        2.base case 反正是0,不必处理
        
        3.状态转移方程 从左上角开始正向遍历
        for (int i = 1; i < olen+1; i++) {
            for (int j = 1; j < tlen+1; j++) {
                if (text1.charAt(i-1) == text2.charAt(j-1)) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                }
                else if (text1.charAt(i-1) != text2.charAt(j-1)) {
                    dp[i][j] = Math.max(Math.max(dp[i][j-1],dp[i-1][j]),dp[i-1][j-1]);
                }
            }
        }
        return dp[olen][tlen];
    }
}

5. 编辑距离

在这里插入图片描述
1.题目分析

  • 1.dp[]数组,dp[i][j] 表示word1前i的字符 变为 word2前j个字符 所需最少步数;

  • 2.base case:

    • dp[i][0] = i, word2空串 变为 word1所需的最少步数;
    • dp[0][j] = j,word1空串 变为 word2所需的最少步数;
  • 3.正向遍历,状态转移方程,word1 转变为 word2 时 各个字符有 4 种操作

    • 1.跳过 dp[i-1][j-1] --(步数+0)–> dp[i][j]
    • 2.删除 dp[i-1][j] --(步数+1)–> dp[i][j]
    • 3.增加 dp[i][j-1] --(步数+1)–> dp[i][j]
    • 4.替换 dp[i-1][j-1] --(步数+1)–> dp[i][j]

2.代码

class Solution {
    public int minDistance(String word1, String word2) {
        int oLen = word1.length();
        int tLen = word2.length();

        1.dp[]数组,dp[i][j] 表示word1前i的字符 变为 word2前j个字符 所需最少步数
        int[][] dp = new int[oLen+1][tLen+1];

        2.base case
        	word2空串 变为 word1所需的最少步数
        for (int i = 0; i < oLen+1; i++) {
            dp[i][0] = i;
        }
        	word1空串 变为 word2所需的最少步数
        for (int i = 0; i < tLen+1; i++) {
            dp[0][i] = i;
        }

        3.状态转移方程,有:跳、增、删、替换 4种操作
        for (int i = 1; i < oLen+1; i++) {
            for (int j = 1; j < tLen+1; j++) {
                无需操作,直接跳过
                if (word1.charAt(i-1) == word2.charAt(j-1)) {
                    dp[i][j] = dp[i-1][j-1]; 
                }
                需要操作,步数+1; 所需的状态皆已求出
                else {
                    dp[i][j] = 1 + minThree(
                                 dp[i-1][j],    删除 1个元素
                                 dp[i-1][j-1],  替换 1个元素
                                 dp[i][j-1]);   增加 1个元素
                }
            }
        }
        return dp[oLen][tLen];
    }
    int minThree(int i, int j, int k) {
        return Math.min(Math.min(i,j),k);
    }
}

6. 最长回文子序列

在这里插入图片描述

1.题目分析

  • 1.dp数组,dp[i][j] 表示 s[i] -到-> s[j] 之间的最长回文子序列的长度

  • 2.base case

    • 当i==j 时,则为1
    • 当 i > j 时,则为0
    • 当 i < j 时,则为需要计算的情况
  • 3.状态转移方程,反向遍历

    • 出现回文字符时: if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1] + 2
    • 没有回文字符时 else dp[i][j] = Max( dp[i][j-1], dp[i+1][j] )
  • 4.目标是求dp[0][n-1]

2.代码

class Solution {
    public int longestPalindromeSubseq(String s) {
        if (s == null || s.length() == 0)
            return 0;
        int len = s.length();
        1.建立动态 dp
        int[][] dp = new int[len][len];
        2.base case
        for (int i = 0; i < len; i++) {
            dp[i][i] = 1;
        }
        3.状态转移方程  反向遍历,保证所需状态都是已经计算出来的
        for (int i = len-2; i >= 0; i--) {
            for (int j = i+1; j < len; 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][len-1];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值