LintCode 字符串(一)

字符串全排列

字符串的全组合

字符串的包含

乱序字符串 

旋转字符串

翻转字符串

判断字符串是否没有重复字符

转换字符串到整数

最后一个单词的长度

最长单词

单词切分

字符串匹配

最长公共子串

最长公共子序列

最长公共前缀

最长无重复字符的子串

 

 

乱序字符串

给出一个字符串数组S,找到其中所有的乱序字符串(Anagram)。如果一个字符串是乱序字符串,那么他存在一个字母集合相同,但顺序不同的字符串也在S中。

对于字符串数组 ["lint","intl","inlt","code"]。返回 ["lint","inlt","intl"]

分析:每一组乱序字符串组都应该有唯一的相同的“ Hash 值 ”,这个值是数字和字母的结合,比如 "and" 和 "dan",他们的“ Hash 值 ”就是“a1d1n1","array" 和 "yarar" 就是 a2r2y1,这样就确保了唯一性。用这个hash值作为key,value为List<String>

public class Solution {
    /**
     * @param strs: A list of strings
     * @return: A list of strings
     */
    public List<String> anagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<>();
        for(int i=0; i<strs.length; i++){
            String mapKey = getKey(strs[i]);
            if(map.containsKey(mapKey)){
                map.get(mapKey).add(strs[i]);
            }else{
                List<String> list = new ArrayList<>();
                list.add(strs[i]);
                map.put(mapKey, list);
            }
        }
        Collection<List<String>> values = map.values();
        List<String> res = new ArrayList<>();
        for(List<String> li : values){
            if(li.size() > 1){
                res.addAll(li);
            }
        }
        return res;
    }
    
    private String getKey(String str){
        int[] hash = new int[26];
        for(int i=0; i<str.length(); i++){
            int idx = str.charAt(i)-'a';
            hash[idx]++;
        }
        StringBuilder sb = new StringBuilder();
        for(int i=0; i<26; i++){
            if(hash[i] > 0){
                sb.append((char)(i+'a'));
                sb.append(hash[i]);
            }else{
                continue;
            }
        }
        return sb.toString();
    }
    
}

判断字符串是否没有重复字符

public boolean isUnique(String str){
    boolean[] charArr = new boolean[128];
    for(int i=0; i<str.length(); i++){
        int val = str.charAt(i);
        if(charArr[val])
            return false;
        charArr[val] = true;
    }
    return true;
}

单词切分

给出一个字符串s和一个词典,判断字符串s是否可以被切分成一个或多个出现在字典中的单词。

public boolean wordBreak(String s, Set<String> dict) {
    if ((s == null || s.length() == 0) && (dict == null || dict.size() == 0))
        return true;
    return wordBreak(s, dict, 0);
}

public boolean wordBreak(String s, Set<String> dict, int start) {
    boolean dp[] = new boolean[s.length() + 1];
    dp[0] = true;// 初始值
    for (int i = 0; i < s.length(); i++) {
        if (!dp[i])
            continue;
        for (String t : dict) {
            int len = t.length();
            int end = i + len;
            if (end > s.length())
                continue;
            if (s.substring(i, end).equals(t)) {
                dp[end] = true;
            }
        }
    }
    return dp[s.length()];
}

 

字符串的包含

判断字符串B中的所有字符是否包含在字符串A中,A长B短。

/**
 * Created by ix on 16/9/10.
 * http://blog.csdn.net/v_JULY_v/article/details/6347454
 */
public class ContainStr {

    /**
     * 蛮力轮询 O(mn)
     * @param longStr
     * @param shortStr
     * @return
     */
    public static boolean contain(String longStr, String shortStr){
        for(int i=0; i<shortStr.length(); i++){
            int j;
            for(j=0; j<longStr.length() && longStr.charAt(j)!=shortStr.charAt(i); ++j){
                ;
            }
            if(j>=longStr.length()){
                return false;
            }
        }
        return true;
    }

    /**
     * 先排序,后轮询 采用快速排序   排序O(mlogm)+O(nlogn) 轮询O(m+n)
     * @param longStr
     * @param shortStr
     * @return
     */
    public static boolean contain2(String longStr, String shortStr){
        char[] longArr = longStr.toCharArray();
        char[] shortArr = shortStr.toCharArray();
        quickSort(longArr, 0, longArr.length-1);
        quickSort(shortArr, 0, shortArr.length-1);
        for(int pa=0, pb=0; pb<shortArr.length ; ){
            while(pa<longArr.length && longArr[pa]<shortArr[pb]){
                ++pa;
            }
            if(pa>=longArr.length || longArr[pa]>shortArr[pb]){
                return false;
            }
            ++pb;
        }
        return true;
    }

    private static int partition(char[] a, int left, int right){
        char pivot = a[left];
        while(left<right){
            while(left<right && a[right]>pivot){
                right--;
            }
            if(left<right){
                a[left++] = a[right];
            }

            while(left<right && a[left]<pivot){
                left++;
            }
            if(left<right){
                a[right--] = a[left];
            }
        }
        a[left] = pivot;
        return left;
    }
    private static void quickSort(char[] a, int left, int right){
        if(left>=right){
            return;
        }
        int index = partition(a, left, right);
        quickSort(a, left, index-1);
        quickSort(a, index+1, right);
    }

}

字符串的全组合

输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。

解析:

1、全组合问题就是所有元素(记为n)中选1个元素的组合, 加上选2个元素的组合...加上选n个元素的组合的和。

n个元素选m个元素的组合问题的实现。原理如下: 从后往前选取, 选定位置i后, 再在前i-1个里面选取m-1个. 如: 1, 2, 3, 4, 5 中选取3个元素.

  1) 选取5后, 再在前4个里面选取2个, 而前4个里面选取2个又是一个子问题, 递归即可;

  2) 如果不包含5, 直接选定4, 那么再在前3个里面选取2个, 而前三个里面选取2个又是一个子问题, 递归即可;

  3) 如果也不包含4, 直接选取3, 那么再在前2个里面选取2个, 刚好只有两个.

纵向看, 1与2与3刚好是一个for循环, 初值为5, 终值为m. 横向看, 该问题为一个前i-1个中选m-1的递归. 

/**
 * 全组合 递归 有重复字符就不能用
 * @param s
 */
public static void combinationRec(char[] s){
    char[] subchars = new char[s.length];
    for(int i=0; i<s.length; i++){
        int m = i+1;
        combinationRec(s, s.length, m, subchars, m);
    }
}
private static void combinationRec(char[] s, int n, int m, char[] subchars, int subNum){
    if(m==0){
        for(int i=0; i<subNum; i++){
            System.out.print(subchars[i]);
        }
        System.out.println();
    }else {
        for(int i=n; i>=m; i--){
            subchars[m-1] = s[i-1];
            combinationRec(s, i-1, m-1, subchars, subNum);
        }
    }
}

2、位操作

假设原有元素n个,最终的组合结果有2^n - 1. 可以使用2^n - 1个位,1表示取该元素,0表示不取。 所以a表示001,取ab是011。
001,010,011,100,101,110,111。对应输出组合结果为:a,b,ab,c,ac,bc,abc
因此可以循环 1~2^n-1(字符串长度),然后输出对应代表的组合即可。

/**
 * 字符串全组合 基于位操作
 * @param s
 */
public static void combination(char[] s) {
    if (s.length == 0) {
        return;
    }
    int len = s.length;
    int n = 1 << len;
    for (int i = 0; i < n; i++) {
        StringBuilder sb = new StringBuilder();
        for (int j = 0; j < len; j++) {
            if ((i & (1 << j)) != 0) {
                sb.append(s[j]);
            }
        }
        System.out.print(sb + " ");
    }
}

字符串全排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 结果请按字母顺序输出。

解析:全排列递归解法、非递归解法。

1)递归算法设计的基本思想是将一个难以解决的大问题,分解为一些规模较小的相同问题,以便各个击破,分而治之。我们有一字符串为abc,对应的全排列有6种:abc、acb、bca、bac、cab和cba。它可以由下述规则得到:
abc的全排列可以由下述规律得到:
a + 全排列(bc)
b + 全排列(ac)
c + 全排列(ab)
而各个子串全排列可以看作总串的一个相同但规模较小的问题,即用递归解决。

符合题目要求的代码:不管输入的字符串顺序如何,最后结果都是字典顺序输出。TreeSet保证顺序和去重。

/**
 * 返回List 用Set去重
 * @param str
 * @return
 */
public static ArrayList<String> permutation2(String str) {
    ArrayList<String> re = new ArrayList<>();
    if (str == null || str.length() == 0) {
        return re;
    }
    TreeSet<String> set = new TreeSet<>();
    fun(set, str.toCharArray(), 0, str.length()-1);
    re.addAll(set);
    return re;
}

private static void fun(TreeSet<String> set, char[] str, int start, int end) {
    if (start == str.length) {
        set.add(new String(str));
        return;
    }
    for (int i = start; i <= end; i++) {
        swap(str, i, start);
        fun(set, str, start + 1, end);
        swap(str, i, start);
    }
}

private static void swap(char[] s, int i, int j) {
    char tmp = s[i];
    s[i] = s[j];
    s[j] = tmp;
}

另外一种形式:直接打印输出每个字符串。但是只针对没有重复的字符串,没有去重功能。而且输入的字符串必须是字典顺序(可以考虑先排序)

/**
 * 没有重复字符的全排列 递归
 *
 * @param s
 * @param from
 * @param to
 */
public static void permutation(char[] s, int from, int to) {
    if (from == to) {
        System.out.println(s);
    } else {
        for (int i = from; i <= to; i++) {
            swap(s, i, from);
            permutation(s, from + 1, to);
            swap(s, i, from);
        }
    }
}

在此基础上考虑去重,即如果输入122怎么办?

对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。

/**
 * 有重复字符的全排列 递归
 *
 * @param s
 * @param from
 * @param to
 */
public static void repeatPermutation(char[] s, int from, int to) {
    if (from == to) {
        System.out.println(s);
    } else {
        for (int i = from; i <= to; i++) {
            if (!hasSame(s, from, i)) {
                swap(s, i, from);
                repeatPermutation(s, from + 1, to);
                swap(s, i, from);
            }
        }
    }
}
// 判断[from, to) 区间内有没有s[i]==s[to]的
private static boolean hasSame(char[] s, int from, int to) {
    for (int i = from; i < to; i++) {
        if (s[i] == s[to]) {
            return true;
        }
    }
    return false;
}

2)非递归

要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字串"6220"颠倒即得到"950226"。

但是,要想得到全部的排列,输入的字符串必须从小到大排列。

这样,只要一个循环再加上计算字符串下一个排列的函数就可以轻松的实现非递归的全排列算法。

// 找下一个排列
private static boolean calc(char[] s, int n) {
    int i;
    // 从后往前找, 找到第一个 s[i]<s[i+1] 的i值
    for (i = n - 2; (i >= 0) && (s[i] >= s[i + 1]); --i) {
        ;
    }
    if (i < 0) {
        return false;
    }
    int k;
    // 从后往前找, 找出第一个大于 s[i] 的字符, 要交换二者。
    for (k = n - 1; (k > i) && (s[k] <= s[i]); --k) {
        ;
    }
    swap(s, i, k); // 交换
    reverse(s, i + 1, n - 1);  // 把i之后的逆序。
    return true;
}
// 反转字符串
private static void reverse(char[] s, int i, int j) {
    while (i < j) {
        char tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
        i++;
        j--;
    }
}

最后一个单词的长度

给定一个字符串, 包含大小写字母、空格' ',请返回其最后一个单词的长度。

解答:主要是考虑字符串最后可能是空格,需要判断。

public int lengthOfLastWord(String s) {
    char[] chars = s.toCharArray();
    int len = 0;
    for(int i=chars.length-1; i>=0; i--){
        if(len==0){
            if(chars[i]==' '){
                continue;
            }else{
                len++;
            }
        }else{
            if(chars[i]==' '){
                break;
            }else{
                len++;
            }
        }
    }
    return len;
}

  

最长单词

找出字符串数组中其中所有最长的单词,有可能是多个。

解答一:可以遍历找到longest,再遍历找出单词。

// 遍历两次
public ArrayList<String> longestWords(String[] dictionary) {
    ArrayList<String> list = new ArrayList<>();
    
    int longest = -1;
    for(int i=0; i<dictionary.length; i++){
        int len = dictionary[i].length();
        longest = Math.max(len, longest);
    }
    
    for(int i=0; i<dictionary.length; i++){
        if(dictionary[i].length()==longest){
            list.add(dictionary[i]);
        }
    }
    return list;
}

解答二:思路是,碰到更长的字符串,把原来的ArrayList中的内容清空。这样只需遍历一次!

public ArrayList<String> longestWords(String[] dictionary){
    ArrayList<String> list = new ArrayList<>();
    int longest = -1;
    for(int i=0; i<dictionary.length; i++){
        int len = dictionary[i].length();
        if(len>longest){
            longest = len;
            list.clear(); // 清空
            list.add(dictionary[i]);
        }else if(len==longest){
            list.add(dictionary[i]);
        }
    }
    return list;
}

翻转字符串

给定一个字符串,逐个翻转字符串中的每个单词。(给出s = "the sky is blue",返回"blue is sky the")

解答:

(1)先转换成字符串数组,再从后往前遍历

public String reverseWords(String s) {
    if(s==null || s.length()==0){
        return "";
    }
    String[] ss = s.split(" ");
    StringBuilder sb = new StringBuilder();
    for(int i=ss.length-1; i>=0; i--){
        if(!ss[i].equals("")){
            sb.append(ss[i]).append(" ");
        }
    }
    return sb.length()==0?"":sb.substring(0, sb.length()-1);
}

 (2)不用StringBuilder,先整体翻转,再依次翻转每个单词

public static String reverseWords(String s) {
    if(s==null || s.length()==0) {
        return s;
    }

    char[] sArr = s.toCharArray();
    int start = 0;
    int end = s.length()-1;
    reverseStr(sArr, start, end); //反转整个字符串
    start = end = 0;
    while(start<s.length()){
        if(sArr[start]==' '){ //如果start为空格,则跳过它不把它识别为单词的一部分
            start++;
            end++;
        }
        //当end指针移到字符串数组末尾,或者当前为空格,则start指针到end指针之间构成一个单词
        else if(end==s.length() || sArr[end]==' '){
            reverseStr(sArr, start, --end);
            start = ++end;
        }else{
            end++;
        }
    }
    return new String(sArr);
}

private static void reverseStr(char[] strArr, int start, int end){
    if(strArr==null) return;
    while(start<end){
        char tmp = strArr[start];
        strArr[start++] = strArr[end];
        strArr[end--] = tmp;
    }
}

字符串匹配 13

对于一个给定的 source 字符串和一个 target 字符串,你应该在 source 字符串中找出 target 字符串出现的第一个位置(从0开始)。如果不存在,则返回 -1

 1、BF算法。暴力

// BF匹配算法,即暴力算法 O(MN)
public static int pipeiBF(String source, String target){
    if(source==null || target==null){
        return -1;
    }
    int lens = source.length();
    int lent = target.length();
    if(lent==0){
        return -1;
    }
    if(lens<lent){
        return -1;
    }
    
    int i=0, j=0;
    while(i<lens && j<lent){
        if(source.charAt(i)==target.charAt(j)){
            i++;
            j++;
        }else{ // 不相等主串回溯,模式串从0开始
            i = i-j+1;
            j = 0;
        }
    }
    if(j==lent){
        return i-j;
    }
    return -1;
}

BF算法的另一种形式

public int strStr(String source, String target) {
    if(source==null || target==null){
        return -1;
    }
    
    int slen = source.length();
    int tlen = target.length();
    if(tlen==0){
        return 0;
    }
    if(slen<tlen){
        return -1;
    }
    
    for(int i=0; i<slen; i++){
        if(slen-i<tlen){
            return -1;
        }
        int k=i;
        for(int j=0; j<tlen; j++){
            if(source.charAt(k)==target.charAt(j)){
                if(j==tlen-1){
                    return i;
                }
                k++;
            }else{
                break;
            }
        }
    }
    return -1;
}

暴力第三种形式

public int strStr(String haystack, String needle) {
  for (int i = 0; ; i++) {
    for (int j = 0; ; j++) {
      if (j == needle.length()) return i;
      if (i + j == haystack.length()) return -1;
      if (needle.charAt(j) != haystack.charAt(i + j)) break;
    }
  }
}

2、Sunday算法。

Sunday算法的思想是: 
1. 从文本串S的第pos个字符开始,和模式串T的第一个字符进行比较,若相等,则主串和模式串都后移一个字符继续比较; 
2. 若不相同,则将文本串参与匹配的最末位字符的后一个字符与模式串逆着匹配。 
3. 若匹配完模式串没有该字符,则模式串直接跳过,即移动位数 = 匹配串长度 + 1。 
4. 若模式串匹配到了该字符,则模式串中相同字符移动到文本串该字符下,与该字符对齐。其移动位数 = 模式串中最右端的该字符到末尾的距离+1。

实例:

字符串匹配:
下标i    0  1  2  3  4  5  6  7  8  9  10  11  12
主串     c  b  a  b  d  c  b  a  c  b   b   a   d
模式串   c  b  b  a 

从前往后比较,在i = 2处失配,关注i= 4(d)是否包含在模式串T中。遍历比较完发现不包含,将模式T移动到i = 5继续比较。(失配后,主串这轮参与比较的后一位(d)肯定要参与下一轮的比较,T中都没有d,肯定匹配不成功啊!还比什么,直接后移至(d)的下一位。)

下标i    0  1  2  3  4  5  6  7  8  9  10  11  12
主串S    c  b  a  b  d  c  b  a  c  b   b   a   d
模式串T                 c  b  b  a 

在i = 7处失配,关注i = 9(b),在模式串中找b,发现i = 6, i = 7处都有b,那应该和谁对齐? 应该和后面的(i = 7)处的对齐,因为要倒着在模式串中找b,找到了直接break,并将此处与i = 9对齐。

下标i    0  1  2  3  4  5  6  7  8  9  10  11  12
主串S    c  b  a  b  d  c  b  a  c  b   b   a   d
模式串T                       c  b  b   a 

在i=7处失配,关注i=11(a),匹配到模式串中最后一位为a,break;将模式串中的a与i = 17 处的 a 对齐。继续比较。

下标i    0  1  2  3  4  5  6  7  8  9  10  11  12
主串S    c  b  a  b  d  c  b  a  c  b   b   a   d
模式串T                          c  b   b   a 

匹配完成!

代码:

public static int pipeiSunday(String source, String target){
    if(source==null || target==null){
        return -1;
    }
    int lens = source.length();
    int lent = target.length();
    if(lent==0){
        return -1;
    }
    if(lens<lent){
        return -1;
    }
    
    int i = 0;
    int j = 0;
    boolean flag = false;
    while(i<lens && j<lent){
        if(source.charAt(i)==target.charAt(j)){
            i++;
            j++;
        }else{
            int num = i-j+lent; // 主串参与比较的最后一位的后一位
            for(j=lent-1; j>=0; j--){  // 主串num处的字符与模式串倒序比较,匹配到相同的就对齐。
                if(source.charAt(num)==target.charAt(j)){
                    i = num - j;
                    flag = true;
                    break;
                }
            }
            if(flag==false){  //在模式串没有找到匹配num的,直接跳到num的下一个开始匹配
                i = num + 1;
            }
            flag = false; //不管从哪开始匹配,flag都归为false
            j = 0; // 模式串还要从0开始匹配
        }
    }
    if(j==lent){
        return i-j;
    }
    return -1;
}

旋转字符串 8

给定一个字符串和一个偏移量,根据偏移量旋转字符串(从左向右旋转)

offset=0 => "abcdefg"
offset=1 => "gabcdef"
offset=2 => "fgabcde"
offset=3 => "efgabcd"

解答:前部分逆序,后部分逆序,整体逆序。

public void rotateString(char[] str, int offset) {
    int right = str.length-1;
    if(str!=null && str.length!=0){
        offset = offset%(right+1);
        rotateStr(str,0,right - offset);
        rotateStr(str,right - offset+1,right);
        rotateStr(str,0,right);
    }
}

// 倒序
private void rotateStr(char[]str,int left,int right){
    char tmp;
    while(left<right){
        tmp = str[left];
        str[left] = str[right];
        str[right] = tmp;
        left++;
        right--;
    }
}

最长公共子串 79

 给出两个字符串,找到最长公共子串,并返回其长度。(给出A=“ABCD”,B=“CBCE”,返回 2)
解答一:
// 暴力
public int longestCommonSubstring(String A, String B) {
    if(A==null || B==null || A.length()==0 || B.length()==0)
        return 0;
        
    int lenA = A.length();
    int lenB = B.length();
    int longest = -1;
    for(int i=0; i<lenA; i++){
        for(int j=0; j<lenB; j++){
            int m = i;
            int n = j;
            int sublongest = 0;
            while(m<lenA && n<lenB){
                if(A.charAt(m)==B.charAt(n)){
                    m++;
                    n++;
                    sublongest++;
                }else{
                    break;
                }                    
            }
            longest = Math.max(longest, sublongest);
        }
    }
    return longest;
}

 解答二:动态规划

假设两个字符串分别为s和t,s[i]和t[j]分别表示其第i和第j个字符(字符顺序从0开始),再令L[i, j]表示以s[i]和s[j]为结尾的相同子串的最大长度。若s[i+1]和t[j+1]不同,那么L[i+1, j+1]自然应该是0,因为任何以它们为结尾的子串都不可能完全相同;而如果s[i+1]和t[j+1]相同,那么就只要在以s[i]和t[j]结尾的最长相同子串之后分别添上这两个字符即可,这样就可以让长度增加一位。合并上述两种情况,也就得到L[i+1,j+1]=(s[i]==t[j]?L[i,j]+1:0)这样的关系。

public int longestCommonSubstring(String A, String B) {
    if(A==null || B==null || A.length()==0|| B.length()==0){
        return 0;
    }
    int lena = A.length();
    int lenb = B.length();
    int[][] cnt = new int[lena+1][lenb+1];
    int longest = -1;
    
    for(int i=1; i<=lena; i++){
        for(int j=1; j<=lenb; j++){
            if(A.charAt(i-1)==B.charAt(j-1)){
                cnt[i][j] = cnt[i-1][j-1] + 1;
            }else{
                cnt[i][j] = 0;
            }
            longest = Math.max(cnt[i][j], longest);
        }
    }
    return longest;
}

 最长公共子序列 77

给出两个字符串,找到最长公共子序列(LCS),返回LCS的长度。(给出 "ABCD" 和 "EACB",这个LCS是"AC"返回 2)
  最长公共子串:子串一定在原来字符串中连续存在的。如:ABCDEF 和SSSABCDOOOO最长公共子串是ABCD
  最长公共子序列:这个序列不是在原字符串中连续的位置,而是有间隔的,如:ABCDE 和AMBMCMDMEM 最长公共子序列是ABCDE
 
解答:动态规划。 设两个字符串是A、B,其长度是lenA、lenB,定义数组Arr[lenA+1][lenB+1] ,Arr[i][j] 表示A[0--i]、B[0--j] 两个字符串的最长公共子序列长度,对下面的一个位置 i+1 、j+1:
  若A[i+1] == B[j+1], 则Arr[i+1][j+1] = Arr[i][j] + 1
  若A[i+1]!=B[j+1],Arr[i+1][j+1] 应该根据其前面一个元素的值确定 Arr[i+1][j+1] =max(Arr[i+1][j],Arr[i][j+1])
public int longestCommonSubsequence(String A, String B) {
    if(A== null || B == null||A.length() == 0|| B.length() ==0)
        return 0;
    
    int lenA = A.length();
    int lenB = B.length();
    int d[][] = new int[lenA + 1][lenB + 1];
    
    for(int i=1;i<= lenA;i++){
        for(int j=1;j<=lenB;j++){
            char a = A.charAt(i-1);
            char b = B.charAt(j-1);
            if(a ==b ){
                d[i][j] = d[i-1][j-1] + 1;
            }else{
                d[i][j] = Math.max(d[i-1][j],d[i][j-1]);
            }
        }
    }
    return d[lenA][lenB];
}

最长公共前缀

给k个字符串,求出他们的最长公共前缀(LCP)  (在 "ABCDEFG", "ABCEFG", "ABCEFA" 中, LCP 为 "ABC")

 解答一:
  1.找到最短的字符串,求出长度shortest
  2.设最短公共前缀长度是shortest
  3.遍历所有字符串,比较长度是shortest的前缀是否相同,相同就是答案
  3.不相同shortest-=1,重复 2 、3 步
public String longestCommonPrefix(String[] strs) {
    if(strs.length==0)
        return "";
    
    int shortest = Integer.MAX_VALUE;
    for(int i=0; i<strs.length; i++){
        int len = strs[i].length();
        shortest = Math.min(shortest, len);
    }
    
    // 遍历所有字符串,比较前缀是否相等
    int i = 0;
    while(shortest>0){
        for(i=0; i<strs.length-1; i++){
            if(strs[i].substring(0, shortest).equals(strs[i+1].substring(0, shortest))){
                continue;
            }else{
                break;
            }
        }
        if(i==(strs.length-1)){
            break;
        }else{
            shortest--;
        }
    }
    return strs[0].substring(0, shortest);
}

解答二:

  把第0个str当作prefix,比较prefix和下一个str有几位相等。根据相等的位数,再切分出新的prefix。这样依次遍历所有的str,得到longest prefix。

public String longestCommonPrefix(String[] strs) {
    if(strs==null || strs.length==0){
        return "";
    }
    
    String prefix = strs[0];
    for(int i=1; i<strs.length; i++){
        int j = 0;
        while(j<strs[i].length() && j<prefix.length() && strs[i].charAt(j)==prefix.charAt(j)){
            j++;
        }
        if(j==0){
            return "";
        }
        prefix = prefix.substring(0, j);
    }
    return prefix;
}

转换字符串到整数

将一个字符串转换为整数。如果没有合法的整数,返回0。如果整数超出了32位整数的范围,返回INT_MAX(2147483647),或者INT_MIN(-2147483648)。

样例:"10" =>10, "-1" => -1, "1.0" => 1, "123123123123123" => 2147483647

解答:先判断第0位是不是“+”,“-”。 然后index从小到大遍历字符串:num = num*10 + (str.charAt(index)-'0');

public int atoi(String str) {
    if(str==null){
        return 0;
    }
    str = str.trim();
    if(str.length()==0){
        return 0;
    }
    
    int sign = 1; //正负
    int index = 0;
    if(str.charAt(index)=='+'){
        index++;
    }else if(str.charAt(index)=='-'){
        sign = -1;
        index++;
    }
    
    long num = 0;
    for( ; index<str.length(); index++){
        if(str.charAt(index)<'0' || str.charAt(index)>'9'){
            break;
        }
        num = num*10 + (str.charAt(index)-'0');
        if(num>Integer.MAX_VALUE){
            break;
        }
    }
    
    if(num*sign >= Integer.MAX_VALUE){
        return Integer.MAX_VALUE;
    }
    if(num*sign <= Integer.MIN_VALUE){
        return Integer.MIN_VALUE;
    }
    
    return (int)num*sign;
}

最长无重复字符的子串

给定一个字符串,请找出其中无重复字符的最长子字符串。

样例:在"abcabcbb"中,其无重复字符的最长子字符串是"abc",其长度为 3"bbbbb",其无重复字符的最长子字符串为"b",长度为1

解答一:利用HashMap,map中不存在就一直加入,存在的时候,找到相同字符的位置,更改索引,清空map,继续遍历。

public int lengthOfLongestSubstring(String s) {
    if(s==null || s.length()==0){
        return 0;
    }
    if(s.length()==1){
        return 1;
    }
    
    int longest = -1;
    HashMap<Character, Integer> map = new HashMap<>();
    for(int i=0; i<s.length(); i++){
        Character ch = s.charAt(i);
        if(map.containsKey(ch)){
            longest = Math.max(map.size(), longest);
            i = map.get(ch); // 从出现相同字符的下一位继续遍历
            map.clear();
        }else{
            map.put(ch, i);
        }
    }
    
    longest = Math.max(map.size(), longest);
    return longest;
}

 解答二:利用数组

public int lengthOfLongestSubstring(String s) {
    if(s==null || s.length()==0){
        return 0;
    }
    if(s.length()==1){
        return 1;
    }
    
    int start = 0;
    int longest = -1;
    boolean[] flag = new boolean[256];
    char[] chars = s.toCharArray();
    for(int i=0; i<chars.length; i++){
        char cur = chars[i];
        if(flag[cur]){ // 出现重复字符串
            longest = Math.max(longest, i-start);
            for(int k=start; k<i; k++){ //找到是哪个char重复了
                if(chars[k]==cur){
                    start = k+1;
                    break;
                }
                flag[chars[k]] = false;
            }
        }else{
            flag[cur] = true;
        }
    }
    longest  = Math.max(longest, chars.length-start);
    return longest;
}
 

 

原文地址:https://www.cnblogs.com/hesier/p/5630300.html