代码随想录拓展day4 205. 同构字符串;1002. 查找常用字符;925.长按键入;844.比较含退格的字符串
哈希表和字符串的一些应用,放到一起了。
同构字符串
https://leetcode.cn/problems/isomorphic-strings/description/
刚开始以为一个map就够用了。但是遇到一个测试样例,s = “badc” 和t = “baba”。题目要求不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上因为这个要求,一个map就不够用了,所以还需要一个从
t到s的map映射。
思路
字符串没有说都是小写字母之类的,所以用数组不合适了,用map来做映射。
使用两个map 保存 s[i] 到 t[j] 和 t[j] 到 s[i] 的映射关系,如果发现对应不上,立刻返回 false
C++代码 如下:
class Solution {
public:
bool isIsomorphic(string s, string t) {
unordered_map<char, char> map1;
unordered_map<char, char> map2;
for (int i = 0, j = 0; i < s.size(); i++, j++) {
if (map1.find(s[i]) == map1.end()) { // map1保存s[i] 到 t[j]的映射
map1[s[i]] = t[j];
}
if (map2.find(t[j]) == map2.end()) { // map2保存t[j] 到 s[i]的映射
map2[t[j]] = s[i];
}
// 发现映射 对应不上,立刻返回false
if (map1[s[i]] != t[j] || map2[t[j]] != s[i]) {
return false;
}
}
return true;
}
};
1002. 查找常用字符
https://leetcode.cn/problems/find-common-characters/
关键是如果一个字母在几个单词中都出现了多次,这个也要区分记录。所以记录每个字母的最小出现次数。当时写的时候想用一个数组就完成,但是具体无法实现,看了解答其实是有两个数组来完成了。
思路
这道题意一起就有点绕,不是那么容易懂,其实就是26个小写字符中有字符 在所有字符串里都出现的话,就输出,重复的也算。
例如:
输入:[“ll”,“ll”,“ll”] 输出:[“l”,“l”]
这道题目一眼看上去,就是用哈希法,“小写字符”,“出现频率”, 这些关键字都是为哈希法量身定做的啊
首先可以想到的是暴力解法,一个字符串一个字符串去搜,时间复杂度是 O ( n m ) O(n^m) O(nm),n是字符串长度,m是有几个字符串。
可以看出这是指数级别的时间复杂度,非常高,而且代码实现也不容易,因为要统计 重复的字符,还要适当的替换或者去重。
了解了哈希法,理解了数组在哈希法中的应用之后,可以来看解题思路了。
整体思路就是统计出搜索字符串里26个字符的出现的频率,然后取每个字符频率最小值,最后转成输出格式就可以了。
如图:
先统计第一个字符串所有字符出现的次数,代码如下:
int hash[26] = {0}; // 用来统计所有字符串里字符出现的最小频率
for (int i = 0; i < A[0].size(); i++) { // 用第一个字符串给hash初始化
hash[A[0][i] - 'a']++;
}
接下来,把其他字符串里字符的出现次数也统计出来一次放在hashOtherStr中。
然后hash 和 hashOtherStr 取最小值,这是本题关键所在,此时取最小值,就是 一个字符在所有字符串里出现的最小次数了。
代码如下:
int hashOtherStr[26] = {0}; // 统计除第一个字符串外字符的出现频率
for (int i = 1; i < A.size(); i++) {
memset(hashOtherStr, 0, 26 * sizeof(int));
for (int j = 0; j < A[i].size(); j++) {
hashOtherStr[A[i][j] - 'a']++;
}
// 这是关键所在
for (int k = 0; k < 26; k++) { // 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数
hash[k] = min(hash[k], hashOtherStr[k]);
}
}
此时hash里统计着字符在所有字符串里出现的最小次数,那么把hash转成题目要求的输出格式就可以了。
代码如下:
// 将hash统计的字符次数,转成输出形式
for (int i = 0; i < 26; i++) {
while (hash[i] != 0) { // 注意这里是while,多个重复的字符
string s(1, i + 'a'); // char -> string
result.push_back(s);
hash[i]--;
}
}
整体C++代码如下:
class Solution {
public:
vector<string> commonChars(vector<string>& A) {
vector<string> result;
if (A.size() == 0) return result;
int hash[26] = {0}; // 用来统计所有字符串里字符出现的最小频率
for (int i = 0; i < A[0].size(); i++) { // 用第一个字符串给hash初始化
hash[A[0][i] - 'a']++;
}
int hashOtherStr[26] = {0}; // 统计除第一个字符串外字符的出现频率
for (int i = 1; i < A.size(); i++) {
memset(hashOtherStr, 0, 26 * sizeof(int));
for (int j = 0; j < A[i].size(); j++) {
hashOtherStr[A[i][j] - 'a']++;
}
// 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数
for (int k = 0; k < 26; k++) {
hash[k] = min(hash[k], hashOtherStr[k]);
}
}
// 将hash统计的字符次数,转成输出形式
for (int i = 0; i < 26; i++) {
while (hash[i] != 0) { // 注意这里是while,多个重复的字符
string s(1, i + 'a'); // char -> string
result.push_back(s);
hash[i]--;
}
}
return result;
}
};
925.长按键入
https://leetcode.cn/problems/long-pressed-name/
刚开始以为是绑架信那种,用哈希表的方法,但是这道题的关键是顺序,所以就不能用哈希表了。看解答是用了同时遍历两个数组的方法。注意判读边界条件,就是在终止之后name或typed长出去的部分要怎么处理。
思路
这道题目一看以为是哈希,仔细一看不行,要有顺序。
所以模拟同时遍历两个数组,进行对比就可以了。
对比的时候需要一下几点:
- name[i] 和 typed[j]相同,则i++,j++ (继续向后对比)
- name[i] 和 typed[j]不相同
- 看是不是第一位就不相同了,也就是j如果等于0,那么直接返回false
- 不是第一位不相同,就让j跨越重复项,移动到重复项之后的位置,再次比较name[i] 和typed[j]
- 如果 name[i] 和 typed[j]相同,则i++,j++ (继续向后对比)
- 不相同,返回false
- 对比完之后有两种情况
- name没有匹配完,例如name:“pyplrzzzzdsfa” type:“ppyypllr”
- type没有匹配完,例如name:“alex” type:“alexxrrrrssda”
动画如下:
上面的逻辑想清楚了,不难写出如下C++代码:
class Solution {
public:
bool isLongPressedName(string name, string typed) {
int i = 0, j = 0;
while (i < name.size() && j < typed.size()) {
if (name[i] == typed[j]) { // 相同则同时向后匹配
j++; i++;
} else { // 不相同
if (j == 0) return false; // 如果是第一位就不相同直接返回false
// j跨越重复项,向后移动,同时防止j越界
while(j < typed.size() && typed[j] == typed[j - 1]) j++;
if (name[i] == typed[j]) { // j跨越重复项之后再次和name[i]匹配
j++; i++; // 相同则同时向后匹配
}
else return false;
}
}
// 说明name没有匹配完,例如 name:"pyplrzzzzdsfa" type:"ppyypllr"
if (i < name.size()) return false;
// 说明type没有匹配完,例如 name:"alex" type:"alexxrrrrssda"
while (j < typed.size()) {
if (typed[j] == typed[j - 1]) j++;
else return false;
}
return true;
}
};
时间复杂度:O(n) 空间复杂度:O(1)
844.比较含退格的字符串
https://leetcode.cn/problems/backspace-string-compare/
用栈来模拟删除的过程。可以看到用字符串直接充当栈结构,比单独用stack栈方便了很多。另外双指针法比较绕,先做个记录吧。
思路
本文将给出 空间复杂度O(n)的栈模拟方法 以及空间复杂度是O(1)的双指针方法。
普通方法(使用栈的思路)
这道题目一看就是要使用栈的节奏,这种匹配(消除)问题也是栈的擅长所在。
那么本题,确实可以使用栈的思路,但是没有必要使用栈,因为最后比较的时候还要比较栈里的元素,有点麻烦。
这里直接使用字符串string,来作为栈,末尾添加和弹出,string都有相应的接口,最后比较的时候,只要比较两个字符串就可以了,比比较栈里的元素方便一些。
代码如下:
class Solution {
public:
bool backspaceCompare(string S, string T) {
string s; // 当栈来用
string t; // 当栈来用
for (int i = 0; i < S.size(); i++) {
if (S[i] != '#') s += S[i];
else if (!s.empty()) {
s.pop_back();
}
for (int i = 0; i < T.size(); i++) {
if (T[i] != '#') t += T[i];
else if (!t.empty()) {
t.pop_back();
}
}
if (s == t) return true; // 直接比较两个字符串是否相等,比用栈来比较方便多了
return false;
}
};
- 时间复杂度:O(n + m),n为S的长度,m为T的长度 ,也可以理解是O(n)的时间复杂度
- 空间复杂度:O(n + m)
优化方法(从后向前双指针)
当然还可以有使用 O(1) 的空间复杂度来解决该问题。
同时从后向前遍历S和T(i初始为S末尾,j初始为T末尾),记录#的数量,模拟消除的操作,如果#用完了,就开始比较S[i]和S[j]。
动画如下:
如果S[i]和S[j]不相同返回false,如果有一个指针(i或者j)先走到的字符串头部位置,也返回false。
代码如下:
class Solution {
public:
bool backspaceCompare(string S, string T) {
int sSkipNum = 0; // 记录S的#数量
int tSkipNum = 0; // 记录T的#数量
int i = S.size() - 1;
int j = T.size() - 1;
while (1) {
while (i >= 0) { // 从后向前,消除S的#
if (S[i] == '#') sSkipNum++;
else {
if (sSkipNum > 0) sSkipNum--;
else break;
}
i--;
}
while (j >= 0) { // 从后向前,消除T的#
if (T[j] == '#') tSkipNum++;
else {
if (tSkipNum > 0) tSkipNum--;
else break;
}
j--;
}
// 后半部分#消除完了,接下来比较S[i] != T[j]
if (i < 0 || j < 0) break; // S 或者T 遍历到头了
if (S[i] != T[j]) return false;
i--;j--;
}
// 说明S和T同时遍历完毕
if (i == -1 && j == -1) return true;
return false;
}
};
- 时间复杂度:O(n + m)
- 空间复杂度:O(1)