CS-Notes全部笔记在阅读器中打开

Leetcode 题解 - 字符串

本文目录

1. 字符串循环移位包含

《编程之美》3.1

s1 = AABCD, s2 = CDAA
Return : true

给定两个字符串 s1 和 s2,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。

s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。需要注意的是,循环移位不改变长度,还要先判断 s2 不比 s1 长,例如 s2 = ABA 是 s1s1 = ABAB 的子串,却不可能被 AB 的循环移位包含。

2. 字符串循环移位

《编程之美》2.17

s = "abcd123" k = 3
Return "123abcd"

将字符串向右循环移动 k 位。

将 abcd123 中的 abcd 和 123 单独翻转,得到 dcba321,然后对整个字符串进行翻转,得到 123abcd。

3. 字符串中单词的翻转

《程序员代码面试指南》

s = "I am a student"
Return "student a am I"

将每个单词翻转,然后将整个字符串翻转。

4. 两个字符串包含的字符是否完全相同

242. Valid Anagram (Easy)

Leetcode / 力扣

s = "anagram", t = "nagaram", return true.
s = "rat", t = "car", return false.

可以用 HashMap 来映射字符与出现次数,然后比较两个字符串出现的字符数量是否相同。

由于本题的字符串只包含 26 个小写字符,因此可以使用长度为 26 的整型数组对字符串出现的字符进行统计,不再使用 HashMap。

public boolean isAnagram(String s, String t) {
    int[] cnts = new int[26];
    for (char c : s.toCharArray()) {
        cnts[c - 'a']++;
    }
    for (char c : t.toCharArray()) {
        cnts[c - 'a']--;
    }
    for (int cnt : cnts) {
        if (cnt != 0) {
            return false;
        }
    }
    return true;
}

5. 计算一组字符集合可以组成的回文字符串的最大长度

409. Longest Palindrome (Easy)

Leetcode / 力扣

Input : "abccccdd"
Output : 7
Explanation : One longest palindrome that can be built is "dccaccd", whose length is 7.

使用长度为 256 的整型数组来统计每个字符出现的个数,每个字符有偶数个可以用来构成回文字符串。

因为回文字符串最中间的那个字符可以单独出现,所以如果有单独的字符就把它放到最中间。

public int longestPalindrome(String s) {
    int[] cnts = new int[256];
    for (char c : s.toCharArray()) {
        cnts[c]++;
    }
    int palindrome = 0;
    for (int cnt : cnts) {
        palindrome += (cnt / 2) * 2;
    }
    if (palindrome < s.length()) {
        palindrome++;   // 这个条件下 s 中一定有单个未使用的字符存在,可以把这个字符放到回文的最中间
    }
    return palindrome;
}

6. 字符串同构

205. Isomorphic Strings (Easy)

Leetcode / 力扣

Given "egg", "add", return true.
Given "foo", "bar", return false.
Given "paper", "title", return true.

记录一个字符上次出现的位置,如果两个字符串中的字符上次出现的位置一样,那么就属于同构。代码中存的是 i + 1,用数组初值 0 表示“还没出现过”;如果存 i,第一次出现在位置 0 的字符会和没出现过的字符混淆,例如 "ab" 和 "aa" 会被误判为同构。

public boolean isIsomorphic(String s, String t) {
    int[] preIndexOfS = new int[256];
    int[] preIndexOfT = new int[256];
    for (int i = 0; i < s.length(); i++) {
        char sc = s.charAt(i), tc = t.charAt(i);
        if (preIndexOfS[sc] != preIndexOfT[tc]) {
            return false;
        }
        preIndexOfS[sc] = i + 1;
        preIndexOfT[tc] = i + 1;
    }
    return true;
}

7. 回文子字符串个数

647. Palindromic Substrings (Medium)

Leetcode / 力扣

Input: "aaa"
Output: 6
Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa".

每个回文串都有一个中心:长度为奇数时是一个字符,长度为偶数时是两个相邻字符之间。长度为 N 的字符串共有 2N - 1 个中心,从每个中心向两边扩展,两端字符相等就多得到一个回文子串,不相等就停止。例如 "aaa" 以中间的 a 为中心,依次得到 "a" 和 "aaa"。

private int cnt = 0;

public int countSubstrings(String s) {
    for (int i = 0; i < s.length(); i++) {
        extendSubstrings(s, i, i);     // 奇数长度
        extendSubstrings(s, i, i + 1); // 偶数长度
    }
    return cnt;
}

private void extendSubstrings(String s, int start, int end) {
    while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) {
        start--;
        end++;
        cnt++;
    }
}

8. 判断一个整数是否是回文数

9. Palindrome Number (Easy)

Leetcode / 力扣

进阶要求不将整数转换为字符串进行判断。

将整数分成左右两部分,右边那部分需要转置,然后判断这两部分是否相等。每次把 x 的最后一位移到 right 上,直到 right 不小于 x,这时已经处理了一半的位数。例如 12321 得到 x = 12、right = 123,位数为奇数,比较 x 和 right / 10。末尾为 0 的正数不可能是回文数,需要先排除,否则 10 会得到 x = 0、right = 1,x 等于 right / 10,被误判为回文数。

public boolean isPalindrome(int x) {
    if (x == 0) {
        return true;
    }
    if (x < 0 || x % 10 == 0) {
        return false;
    }
    int right = 0;
    while (x > right) {
        right = right * 10 + x % 10;
        x /= 10;
    }
    return x == right || x == right / 10;
}

9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数

696. Count Binary Substrings (Easy)

Leetcode / 力扣

Input: "00110011"
Output: 6
Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".

满足条件的子串一定跨过一个 0 和 1 的交界,左半边全是一种字符,右半边全是另一种。相邻的两组连续字符长度为 preLen 和 curLen 时,交界处能组成 min(preLen, curLen) 个子串,例如 "000" 和 "11" 之间是 "01" 和 "0011"。

代码在遍历时边走边计数:当前这组每多一个字符,只要前一组还有足够的字符与它配对(preLen >= curLen),就多一个子串。

public int countBinarySubstrings(String s) {
    int preLen = 0, curLen = 1, count = 0;
    for (int i = 1; i < s.length(); i++) {
        if (s.charAt(i) == s.charAt(i - 1)) {
            curLen++;
        } else {
            preLen = curLen;
            curLen = 1;
        }

        if (preLen >= curLen) {
            count++;
        }
    }
    return count;
}

10. 最长回文子串

5. Longest Palindromic Substring (Medium)

Leetcode / 力扣

Input: s = "babad"
Output: "bab"("aba" 也是正确答案)

和回文子字符串个数一样从中心扩展:回文串的中心可能是一个字符,也可能是两个字符之间,以每个位置为中心分别向两边扩展,记录最长的回文。时间复杂度为 O(N2),空间复杂度为 O(1)。

private int start = 0, maxLen = 0;

public String longestPalindrome(String s) {
    for (int i = 0; i < s.length(); i++) {
        extend(s, i, i);     // 奇数长度
        extend(s, i, i + 1); // 偶数长度
    }
    return s.substring(start, start + maxLen);
}

private void extend(String s, int l, int r) {
    while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
        l--;
        r++;
    }
    if (r - l - 1 > maxLen) {
        maxLen = r - l - 1;
        start = l + 1;
    }
}

11. 字符串相乘

43. Multiply Strings (Medium)

Leetcode / 力扣

Input: num1 = "123", num2 = "45"
Output: "5535"

题目描述:以字符串形式给出两个非负整数,返回它们的乘积,不能把字符串直接转换为整数。

模拟竖式乘法。乘积最多有 m + n 位,用数组 digits 按从高位到低位的顺序保存。num1[i] 与 num2[j] 相乘的结果落在 digits[i + j + 1] 上,先把所有乘积累加到对应位置,最后从低位到高位统一处理进位。例如 123 × 45 中,个位相乘的 3 × 5 落在 digits[2 + 1 + 1] = digits[4],也就是乘积的最低位。

public String multiply(String num1, String num2) {
    int m = num1.length(), n = num2.length();
    int[] digits = new int[m + n];
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            digits[i + j + 1] += (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
        }
    }
    for (int k = m + n - 1; k > 0; k--) {
        digits[k - 1] += digits[k] / 10;
        digits[k] %= 10;
    }
    int k = 0;
    while (k < m + n - 1 && digits[k] == 0) {
        k++;
    }
    StringBuilder sb = new StringBuilder();
    for (; k < m + n; k++) {
        sb.append(digits[k]);
    }
    return sb.toString();
}

12. 比较版本号

165. Compare Version Numbers (Medium)

Leetcode / 力扣

Input: version1 = "1.01", version2 = "1.001"
Output: 0
Input: version1 = "1.0.1", version2 = "1"
Output: 1

题目描述:版本号由 . 分隔的修订号组成,从左到右依次比较修订号的整数值,前导 0 忽略,缺少的修订号视为 0。

用两个指针分别扫描两个字符串,每次各解析出一段修订号的整数值进行比较,不需要先把字符串分割成数组。

public int compareVersion(String version1, String version2) {
    int i = 0, j = 0;
    while (i < version1.length() || j < version2.length()) {
        int x = 0, y = 0;
        while (i < version1.length() && version1.charAt(i) != '.') {
            x = x * 10 + version1.charAt(i++) - '0';
        }
        while (j < version2.length() && version2.charAt(j) != '.') {
            y = y * 10 + version2.charAt(j++) - '0';
        }
        if (x != y) {
            return x > y ? 1 : -1;
        }
        i++;
        j++;
    }
    return 0;
}

参考资料

在阅读器中可以用目录和搜索、切换深色模式、练习这篇笔记中的面试题:打开《Leetcode 题解 - 字符串》