动态规划题总结

最长递增子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

思路
序列的初始值都是1,那么如果当前值i比j(0<=j 找出这个序列的方法也很简单,先找到最大值dp[i]和对应的index,然后反向遍历,如果dp[j] = dp[i] - 1 并且 arr[j] < arr[i], 那么这个数就是递增子序列中的一个

//最优解:
public static int LongestSubString(int arr[])
    {
        int len=0;
        if(arr==null||arr.length==0)
            return 0;
        int dp[]=new int[arr.length];
        dp[0]=1;
        //dp[i] 表示到i为止是最长递增子序列的长度
        for(int i=1;iarr[j])
                {
                //求dp[i]时遍历,dp[0...i-1],找出arr[j]arr[j],则可知arr[j]是子序列中
    //arr[i]前面的数.

    public  generateLIS(int arr[],int dp[])
    {
        int k=0;
        int index=0;
        int len=0;
        for(int i=0;ilen)
            {
                len=dp[i];
                index=i;
                //找到递增子序列中的最后一个元素[10,22,33,41,60,80]中的80,
            }
        }
        int subArr[]=new int[len];
        subArr[k++]=arr[index]; 
        for(int j=index-1;j>=0;j--)
        {
            if((dp[index]==dp[j]+1)&&(arr[index]>arr[j]))
            {
                //从后向前,将属于递增子序列的元素加入到subArr中。
                subArr[k++]=arr[j];
                index=j;
            }
        }
        for(int j=subArr.length-1;j>=0;j--)
        {
            System.out.print(subArr[j]+" ");
        }

    }
--------------------- 
作者:HankingHu 
来源:CSDN 
原文:https://blog.csdn.net/u013309870/article/details/62037674 
版权声明:本文为博主原创文章,转载请附上博文链接!

最长公共子序列

题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,
则字符串一称之为字符串二的子串。

动态规划题总结_第1张图片
image.png

思路
假如S1的最后一个元素 与 S2的最后一个元素相等,那么S1和S2的LCS就等于 {S1减去最后一个元素} 与 {S2减去最后一个元素} 的 LCS 再加上 S1和S2相等的最后一个元素。
假如S1的最后一个元素 与 S2的最后一个元素不等(本例子就是属于这种情况),那么S1和S2的LCS就等于 : {S1减去最后一个元素} 与 S2 的LCS, {S2减去最后一个元素} 与 S1 的LCS 中的最大的那个序列。


动态规划题总结_第2张图片
公式
public static String LCS_caculate(String s1,String s2){
        int size1=s1.length();
        int size2=s2.length();
        int chess[][]=new int[s1.length()+1][s2.length()+1];
        for(int i=1;i<=size1;i++){//根据上面提到的公式计算矩阵
            for(int j=1;j<=size2;j++){
                if (s1.charAt(i-1)==s2.charAt(j-1)) {
                    chess[i][j]=chess[i-1][j-1]+1;
                }else {
                    chess[i][j]=max(chess[i][j-1],chess[i-1][j]);
                }
            }
        }
        int i=size1;
        int j=size2;
        StringBuffer sb=new StringBuffer();
        while((i!=0)&&(j!=0)){//利用上面得到的矩阵计算子序列,从最右下角往左上走
            if (s1.charAt(i-1)==s2.charAt(j-1)) {
                sb.append(s1.charAt(i-1));//相同时即为相同的子串
                i--;
                j--;
            }else {
                if (chess[i][j-1]>chess[i-1][j]) {
                    j--;
                }else {
                    i--;
                }
            }
        }
        System.out.println((double)sb.length()/s2.length()+","+(double)sb.length()/s1.length());
        return sb.reverse().toString();//记得反转
    }
--------------------- 
作者:Bryan__ 
来源:CSDN 
原文:https://blog.csdn.net/Bryan__/article/details/51927537 
版权声明:本文为博主原创文章,转载请附上博文链接!

最长公共子串

问题:有两个字符串str和str2,求出两个字符串中最长公共子串长度。
比如:str=acbcbcef,str2=abcbced,则str和str2的最长公共子串为bcbce,最长公共子串长度为5。

思路
跟最长公共子串差不多,只是要求字符串是连续的,所以每次相等时对dp[i-1][j-1]+1,如果不等就直接为0了

/**
     * 获取两个字符串最长公共子串长度
     * @param str   第一个字符串
     * @param str2  第二个字符串
     * @return  如果存在则返回最长公共子串长度,否则返回0
     */
    public static int getLCSLength2(String str, String str2){
        char[] ary = str.toCharArray();
        char[] ary2 = str2.toCharArray();
        
        int[][] temp = new int[ary.length][ary2.length];    //声明一个二维数组,存储最长公共子串长度
        int length = 0; //最长公共子串长度
        
        for(int i = 0; i < ary2.length; i++){   //初始化二维矩阵中的第一行
            temp[0][i] = (ary[0] == ary2[i]) ? 1 : 0;
        }
        
        for(int j = 1; j < ary.length; j++){    //初始化二维矩阵中的第一列
            temp[j][0] = (ary2[0] == ary[j]) ? 1 : 0;
        }
        
        for (int i = 1; i < ary.length; i++) {
            for (int j = 1; j < ary2.length; j++) {
                if(ary[i] == ary2[j]){
                    temp[i][j] = temp[i-1][j-1] + 1;
                    
                    if(temp[i][j] > length){    //当前元素值大于最大公共子串长度
                        length = temp[i][j];
                    }
                }else{
                    temp[i][j] = 0;
                }
            }
        }
        return length;
    }
--------------------- 
作者:banbanaoxiang 
来源:CSDN 
原文:https://blog.csdn.net/u010397369/article/details/38979077 
版权声明:本文为博主原创文章,转载请附上博文链接!

最大子序列和

问题: 给定(可能有负数)整数序列A1, A2, A3..., An, 求这个序列中子序列和的最大值。(为方便起见,如果所有整数均为负数,则最大子序列和为0)。例如:输入整数序列: -2, 11, 8, -4, -1, 16, 5, 0,则输出答案为35,即从A2~A6。

思路: 只要子序列和不小于0就有可能是最大子序列和的一部分,但是如果小于0了就肯定不是了

public static int maxSubsequenceSum(int[] a) {
    int maxSum = 0, thisSum = 0;;
    for(int i=0; i maxSum)
            maxSum = thisSum;
        else if(thisSum < 0)
            thisSum = 0;
    }
    return maxSum;
}

数组中的最长连续序列

题目:找出无序数组中的最长连续序列的长度:例如数组[ 1 , 23 , 2 , 300 , 3 , 9 ,4 , 5 , 90 ],最长连续序列为:1,2,3,4,5,因此返回长度为 5。

分割回文串

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

示例:

输入: "aab"
输出:
[
["aa","b"],
["a","a","b"]
]

思路: 回溯法,每次去从当前点开始扩展出去,如果是回文就在当前的基础上再进行下一次的查找

class Solution {
    List> resultList;
    ArrayList current;
 
    public List> partition(String s) {
        resultList = new ArrayList<>();
        current = new ArrayList();
        findPalindrome(s, 0);
        return resultList;
    }
 
    /**
     * 主要思路是回溯
     * @param str
     * @param left
     */
    private void findPalindrome(String str, int left) {
        //回溯返回条件,left指针已到最后,也就是回溯到底了
        if (current.size() > 0 && left >= str.length()) {
            ArrayList tempList = (ArrayList) current.clone();
            resultList.add(tempList);
        }
        for (int right = left; right < str.length(); right++) {
            //不是回文的话,直接right++;
            if (isPalindrome(str, left, right)) {
                //添加回文
                if (left == right) {
                    current.add(Character.toString(str.charAt(left)));
                }else{
                    current.add(str.substring(left, right +1));
                }
                //进行回溯
                findPalindrome(str, right + 1);
                //移除刚刚添加的元素,也就是回到之前的状态,以便走其他分支
                current.remove(current.size() - 1);
            }
        }
 
    }
 
    public boolean isPalindrome(String str, int left, int right) {
        if (left == right) {
            return true;
        }
        while (left < right) {
            if (str.charAt(left) != str.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

回文最少分割数

给定一个字符串s,将s分割成一些子串,使每个子串都是回文。
返回s符合要求的的最少分割次数。

思路:第一步,创建一个二维数组,假设为b,其中b[i][j],表示的是如果i~j范围的字符串是回文字符串的话,b[i][j]为true,反之为false。这里就是这道题比较难的部分
  这里用图片来辅助解释一下

示意图

例如上面的图片,其中i在[1,s.length()]区间里面,至于为什么在这个区间里面,待会代码中解释;j再[0, s.length() - i + 1)区间里面;k是由i,j计算出来的。
  从图中我们可以得出的是,i表示[i,k]的字符串的中心,i,表示子串的两端,我们可以得出:如果s.charAr(j) == s.charAt(k)和b[j +1][k - 1]为true的话,b[i][k]肯定为true。
   第二步,再创建一个以维数组,假设为dp,其中dp[i],表示切割0~i范围的字符串的最小次数。动态规划方程:dp[i] = Math.min(dp[i], dp[j] + 1);
作者:琼珶和予
链接: https://www.jianshu.com/p/188780c84d78
来源:
著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    public int minCut(String s) {
        boolean[][] b = new boolean[s.length()][s.length()];
        for (int i = 1; i <= s.length(); i++) {
            //j表示以i为中心的子字符串左侧
            //s.length() - i + 1,计算的是i的右侧还有几个字符
            //所以j在[0, s.length() - i + 1)
            //这里便能解释为什么i必须从1开始,如果从0开始的话,0是没有左侧的
            for (int j = 0; j < s.length() - i + 1; j++) {
                //以i为中心,与j对应的右侧位置k
                int k = j + i - 1;
                if (i == 1) {
                    b[j][k] = true;
                } else {
                    if (s.charAt(j) == s.charAt(k) && (j == k - 1 || b[j + 1][k - 1]))
                        b[j][k] = true;
                }
            }
        }
        int[] dp = new int[s.length()];
        Arrays.fill(dp, Integer.MAX_VALUE);
        for (int i = 0; i < s.length(); i++) {
            if (b[0][i]) {
                dp[i] = 0;
            }
            for (int j = 0; j < i; j++) {
                if (b[j + 1][i])
                    dp[i] = Math.min(dp[i], dp[j] + 1);
            }
        }
        return dp[s.length() - 1];
    }

至少有K个重复字符的最长子串

找到给定字符串(由小写字符组成)中的最长子串 T , 要求 T 中的每一字符出现次数都不少于 k 。输出 T 的长度。
输入:
s = "ababbc", k = 2
输出:
5
最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。

思路:记录每个字符出现的次数,把出现次数小于k次的字符存放在splitSet中,如果没有,说明子串所有字符都出现大于k次,可以直接返回。否则就根据分割点分割然后进行下次检查

public int longestSubstring(String s, int k) {
    HashMap counter = new HashMap();
  
    for(int i=0; i splitSet = new HashSet();
    for(char c: counter.keySet()){
        if(counter.get(c)

三角形最小路径和

思路: 从下往上算出每个节点到父节点的最小路径,然后循环向上即可,顶点就是最大值
申请n个空间minNums[n],初始化minNums[n]为数据triangle[][]的最后一行。最后一行的数字到最底层的最小路径和就是他们自己本身。
从倒数第二行开始往上(row),从左向右(col)循环计算并更新minNums的值,minNums[col]=min(minNums[col], minNums[col+1]) + triangle[row][col], 最后minNums[0]就是我们要的答案。
作者:Orange橘子洲头
来源:CSDN
原文:https://blog.csdn.net/smile_watermelon/article/details/46741303
版权声明:本文为博主原创文章,转载请附上博文链接!

public int minimumTotal(List> triangle) {
    int[] A = new int[triangle.size()+1];
    for(int i=triangle.size()-1;i>=0;i--){
        for(int j=0;j

二叉树中的最大路径和

给出一棵二叉树,寻找一条路径使其路径和最大,路径可以在任一节点中开始和结束(路径和为两个节点之间所在路径上的节点权值之和)

思路: 与三角形的最小路径和相似,只是二叉树只能从顶向下,这样可以算出每个孩子的路径然后递归求到最大路径和,需要将负数的路径减枝

class Solution {
public:
    int maxPathSum(TreeNode* root) {
        int res = INT_MIN;
        helper(root, res);
        return res;
    }
    int helper(TreeNode* node, int& res) {
        if (!node) return 0;
        int left = max(helper(node->left, res), 0);
        int right = max(helper(node->right, res), 0);
        res = max(res, left + right + node->val);
        return max(left, right) + node->val;
    }
};

单词拆分

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

For example, given
s = “leetcode”,
dict = [“leet”, “code”].

Return true because “leetcode” can be segmented as “leet code”.

思路: 将问题拆分成更小的子问题。用dp[i]表示0到i的子字符串是否可以拆分成满足条件的单词,在计算dp[i]的时候,我们已经知道dp[0],dp[1],…,dp[i-1],如果以i为结尾的ji子串是满足条件的,并且0j的子串也是在字典中的,那么dp[i]就是true。
用公式表示就是:
∪res[j]&&s.substring[j,i+1]∈dict

   public boolean wordBreak(String s, Set wordDict) {
        if(s==null || s.length() == 0){
            return false;
        }
        boolean[] res = new boolean[s.length()+1];
        res[0] = true;
        for(int i=0;i

单词拆分2

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。

思路: 先计算是否可拆分,然后通过判断进行回溯,这样其实需要的循环就很少了

public List wordBreak(String s, Set wordDict) {
    ArrayList [] pos = new ArrayList[s.length()+1];
    pos[0]=new ArrayList();
  
    for(int i=0; i list = new ArrayList();
                        list.add(sub);
                        pos[j]=list;
                    }else{
                        pos[j].add(sub);
                    }
  
                }
            }
        }
    }
  
    if(pos[s.length()]==null){
        return new ArrayList();
    }else{
        ArrayList result = new ArrayList();
        dfs(pos, result, "", s.length());
        return result;
    }
}
  
public void dfs(ArrayList [] pos, ArrayList result, String curr, int i){
    if(i==0){
        result.add(curr.trim());
        return;
    }
  
    for(String s: pos[i]){
        String combined = s + " "+ curr;
        dfs(pos, result, combined, i-s.length());
    }
} 

完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

思路:广度优先+队列,每次都把可以减少的数减去,然后进行下次递归,当等于0时返回step

class Solution {
    public int numSquares(int n) {
        //Queue queue = new PriorityQueue<>();
        //知识点链表赋值给队列接口,也可以如上使用优先队列
        //对这个有疑问可参考:https://stackoverflow.com/questions/21727873/queueinteger-q-new-linkedlistinteger
        Queue queue = new LinkedList<>();
        Set set = new HashSet<>();
        int step = 0;
        queue.offer(n);
        while (!queue.isEmpty()) {
            int size = queue.size();
            step++;
            for (int i = 0; i < size; i++) {
                int curr = queue.poll();
                if (!set.add(curr)) {
                    continue;
                }
                for (int j = 1; j <= Math.sqrt(curr); j++) {
                    int next = curr - j * j;
                    if (next == 0) {
                        return step;
                    }
                    queue.offer(next);
                }
            }
        }
        return 0;
    }
}

零钱兑换

给定不同面额的硬币(coins)和一个总金额(amount)。写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合方式能组成总金额,返回-1。
示例 1:
coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)

示例 2:
coins = [2], amount = 3
return -1.

注意:
你可以认为每种硬币的数量是无限的。

思路:如果能换就是dp[i-coins[j]] +1, 不能换就是默认值,取小的那个

class Solution {
public:
    int coinChange(vector& coins, int amount) {
        vector dp(amount+1,amount+1);
        dp[0]=0;
        int n=coins.size();
        if(n==0||amount==0) return 0;
        sort(coins.begin(),coins.end());
        for(int i=1;i<=amount;i++){
            for(int j=0;j

买卖股票的时机-1

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

思路:维护一个min数组来记录之前的最小值,然后用当前值减去最小值即可,更新max

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length <= 1) {
            return 0;
        }
        int[] min = new int[prices.length];
        min[0] = prices[0];
        int maxProfit = 0;
        for(int i=1;i < prices.length;i++){
            min[i] = prices[i] < min[i-1]? prices[i] : min[i-1];
            maxProfit = maxProfit < prices[i] - min[i]?prices[i] - min[i]:maxProfit;
        }
        return maxProfit;
    }
}

买卖股票的时机-2

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

思路:因为可以当日买卖,所以只要有利润就可以买卖

class Solution {
    public int maxProfit(int[] prices) {
        // write your code here
        if(prices == null)
            return 0;
        int sum = 0;
        for(int i =0;i< prices.length - 1;i++){
            if(prices[i] < prices[i+1])
                sum += prices[i+1] - prices[i];
        }
        return sum;
        
    }
}

你可能感兴趣的:(动态规划题总结)