zoukankan      html  css  js  c++  java
  • leetcode 动态规划算法

    动态规划问题

    概念:若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。动态规划往往用于优化递归问题,例如斐波那契数列,如果运用递归的方式来求解会重复计算很多相同的子问题,利用动态规划的思想可以减少计算量。

    动态规划法仅仅解决每个子问题一次,具有天然剪枝的功能,从而减少计算量,一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。

    通过leetcode相关题目来理解动态规划的套路

    Leetcode 300.最长上升子序列

    题目描述

    给定一个无序的整数数组,找到其中最长上升子序列的长度。

    示例:

    输入: [10,9,2,5,3,7,101,18]
    输出: 4
    解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
    说明:

    可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
    你算法的时间复杂度应该为 O(n2) 。
    进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

    解题思路

    1)dp[i] :存放数组前i个元素的最大子序列长度
    2)初始化状态 dp[i] = 1; // 因为每个元素都可能是最大子序里,其长度为 1
    3)对于 j 在 (0,i)区间,if num[i] > num[j],dp[i] = Math.max(dp[i], dp[j] + 1);否则 什么也不做

    代码

    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];   // 1)存放数组前i个元素的最大子序列长度
        Arrays.fill(dp,1);   // 2)确定动态规划状态
    
        // 3)找出dp[i]中最大的数即为 最大子序列长度
        int maxL = 0;
        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);
                }
            }
            maxL = Math.max(maxL, dp[i]);
        }
    
        return maxL;
    }
    

    复杂度分析:

    • 时间复杂度:O(n^2)
    • 空间复杂度:O(n)

    674 最长连续递增序列

    题目描述

    给定一个未经排序的整数数组,找到最长且连续的的递增序列。

    示例 1:
    输入: [1,3,5,4,7]
    输出: 3
    解释: 最长连续递增序列是 [1,3,5], 长度为3。
    尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。 
    
    示例 2:
    输入: [2,2,2,2,2]
    输出: 1
    解释: 最长连续递增序列是 [2], 长度为1。
    

    解题思路

    1)dp[i] --- 存放数组前i个最大且连续的序列长度
    2)初始化状态 dp[i] = 1; // 因为每个元素都可能是最大子序里,其长度为 1
    3)对于i属于(1,nums.length),
    if nums[i] > nums[i-1] 则 dp[i] = dp[i-1] + 1;
    由于返回的是递增的最大长度,所以在申请一个临时变量保存数组中最大的元素(长度)返回即可。

    代码

    public int findLengthOfLCIS(int[] nums) {
        int[] dp = new int[nums.length]; // 1)存放数组前i个最大且连续的序列长度
        Arrays.fill(dp, 1);  // 2) 初始化状态  dp[i] = 1;  // 因为每个元素都可能是最大子序里,其长度为 1
    
        if (nums.length == 1) return 1;
    
        int maxL = 0;
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > nums[i - 1])  // 3)状态转移方程(根据题意,需要连续只需比较相邻两者即可
                dp[i] = dp[i - 1] + 1;
    
            maxL = Math.max(maxL, dp[i]);
        }
        return maxL;
    }
    

    复杂度分析:

    • 时间复杂度:O(n)
    • 空间复杂度:O(n)

    Leetcode5. 最长回文子串

    题目描述

    给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

    示例 1:
    
    输入: "babad"
    输出: "bab"
    注意: "aba" 也是一个有效答案。
    
    示例 2:
    
    输入: "cbbd"
    输出: "bb"
    

    解题思路

    1)定义dp[i][j] --- 表示下标为i的字符串到下标为j的字符串是否为回文串
    2)初始化,默认为false
    3)算法核心思想:(采用暴力算法的优化解决)
    3.1 如果 s[i] == s[j],且s[i+1,j-1]是回文串,则s[i][j]也是回文串
    3.2 如果 s[i] == s[j],且i == j 指向同一元素,肯定是回文串
    3.3 如果s[i] == s[j],且 j - i == 1,说明两者之间隔着单个元素,此时组成的字串也是回文串

    综上可将状态转移条件合并为如下语句:
    if (s[i] == s[j] && (j-i<2 || dp[i+1][j-1]为回文串)) 则 s[i,j]也为回文串

    代码

    public String longestPalindrome(String s) {
        int n = s.length();
        // 1)2)定义dp[i][j] --- 表示下标为i的字符串到下标为j的字符串是否为回文串,默认为false
        boolean[][] dp = new boolean[n][n];  
        int maxLength = 0;
        String maxStr = "";
        // 3)状态转移方程
        for (int j = 0; j < s.length(); ++j) {
            for (int i = 0; i <= j; ++i) {
                if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i+1][j-1])) {
                    dp[i][j] = true;
                    if (maxLength < s.substring(i,j+1).length()) {
                        maxLength = s.substring(i,j+1).length();
                        maxStr = s.substring(i,j+1);
                    }
                }
            }
        }
        return maxStr;
    }
    

    复杂度分析:

    • 时间复杂度:O(n^2)
    • 空间复杂度:O(n^2)

    Leetcode516 最长回文子序列

    题目描述

    给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。

    示例 1:
    输入:
    
    "bbbab"
    输出:
    
    4
    一个可能的最长回文子序列为 "bbbb"。
    
    示例 2:
    输入:
    
    "cbbd"
    输出:
    
    2
    

    解题思路

    注意这个跟最长回文子串不同之处在于 子序列,可能包括不连续的

    1)定义dp[i][j]: 表示从第i个索引为止的元素到第j个元素的 子序列
    2)初始化,如果只有一个字符,显然最长回文子序列长度是 1,也就是 dp[i][j] = 1 (i == j)
    3)状态转移方程。 i 从最后一个字符开始往前遍历,j 从 i + 1 开始往后遍历,这样可以保证每个子问题都已经算好了。

    代码

    public int longestPalindromeSubseq(String s) {
            int n = s.length();
            // 1) 定义dp[i][j]: 表示从第i个索引为止的元素到第j个元素的 子序列
            int[][] dp = new int[n][n];
    
            // 如果只有一个字符,显然最长回文子序列长度是 1,也就是 dp[i][j] = 1 (i == j)
            for (int i = 0; i < n; i++)
                dp[i][i] = 1;
    
            // i 从最后一个字符开始往前遍历,j 从 i + 1 开始往后遍历,这样可以保证每个子问题都已经算好了。
            for (int i = n - 1; i >= 0; --i) {
           //     dp[i][i] = 1;
                for (int j = i + 1; j < n; j++) {
                    if (s.charAt(i) == s.charAt(j))
                        dp[i][j] = dp[i+1][j-1] + 2;  // 如果满足则在原子串上加2
                    else  // 否则 取[i+1,j]与[i,j-1]两子串中的最大一个作为当前串[i,j]的长度
                        dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
                }
            }
            //  整个 s 的最长回文子串长度
            return dp[0][n-1];
        }
    

    复杂度分析:

    • 时间复杂度:O(n^2)
    • 空间复杂度:O(n^2)

    动态规划模板步骤

    1)确定动态规划状态  (必选)
    
    2)写出状态转移方程(画出状态转移表)   (必选)
    
    3)考虑初始化条件  (必选)
    
    4)考虑输出状态  (必选)
    
    5)考虑对时间,空间复杂度的优化(Bonus)  (可选)
    

    更多请见:https://github.com/datawhalechina/team-learning-program/blob/master/LeetCodeClassification/2.动态规划.md

  • 相关阅读:
    程序员修炼之道:从小工到专家--读书摘录
    代码规范--捡拾(SQL语句)
    新博客,新生活
    如何用Eclipse+maven创建servlet 3.0 web 项目
    swift -- 枚举
    swift -- 函数
    控制语句 for while if switch
    swift -- 字符串
    swift -- 集合
    swift -- 字典
  • 原文地址:https://www.cnblogs.com/guohaoblog/p/13548000.html
Copyright © 2011-2022 走看看