几个常见的DP问题及解法

前言

记录一些最近遇到的DP问题,并给出解法。只会记录我知道的最优解法(一般是时间最优),并贴出源码。有OJ的还会贴上地址。

1. 求字符串的最长不重复子串的长度

举例:

  • abcabcbb,返回abc
  • bbbbb,返回b
  • pwwkew,返回wke。注意pwke是子序列,不是子串

问题分析:
记字符串存放在字符数组cs中,遍历cs的下标为i。分析可知,想求截止到i的最长不重复子串的长度,只要从i向左,找到第1个重复的字符,其右边的数标记为left,则所求长度就是i-left+1与之前求得的结果中的最大值。举例说明:

  • 当字符串为abcdace,而i=5时,left应该为3。此时i-left+1=3,而之前求得的最长不重复子串的长度是4>3,所以i=5时的结果是4。

经分析可知,该算法需要在每一次遍历时,向左查找0~i个不等的元素,所以时间复杂度是O(n2),空间复杂度O(1)
上述方案尚有优化的空间,是因为在寻找left时采用了简单的遍历。经过分析可以知道left的变化是有规律的。只要知道与cs[i]相同的字符在上一次出现的位置,记为k,则left=k+1(k>=left的前提下)。所以,我们需要一个数组,记录所有元素最后出现的下标,就可以将查找left的时间从O(n)降到O(1),此时时间复杂度是O(n),空间复杂度是O(1)(增加了长度为256的定长辅助数组,所以空间复杂度不变)

实现代码如下:

public class Solution {
    public int lengthOfLongestSubstring(String str) {
        if(str==null||str.length()==0)
            return 0;
        char[] cs = str.toCharArray();
        int[] charMap = new int[256];
        Arrays.fill(charMap , -1);
        int max = 0;
        int left = 0;
        for(int i=0;iif(charMap[cs[i]]>=left)
                left = charMap[cs[i]]+1;
            int tmp = i-left+1;
            if(tmp>max)
                max = tmp;
            charMap[cs[i]]=i;
        }
        return max;
    }
}

代码在LeetCode上运行通过。

2. 两个字符串的最长公共子串

最朴素的解法,就是遍历两个字符串的所有子串,然后比较是否相等。两层嵌套循环,再加上字符串的比较,时间复杂度是O(m*n*n),其中mn是两个字符串的长度,且m>n。肯定有优化的方法。
考虑使用二维数组help[i][j],其中istr1的下标,jstr2的下标,help[i][j]记录的是str1的第i个字符与str2的第j个字符是否相等。举例说明,字符串abccbadeccb,其help数组为:

几个常见的DP问题及解法_第1张图片

很明显,最长公共子串就是help中对角线上全为1的最长序列,就是图中的红色部分。这种解法使用了一个m*n的辅助数组,将时间复杂度降到了O(MN)。
其实,help数组并不需要m*n,只需要n就够了。如果将help数组记录成如下方式:

几个常见的DP问题及解法_第2张图片

这样一来,其实我们在每轮遍历时,只需要记录help的一行就可以了,如图中红框所示。下一趟遍历可以覆盖掉上一趟的结果,只要将有可能是最长的位置记录下来即可,这里要使用两个变量endlengthend记录help数组当前的下标,length记录对应的公共子串的长度。在最后一轮遍历结束后,只需要返回从end-length+1end的子串,就可以了。
下面是代码:

public String getLongestCommonSubstring(String str1, String str2){
    //check input parameters
    if(str1==null||str2==null)
        return null;
    //find out the bigger string and the smaller one
    String big = str1.length()>=str2.length()?str1:str2;
    String small = str1.length()//get the end of the longest common substring in small
    int[] help = new int[small.length()];
    int end = 0;
    int length = 0;
    for(int i=0;ifor(int j=small.length()-1;j>=0;j--){
            if(big.charAt(i)==small.charAt(j)){
                if(j==0)
                    help[j]=1;
                else{
                    help[j]=help[j-1]+1;
                    if(help[j]>length){
                        length = help[j];
                        end = j;
                    }
                }
            }else
                help[j]=0;
        }
    }
    //get the longest common substring and return it
    if(length==0)
        return null;
    else
        return small.substring(end-length+1,end+1);
}

代码在华为OJ上测试通过,当然要符合他们的规范。

3.

你可能感兴趣的:(Java,基本功)