Leetcode复盘5——字符串
导读
1.有效的字母异位词 / 两个字符串包含的字符是否完全相同(Leetcode242)
难度:简单Easy
idea: 哈希表(HashMap) / 字典(dict)
用字典来存储每个单词出现的次数,最后看两个字符串相同字母出现的次数是否一样,或者因为只有26个字母,故可以用一个长度为26的字符串来记录每个字母出现的次数,分别扫描两个字符串,一个用来加,另一个用来减,最后再检查一遍是否每个字母出现次数都为0了
代码:
Java版
class Solution {
public boolean isAnagram(String s, String t) {
int[] cnts = new int[26]; // 左边一定要加[],即int[],不然是把int[26]这个数组赋给整数型int了
for(char c: s.toCharArray()) { // 把字符串s转换成字符串数组,然后用for循环去取
cnts[c - 'a']++; // 计算字符c和字母a之间的差距,然后在cnts对应位置+1
}
for(char c: t.toCharArray()) {
cnts[c - 'a']--;
}
for(int cnt : cnts) { // cnts里面存的是每个字母出现的次数
if(cnt != 0) {
return false;
}
}
return true;
}
}
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
dic = {} # 把鼠标放到dic={}上,会显示 (variable) dic: dict[Any, Any]
# 1.加
for char in s: # char在Python中不是关键字,在Java中是
if char not in dic:
dic[char] = 1
else:
dic[char] += 1
# 2.减
for char in t:
if char not in dic:
return False
else:
dic[char] -= 1
# 3.检查是否为0
for cnt in dic.values(): # 遍历每个key的value
if cnt != 0:
return False
return True
2.最长回文串 / 计算一组字符集合可以组成的回文字符串的最大长度(Leetcode409)
难度:简单Easy
idea: 哈希表(HashMap)
利用类似于哈希表的方法,建立一个长度为58的数组,用来存储各个字母出现的次数(哈希表也能存次数,但是此题数组就能解决了),因为A的ACSII码为65,z的ACSII码为122,还是用[c - ‘A’]这个老方法统计完次数后开始用for循环看每一个次数,当次数为偶数时,不管为多少次都可以直接用,但当次数为奇数时,需要减掉1次,因为多出来一个;但是有一个奇数字母例外,它可以把多出来的那个字母放在中间,只有一个奇数字母可以这么搞.最后总次数+1
代码
C++版
class Solution {
public:
int longestPalindrome(string s) {
int cnts[52] = {0};
for(string::size_type i = 0; i < s.size(); i++) {
if('a' <= s[i] && s[i] <= 'z') { // C++就可以用s[i]直接取,不用转换
cnts[s[i] - 'a']++;
} else {
cnts[s[i] - 'A' + 26]++; // 大写字母要放到大写的对应次数那里,故要+26
}
}
int res = 0; // 用来记录字母总和
for(int i = 0; i < 52; i++) {
// 知识点: 当x为偶数(对应二进制末位为0)时,x&1=0; 当x为奇数时(对应二进制末位为1),x&1=1;
if((res & 1) && (cnts[i] & 1)) { // 当且仅当res已经为奇数了(证明已经有一个放在正中间了)且当前字母cnts[i]还是为奇数,则需要-1再放到最终结果里,因为至多只能有一个奇数字母.
res += cnts[i] - 1;
} else {
res += cnts[i];
}
}
return res;
}
};
Java版
class Solution {
public int longestPalindrome(String s) {
int[] cnts = new int[58]; // 因为区分大小写,A的ACSII码为65,z的ACSII码为122
for(char c : s.toCharArray()) { // Java麻烦就麻烦在没办法用for直接取字符串,须转换
cnts[c - 'A']++;
}
int res = 0;
for(int cnt : cnts) {
// 看一下每个字符出现的次数,最多用偶数次,比如2,4,6,8...都可以用,奇数次要-1,因为多了一个,例如3,5,7...但有一种特殊情况,当有好多个字母出现奇数次时,其中有一个是可以用的,把它放到最中间.
res += cnt - (cnt & 1); // 当cnt为偶数时,cnt&1=0; 当cnt为奇数时,cnt&1=1
}
// 如果最终的长度小于原字符串的长度,说明里面至少有一个字符出现了奇数次,那么那个字符可以放在回文串的中间,所以额外再加一
return res < s.length() ? res + 1 : res;
}
}
3.同构字符串 / 字符串同构(LeetCode205)
难度:简单Easy
idea: 哈希表(HashMap)
利用一个 map 来处理映射,对于s到t的映射,我们同时遍历s和t,假设当前遇到的字母分别是c1和c2,分两步:
如果map[c1]不存在,那么就将c1映射到c2,即map[c1]=c2;
如果map[c1]存在,那么就判断map[c1]是否等于c2,也就是验证之前的映射和当前的字母是否相同.
需要遍历两边,即s->t和t->s都需要遍历,
e.g:
s = ab, t = cc
第一遍遍历: a->c, b->c没问题;
第二遍遍历: c->a, c->b就有问题了;
代码:
Java版
class Solution {
public boolean isIsomorphic(String s, String t) {
return isIsomorphicHelper(s, t) && isIsomorphicHelper(t, s); // 须遍历两遍,故定义一个helper函数
}
private boolean isIsomorphicHelper(String s, String t) {
int n = s.length();
HashMap<Character, Character> map = new HashMap<>(); // Java定义就是麻烦,每次都new
for (int i = 0; i < n; i++) {
char c1 = s.charAt(i); // 字符串取字母,用charAt()
char c2 = t.charAt(i);
if (map.containsKey(c1)) { // 当map中已经存在该key了
if (map.get(c1) != c2) { // 若get取到的value和当前不一致
return false;
}
} else {
map.put(c1, c2);
}
}
return true;
}
}
idea: 哈希表(HashMap) / 字典(dictionary)
跟Java法不同,这次不扫描两遍了,把每组映射都转换成数字,例如 a->1, b->1, g->2, f->2,对于当前的两个字母,看它们之前是否出现过,若一个出现过,一个没出现则肯定不是同构,
e.g
s = ab, t = cc
第一轮: a->1, c->1;
第二轮此时: b -> 0(初始), c -> 1,就不同了,返回false
Python版
from collections import defaultdict
class Solution:
def isIsomorphic(self, s: str, t: str) -> bool:
counter_1 = defaultdict(int)
counter_2 = defaultdict(int)
for i in range(len(s)):
c1,c2 = s[i], t[i]
if counter_1[c1] != counter_2[c2]: # 若其中至少有一个出现过了,值肯定不是0了
return False
else:
# 是否已经建立过一对一映射,建立过就不需要再处理;
if counter_1[c1] == 0: # 其实这句话也可以不加,大不了两个已经存在映射value值都+1呗
counter_1[c1] = i + 1
counter_2[c2] = i + 1
return True
4.回文子串 / 回文子字符串个数(LeetCode647)
难度:简单Easy
idea: 中心扩展法(extendPalindrome)
以center为中心左右扩展,一共有2length-1个center,分别是length个单字符center(例如a)和length-1个双字符center(例如ab)
代码:
C++版
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
int count = 0; // 虽然count是函数内定义的,但是也把它放到palindromic函数里(记得加取地址符号&来引用,不然就是复制了),所以count是跟着变化的
for(int i = 0; i < n; i++) {
palindromic(s, i, i, count); // 1个中心点,奇数子串
palindromic(s, i, i + 1, count); // 2个中心点,偶数子串
}
return count;
}
private:
void palindromic(string s, int left, int right, int& count) { // 看以i为中心点的回文子串有几个
while(left >=0 && right < s.size() && s[left] == s[right]) {
count++;
left--;
right++;
}
}
};
Java版
class Solution {
int count = 0; // 全局变量count,在所有函数外定义
public int countSubstrings(String s) {
if(s == null || s.length() == 0) return 0;
for(int i = 0; i < s.length(); i++) { // i为中心点,即center
extendPalindrome(s, i, i); // 中心点为1个字母
extendPalindrome(s, i, i + 1); // 中心点为2个字母
}
return count;
}
private void extendPalindrome(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { // 字符串型取长度length要加括号
count++;
left--;
right++;
}
}
}
5.回文数 / 判断一个整数是否是回文数(LeetCode9)
idea: 数学解法(math)
通过取整(取头部的数字)和取余(取尾部的数字)操作获取整数中对应的数字进行比较
e.g num = “1221”
1221 / 1000得首位1, 1221 % 10得末位1;
1221 / 100得百位2, 1221 % 100得十位2;
代码
Java版
class Solution {
public boolean isPalindrome(int x) {
// 边界判断
if(x < 0)
return false;
// 先求最高位,如果x除以div大于10的话,div再翻10倍,直到x/div为个位,此时即最高位
int div = 1;
while(x / div >= 10) {
div *= 10;
}
// 此时div已经可以得到x的最高位了,开始比较
while(x > 0) {
int left = x / div;
int right = x % 10;
if(left != right) return false;
// x也需要变化,由1221变成22:
x = (x % div) / 10; // %div的目的是得到除最高位以外的数,/10的目的是去掉最低位;
div /= 100;
}
return true;
}
}
6.计数二进制子串 / 循环数组中比当前元素大的下一个元素(LeetCode696)
idea: 字符串
每当遇到一个新的数字时(新的字符c不等于最后一个字符last了),
之前数字pre和新数字之前的cur都定下来了,统计二者的较小值,再把cur数字个数赋给pre,因为接下来cur换成新数字了,故pre也变成上一个数字了.cur从1开始记(即记新的数字个数了)
e.g: 00011101
第一轮: c=0,last!=c,last=0,ans=0.cnt_pre=0,cnt_cur=1;
第二轮: c=0,lastc,cnt_cur=2;
第三轮: c=0,lastc,cnt_cur=3;
第四轮: c=1,last!=c,last=1,ans=0(之前有0个空,当前有3个0).cnt_pre=3(即0的个数记为3),cnt_cur=1;
第五轮: c=1,last!=c,cnt_cur=2;
第六轮: c=1,last!=c,cnt_cur=3;
第七轮: c=0,last!=c,last=0,ans=3(之前有3个0,当前有3个1).cnt_pre=3(即1的个数记为3),cnt_cur=1;
第八轮: c=1,last!=c,last=1,ans=3+1=4(之前有3个1,当前有1个3).cnt_pre=1(即0的个数记为1),cnt_cur=1;
第九轮: c=-,last!=c,last=-,ans=4+1=5(之前有1个0,当前有1个1).cnt_pre=1(即1的个数记为1),cnt_cur=1;
代码:
C++版
class Solution {
public:
int countBinarySubstrings(string s) {
int res = 0;
char last = '-';
int cnt_pre = 0;
int cnt_cur = 0;
s += '-';
for (auto c : s) {
if (last != c) {
last = c;
res += min(cnt_pre, cnt_cur); // 注意可能会出现多个01,但是位置不同,也算不同个
cnt_pre = cnt_cur;
cnt_cur = 0;
}
cnt_cur++;
}
return res;
}
};
idea: 字符串
思路跟上一个类似,下面是具体执行过程
e.g: 00011101
第一轮: n = 0, pre = 0, curr = 1, s[0] = s[1], curr = 2;
第二轮: n = 0, pre = 0, curr = 2, s[1] = s[2], curr = 3;
第三轮: n = 0, pre = 0, curr = 3, s[2] != s3, pre = 3(此时指3个0); curr = 1, (pre >= curr), n = 1(3个0,1个1,可以凑1个了)
第四轮: n = 1, pre = 3, curr = 1, s[3] = s[4], curr = 2, (pre >= curr), n = 2(3个0,2个1,可以凑2个了)
第五轮: n = 2, pre = 3, curr = 2, s[4] = s[5], curr = 3; (pre >= curr), n = 3(3个0,3个1,可以凑3个了)
第六轮: n = 3, pre = 3, curr = 3, s[5] != s6, pre = 3(此时指3个1), curr = 1, (pre >= curr),n = 4(3个1,1个0,可以凑1个了)
第七轮: n = 4, pre = 3, curr = 1, s[6] != s7, pre = 1(此时指1个0), curr = 1, (pre >= curr),n = 5(1个0,1个1,可以凑1个了)
C++版
class Solution {
public:
int countBinarySubstrings(string s){ // s是字符串数组
int n = 0, pre = 0, curr = 1;
for (int i = 0; i < s.size() - 1; ++i) { // 比到倒数第二个,下标[s.size()-2]
if(s[i] == s[i + 1]) { // 若还跟上一个一样,curr就接着记
curr++;
} else { // 若不一样了,把curr给上一个,curr重新计
pre = curr;
curr = 1;
}
// 另起一个if语句
if(pre >= curr) { // 只要pre大于等于当前的计数器curr,就能多凑一个出来
n++;
}
}
return n;
}
};