LeetCode探索(动态规划)

动态规划

动态规划的实质其实就是穷举,通过DP表的形式折叠一些重复的计算。dp其实没有什么好的模板,重点在于写出状态转移方程。但是即使写出了状态转移方程还是有很多细节需要注意。
总的来说DP的模板可以归结成如下:

//初始化
dp[0][0] = base;
//进行状态转移
for(状态1 in 状态1所有的取值)
	for(状态2 in 状态2所有的取值)
		dp[状态1][状态2] = 状态转移方程

不过这也太笼统了吧,还是来看几道经典的DP吧。

最长回文子串

题目:https://leetcode-cn.com/problems/longest-palindromic-substring/
题目大意:给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

分析:这道题是DP的经典问题了。
其实判断回文子串就是一个自己和自己比较的过程,我们初始化一个二维数组,比较每个位置的上的字符是否相等,如果相等的话那么向中间继续比较。
写成状态转移方程就是
dp[i][j] = A[i] == A[j]? dp[i-1][j+1]:false

当然对于一些特殊情况还是需要进行考虑,对于回文子串来说一共有两种情况。第一种就是奇数长度,在这种情况下经过不断DP,最后会查找到i = j的位置上,所以我们需要把i = j位置的上的boolean初始化为true。
第二种就是偶数长度,这种情况下最后会归结到一个空的字符串上,对于这种情况,我们可以像以往一样采用dp[i][j]代表A[i-1]和A[j-1]的字符串进行比较,也可以直接进行判断,如果j-i<3&&A[i] == A[j]那么dp[i][j] = true

class Solution {
    public String longestPalindrome(String s) {
        if(s == null || s.length() == 0)
            return s;
        char[] chars = s.toCharArray();
        boolean[][] dp = new boolean[s.length()][s.length()];
        //如果不设置成1的话
        //会导致如果字符串没有回文子串会返回""
        //但是实际上应该返回第一个字符
        //因为一个字符就是一个回文串
        int maxLen = 1;
        //保存最长回文子串的起始位置
        int begin = 0;
        for(int i=0;i<s.length();i++)
            dp[i][i] = true;
        for(int i=1;i<s.length();i++){
            //由于数组是对称的,所以计算一半就行
            for(int j=0;j<i;j++){
                if(chars[i] == chars[j]){
                    if(i-j<3){
                        dp[i][j] = true;
                    }else{
                    	//向中间比较
                        dp[i][j] = dp[i-1][j+1];
                    }
                }else{
                    dp[i][j] = false;
                }
                //是回文子串才能记录最大值
                if(dp[i][j] && i-j+1>maxLen){
                    maxLen = i-j+1;
                    begin = j;
                }
            }
        }
        return s.substring(begin,begin+maxLen);
    }
}

编辑距离

题目:https://leetcode-cn.com/problems/edit-distance/
题目大意:给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:插入一个字符、删除一个字符、替换一个字符。
分析:这道题看起来非常复杂,但是看了解答之后会有一种恍然大悟的感觉。
对于一个字符rose有三种操作,如果要转化为ros采用删除操作,操作数+1.
那么对于rorse转化为rose也是采用删除操作,操作数+1,。
通过这么倒推之后我们发现,前面的操作依赖于后面的操作,那么对于这种题目就可以采用动态规划来做。
我们创建一个数组int[][] dp,对于dp[i][j]代表的是字符串word1[:i-1]word2[:j-1]
如果word1[i-1] == word2[j-2],那么这一步不需要进行任何操作。
如果不相等,那么一共有三种操作。
例如,对于目标字符串a,假设目前字符串为ab,那么采用删除操作,然后比较的
是a和a,所以就是比较w[i-1]和w[j]也就是dp[i-1][j]
假设目标字符串为ab,目标字符串为abc,那么采用添加操作,然后就是比较ab和ab,所以就是比较w1[i]w[j-1]也就是dp[i][j-1]
假设目标字符串为abc,当前字符串为abd,那么采用替换操作,也就是比较w[i-1]w[j-1],也就是dp[i-1][j-1]
所以得出结论,替换依赖于dp[i-1][j-1],添加依赖于dp[i][j-1],删除依赖于dp[i-1][j]
由于需要求的是最小距离,所以我们需要在这三个数中取最小值,所以状态转移方程为
dp[i][j] = w1[i]==w2[j]?dp[i-1][j-1]:Min(dp[i-1][j],dp[i][j-1],dp[i-1]][j-1])+1

class Solution {
    public int minDistance(String word1, String word2) {
        if(word1 == null && word2 == null)
            return 0;
        if(word1.length() == 0 && word2.length() == 0)
            return 0;
        if(word1 == null)
            return word2.length();
        if(word2 == null)
            return word1.length();
        char[] chars1 = word1.toCharArray();
        char[] chars2 = word2.toCharArray();
        int[][] dp = new int[word1.length()+1][word2.length()+1];
        for(int i=0;i<word1.length()+1;i++){
            dp[i][0] = i;
        }
        for(int i=0;i<word2.length()+1;i++){
            dp[0][i] = i;
        }
        for(int i=1;i<word1.length()+1;i++){
            for(int j=1;j<word2.length()+1;j++){
                if(chars1[i-1] == chars2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                	//记得操作+1
                    dp[i][j] = Math.min(Math.min(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1])+1;
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }
}

最长重复子数组

题目:https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/
题目大意:给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
分析:这道题其实就是经典的最长公共子串的变形。
dp[i][j] = nums[i] == nums[j]?dp[i-1][j-1]+1:0

class Solution {
    public int findLength(int[] A, int[] B) {
        if(A == null || B == null)
            return 0;
        int max = 0;
        int[][] dp = new int[A.length+1][B.length+1];
        for(int i=1;i<A.length+1;i++){
            for(int j=1;j<B.length+1;j++){
                if(A[i-1] == B[j-1])
                    dp[i][j] = dp[i-1][j-1]+1;
                else
                    dp[i][j] = 0;
                if(dp[i][j] != 0)
                    max = Math.max(max,dp[i][j]);
            }
        }
        return max;
    }
}

最长公共子序列

题目:https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/submissions/
题目大意:给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。

分析:其实这道题和上面那道差不多,但是区别在于这道题的序列是可以不连续的,但是顺序要相同。
那么对于字符abcdeace来说。
假设我们以abcde为主,逐个比较text2ace,当text2为a的时候此时公共子序列为1,为ac的时候,子序列为1,为ace的时候子序列为2。
对于ace也是一样,所以可以得出状态转移方程:
dp[i][j] = text1[i-1] == text[j-1]?dp[i-1][j-1]+1:Max(dp[i-1][j],dp[i][j-1])

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        if(text1 == null || text2 == null || text1.length() == 0 || text2.length() == 0)
            return 0;
        int[][] dp = new int[text1.length()+1][text2.length()+1];
        int lengt1 = text1.length(),length2 = text2.length();
        for(int i=1;i<lengt1+1;i++){
            for(int j=1;j<length2+1;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[lengt1][length2];
    }
}

零钱兑换

题目:https://leetcode-cn.com/problems/coin-change/
题目大意:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

分析:这道题其实算是完全背包问题,比起上面的题目代码上稍微简单一些。
状态转移方程是:
dp[i] = i>coin?Min(dp[i-coin]+1,dp[i]):dp[i] coin:coins

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount+1];
        Arrays.fill(dp,amount+1);
        dp[0]=0;
        for(int i=1;i<amount+1;i++){
            for(int coin:coins){
                if(i-coin>=0 && dp[i-coin]!=amount+1){
                    dp[i] = Math.min(dp[i],dp[i-coin]+1);
                }
            }
        }
        return dp[amount] == amount+1?-1:dp[amount];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值