文章目录
LeetCode精选题之字符串
参考资料:CyC2018的LeetCode题解
1 有效的字母异位词–LeetCode242
给定两个字符串 s
和 t
,编写一个函数来判断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
给定两个字符串 s
和 t
,判断它们是否是同构的。
同构的含义:如果 s
中的字符可以被替换得到 t
,那么这两个字符串是同构的。
所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。
示例 1:
输入: s = "egg", t = "add"
输出: true
示例 2:
输入: s = "foo", t = "bar"
输出: false
示例 3:
输入: s = "paper", t = "title"
输出: true
说明:你可以假设 s
和 t
具有相同的长度。
思路:两个字符串同构的含义就是字符串 s
可以唯一的映射到 t
,同时 t
也可以唯一的映射到 s
。我们需要验证 s -> t
和 t -> 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
状态转移方程的含义:
- 当只有一个字符时,比如
a
自然是一个回文串。 - 当有两个字符时,如果是相等的,比如
aa
,也是一个回文串。 - 当有三个及以上字符时,比如
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;
}
}