LeetCode精选题之字符串

LeetCode精选题之字符串

参考资料:CyC2018的LeetCode题解

1 有效的字母异位词–LeetCode242

给定两个字符串 st,编写一个函数来判断t是否是 s的字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

说明:你可以假设字符串只包含小写字母。

进阶:如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

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

对于进阶的思考:使用哈希表而不是固定大小的数组,因为哈希表的大小可以根据输入字符串的长度进行动态调整。

2 最长回文串–LeetCode409

给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。

在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。

注意:
假设字符串的长度不会超过 1010。

示例 1:

输入:"abccccdd"
输出:7
解释:我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
class Solution {
    public int longestPalindrome(String s) {
        HashMap<Character, Integer> map = new HashMap<>();
        for (char ch : s.toCharArray()) {
            if (!map.containsKey(ch)) {
                map.put(ch, 1);
            }else {
                map.put(ch, 1+map.get(ch));
            }
        }
        int odd = 0;// 是否有出现奇数次的字符
        int res = 0;
        for (char key : map.keySet()) {
            int cnt = map.get(key);
            if ((cnt&1) == 1) {// cnt是奇数
                odd = 1;
                res += (cnt-1);
            }else {// 否则cnt是偶数
                res += cnt;
            }
        }
        return res+odd;
    }
}

注:如果不用Map的话,也可以用长度为58的数组,为什么长度是58?解释:(26个大写字母)+ (26个小写字母)+ (ASCII表中,大写Z 和 小写a之间有6个特殊字符) = 58。

3 同构字符串–LeetCode205

给定两个字符串 st,判断它们是否是同构的。

同构的含义:如果 s中的字符可以被替换得到 t,那么这两个字符串是同构的。

所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。

示例 1:

输入: s = "egg", t = "add"
输出: true

示例 2:

输入: s = "foo", t = "bar"
输出: false

示例 3:

输入: s = "paper", t = "title"
输出: true

说明:你可以假设 st具有相同的长度。

思路:两个字符串同构的含义就是字符串 s可以唯一的映射到 t,同时 t也可以唯一的映射到 s。我们需要验证 s -> tt -> s两个方向的映射。

class Solution {
    public boolean isIsomorphic(String s, String t) {
        return isIsomorphicHelper(s, t) && isIsomorphicHelper(t, s);
    }

    private boolean isIsomorphicHelper(String s, String t) {
        int len = s.length();
        HashMap<Character, Character> map = new HashMap<>();
        for (int i = 0; i < len; i++) {
            char c1 = s.charAt(i);
            char c2 = t.charAt(i);
            if (map.containsKey(c1)) {
                if (map.get(c1) != c2) {
                    return false;
                }
            }else {
                map.put(c1, c2);
            }
        }
        return true;
    }
}

思路二:将两个字符串分别翻译成第三种类型即可。我们可以定义一个变量 count = 1,映射给出现的字母,然后进行自增。

这种转换的思想很重要,生活中的例子:一个人说中文,一个人说法语,怎么判断他们说的是一个意思呢?把中文翻译成英语,把法语也翻译成英语,然后看最后的英语是否相同即可。

举例如下:

将第一个出现的字母映射成 1,第二个出现的字母映射成 2

对于 egg
e -> 1
g -> 2
也就是将 egg 的 e 换成 1, g 换成 2, 就变成了 122

对于 add
a -> 1
d -> 2
也就是将 add 的 a 换成 1, d 换成 2, 就变成了 122

egg -> 122, add -> 122
都变成了 122,所以两个字符串异构。
class Solution {
    public boolean isIsomorphic(String s, String t) {
        return isIsomorphicHelper(s).equals(isIsomorphicHelper(t));
    }

    private String isIsomorphicHelper(String s) {
        int[] map = new int[128];
        StringBuilder sb = new StringBuilder();
        int count = 1;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 如果是第一次出现
            if (map[c] == 0) {
                map[c] = count;
                count++;
            }
            sb.append(map[c]);
        }
        return sb.toString();
    }
}

参考题解:windliang的题解

4 回文子串–LeetCode647(Medium)

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。

示例 1:

输入: "abc"
输出: 3
解释: 三个回文子串: "a", "b", "c".

示例 2:

输入: "aaa"
输出: 6
说明: 6个回文子串: "a", "a", "a", "aa", "aa", "aaa".

注意:输入的字符串长度不会超过1000。

暴力法:(用时586ms)

class Solution {
    public int countSubstrings(String s) {
        int res = 0;
        int len = s.length();
        for (int i = 0; i < len; i++) {
            for (int j = i; j < len; j++) {
                if (isPalindrome(s, i, j)) {
                    res++;
                }
            }
        }
        return res;
    }

    private boolean isPalindrome(String s, int start, int end) {
        while (start < end) {
            if (s.charAt(start) != s.charAt(end)) {
                return false;
            }
            start++;
            end--;
        }
        return true;
    }
}

暴力法的改进:动态规划,思想是空间换时间。(用时13ms)

状态:dp[i][j]表示字符串s[i,j]区间的子串是否是一个回文串。
状态转移方程:当 s[i] == s[j] && (j - i < 2 || dp[i + 1][j - 1])时,dp[i][j]=true,否则为false

状态转移方程的含义:

  1. 当只有一个字符时,比如 a自然是一个回文串。
  2. 当有两个字符时,如果是相等的,比如 aa,也是一个回文串。
  3. 当有三个及以上字符时,比如ababa这个字符记作串1,把两边的a去掉,也就是bab记作串2,可以看出只要串2是一个回文串,那么左右各多了一个a的串1必定也是回文串。所以当s[i]==s[j]时,自然要看dp[i+1][j-1]是不是一个回文串。

参考思路:jawhiow

class Solution {
    public int countSubstrings(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        int len = s.length();
        int res = 0;
        boolean[][] dp = new boolean[len][len];
        for (int j = 0; j < len; j++) {
            for (int i = j; i >= 0; i--) {
                if (s.charAt(i)==s.charAt(j) 
                && (j-i < 2 || dp[i+1][j-1])) {
                    dp[i][j] = true;
                    res++;
                }
            }
        }
        return res;
    }
}

【补充】还有一种解法:中心扩展法,实质的思路和动态规划的思路类似。参考链接:jawhiow的题解

5 回文数–LeetCode9

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

进阶:你能不将整数转为字符串来解决这个问题吗?

class Solution {
    public boolean isPalindrome(int x) {
        String s = String.valueOf(x);
        int left = 0, right = s.length()-1;
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

不转换为字符串的解法:

class Solution {
    public boolean isPalindrome(int x) {
        if (x < 0) {
            return false;
        }
        int res = 0;
        int temp = x;
        while (temp != 0) {
            res = res*10 + temp%10;
            temp = temp/10;
        }
        return res == x;
    }
}

继续改进:实际上只需要将右边一半的数字转置即可,然后判断两部分是否相等,需要注意原数字的位数是奇数还是偶数。

class Solution {
    public boolean isPalindrome(int x) {
        if (x == 0) {
            return true;
        }
        // 因为最高位不可能为零,所以个位为零时为false
        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;
    }
}

6 计数二进制子串–LeetCode696

给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。

重复出现的子串要计算它们出现的次数。

示例 1 :

输入: "00110011"
输出: 6
解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。

请注意,一些重复出现的子串要计算它们出现的次数。

另外,“00110011”不是有效的子串,因为所有的0(和1)没有组合在一起。

示例 2 :

输入: "10101"
输出: 4
解释: 有4个子串:“10”,“01”,“10”,“01”,它们具有相同数量的连续1和0。

注意:

  • s.length 在1到50,000之间。
  • s 只包含“0”或“1”字符。

动态规划(超时)

class Solution {
    public int countBinarySubstrings(String s) {
        int len = s.length();
        boolean[][] dp = new boolean[len][len];
        int res  = 0;
        for (int j = 1; j < len; j++) {
            for (int i = j-1; i >= 0; i--) {
                if (s.charAt(i)!=s.charAt(j) 
                && (j-i<2 || (dp[i+1][j-1] && s.charAt(j)==s.charAt(j-1)))){
                    dp[i][j] = true;
                    res++;
                }
            }
        }
        return res;
    }
}

思路:假设最简单的情况 000111, 先遍历, 前面的 0 的数量为 curr = 3, 遍历到 1 时, pre = curr 赋值为 3, 然后 curr = 1 表示现在 1 的个数, 只要 curr <= pre, 比如 curr = 1, 那么可以组成 01; curr = 2, 可以组成 0011, curr = 3, 组成 000111, 直到出现下一次 0, 然后那么 prev 就是 1 的个数, curr 表示 0001110…这个 1 后面 0 的个数, 不断重复迭代下去。参考思路:Rocky的评论

class Solution {
    public int countBinarySubstrings(String s) {
        int res = 0;
        int pre = 0, cur = 1;
        for (int i = 0; i < s.length()-1; i++) {
            if (s.charAt(i) == s.charAt(i+1)) {
                cur++;
            }else {
                pre = cur;
                cur = 1;
            }
            if (pre >= cur) {
                res++;
            }
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值