字符串

概述

字符串类类型的题目,往往想法比较简单,但是实现起来并不容易,复杂的字符串题目非常考验对代码的掌控能力

可以用来解决问题的方法:

  • 双指针法
  • KMP算法(当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了)

替换空格

Q:把字符串 s 中的每个空格替换成"%20"

 输入:s = "We are happy."  
 输出:"We%20are%20happy."

关键点:

  • 双指针
  • 从后向前遍历避免每次添加元素后移动元素
string replaceSpace(string s) {
    int count = 0; // 统计空格的个数
    int sOldSize = s.size();
    for (int i = 0; i < s.size(); i++) {
        if (s[i] == ' ') {
            count++;
        }
    }
    // 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
    s.resize(s.size() + count * 2);
    int sNewSize = s.size();
    // 从后先前将空格替换为"%20"
    for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
        if (s[j] != ' ') {
            s[i] = s[j];
        } else {
            s[i] = '0';
            s[i - 1] = '2';
            s[i - 2] = '%';
            i -= 2;
        }
    }
    return s;
}

重复串

Q:给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成

输入: "abab"
输出: True

关键点:

  • KMP(当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了)
  • len % (len - (next[len - 1] + 1)) == 0
  • (数组长度-最长相等前后缀的长度) 正好可以被 数组的长度整除,说明有该字符串有重复的子字符串
    // KMP里标准构建next数组的过程
    void getNext (int* next, const string& s){
        next[0] = -1;
        int j = -1;
        for(int i = 1;i < s.size(); i++){
            while(j >= 0 && s[i] != s[j+1]) {
                j = next[j];
            }
            if(s[i] == s[j+1]) {
                j++;
            }
            next[i] = j;
        }
    }
    bool repeatedSubstringPattern (string s) {
        if (s.size() == 0) {
            return false;
        }
        int next[s.size()];
        getNext(next, s);
        int len = s.size();
        if (next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0) {
            return true;
        }
        return false;
    }

最长回文子串 (真题)

Q:你一个字符串 s,找到 s 中最长的回文子串。

输入:s = "babad"
输出:"bab"

关键点:

  • 中心扩展(从一个中心点开始,找出从这个点能够形成回文的最长长度)
public String longestPalindrome(String s) {
    if (s == null || s.length() < 1) {
        return "";
    }
    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) {
        int len1 = expandAroundCenter(s, i, i);
        int len2 = expandAroundCenter(s, i, i + 1);
        int len = Math.max(len1, len2);
        if (len > end - start) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}
public int expandAroundCenter(String s, int left, int right) {
    while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
        --left;
        ++right;
    }
    return right - left - 1;
}

最小覆盖子串(真题)

Q:给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

关键点:

  • 滑动窗口
Map<Character, Integer> ori = new HashMap<Character, Integer>();
Map<Character, Integer> cnt = new HashMap<Character, Integer>();
public String minWindow(String s, String t) {
    int tLen = t.length();
    for (int i = 0; i < tLen; i++) {
        char c = t.charAt(i);
        ori.put(c, ori.getOrDefault(c, 0) + 1);
    }
    int l = 0, r = -1;
    int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
    int sLen = s.length();
    while (r < sLen) {
        ++r;
        if (r < sLen && ori.containsKey(s.charAt(r))) {
            cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
        }
        while (check() && l <= r) {
            if (r - l + 1 < len) {
                len = r - l + 1;
                ansL = l;
                ansR = l + len;
            }
            if (ori.containsKey(s.charAt(l))) {
                cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
            }
            ++l;
        }
    }
    return ansL == -1 ? "" : s.substring(ansL, ansR);
}
public boolean check() {
    Iterator iter = ori.entrySet().iterator(); 
    while (iter.hasNext()) { 
        Map.Entry entry = (Map.Entry) iter.next(); 
        Character key = (Character) entry.getKey(); 
        Integer val = (Integer) entry.getValue(); 
        if (cnt.getOrDefault(key, 0) < val) {
            return false;
        }
    } 
    return true;
}