LC 字符串-String专题
[声明]:ACWing Y总课程 总结
1.题目链接 — LeetCode38. 外观数列
代码如下
class Solution {
public:
string countAndSay(int n) {
string s = "1";
for(int i = 0; i < n-1; i++){
string ns; // 每轮都设置为空
for(int j = 0; j < s.size(); j++){
int k = j;
while(k < s.size() && s[k] == s[j]) k++; // k遍历连续相同的字符
// k停止在s[k]!=s[j]的地方
ns += to_string(k-j) + s[j]; // k-j个s[j]
j = k-1; // 因为会j++ 所以要-1
}
s = ns;
}
return s;
}
};
2.题目链接 — LeetCode49. 字母异位词分组
输入 / 输出样例
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
代码
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> hash;
// first记录排好序的同字符单词, second记录同字符单词的源单词
for(auto s : strs){
string key = s; // key为排序后的单词
sort(key.begin(), key.end());
hash[key].push_back(s); // key:aet str:eat
}
vector<vector<string>> ans;
for(auto it : hash){
ans.push_back(it.second);
}
return ans;
}
};
3.题目链接 — LeetCode151. 翻转字符串里的单词
输入 / 输出样例
输入:s = " hello world "
输出:"world hello"
方法一:翻转两次
class Solution {
public:
string reverseWords(string s) {
int k = 0; // k 为反转每一个单词时的index
for(int i = 0; i < s.size(); i++){
while(i < s.size() && s[i] == ' ') i++; // i 为除空格单词的首字母
if(i == s.size()) break; // 读到末尾 break
int j = i; // j为单词末尾
while(j < s.size() && s[j] != ' ') j++;
reverse(s.begin()+i ,s.begin()+j);
if(k != 0) s[k++] = ' '; // 非首单词反转后 + 一个空格
while(i < j) s[k++] = s[i++];
}
s.erase(s.begin() + k, s.end()); //前 k 个才是每个单词反转的结果,
// 所以把 k 之后的东西删掉
reverse(s.begin(), s.end()); // 最后把前 k 个反转单词再反转
return s;
}
};
方法二:处理多余空格(技巧:stringstream)
- 优点:方便简单,不需要额外处理多余空格
- 缺点:输入流缓存占内存比较大
class Solution {
public:
string reverseWords(string s) { // example: __boy___good__a_
stringstream ss;
ss << s; // 输入流
string t, ans;
while(ss >> t){ // t 为当前去除空格的单词
ans = t + " " + ans;
// boy --> good boy --> a good boy
}
ans.erase(ans.begin() + ans.size() - 1); // 去掉末尾最后的一个空格
return ans;
}
};
4.题目链接 — LeetCode165. 比较版本号
输入 / 输出样例
输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,"01" 和 "001" 都表示相同的整数 "1"
-------------------------------------------
输入:version1 = "0.1", version2 = "1.1"
输出:-1
解释:version1 中下标为 0 的修订号是 "0",version2 中下标为 0 的修订号是 "1"
0 < 1,所以 version1 < version2
---------------------------------------------
输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有指定下标为 2 的修订号,即视为 "0"
代码
class Solution {
public:
int compareVersion(string v1, string v2) {
int i = 0, j = 0;
int a, b;
while(i < v1.size() || j < v2.size()){
int x = i, y = j; // 用 i~x 和 j~y 来遍历数字部分
while(x < v1.size() && v1[x] != '.') x++;
while(y < v2.size() && v2[y] != '.') y++;
if(x == i) a = 0; // x==i说明v1没有数字部分了,后面补0
else a = stoi(v1.substr(i, x-i)); // 截取i开始x-i长度的字符串,并转为int
if(y == j) b = 0;
else b = stoi(v2.substr(j, y-j));
if(a > b) return 1;
if(a < b) return -1;
i = x + 1;
j = y + 1;
}
return 0; // 相等
}
};
5.题目链接 — LeetCode929. 独特的电子邮件地址
代码1
class Solution {
public:
int numUniqueEmails(vector<string>& emails) {
unordered_set<string> hash;
for(auto &e : emails){
string loc;
int n = e.size();
// 构造 local_name
for(int i = 0; i < n; i++){
if(e[i] == '.') continue;
if(e[i] == '@' || e[i] == '+' ) break;
loc += e[i];
}
// 构造 new DNS
for(int i = 0; i < n; i++){
if(e[i] != '@') continue;
loc = loc + e.substr(i, n-1);
}
// cout<<loc<<endl;
hash.insert(loc);
}
return hash.size();
}
};
代码2:yxc
class Solution {
public:
int numUniqueEmails(vector<string>& emails) {
unordered_set<string> hash;
for(auto &e : emails){
string loc;
int at = e.find('@');
for(auto c : e.substr(0, at)){
if(c == '+') break;
else if(c != '.') loc += c;
}
string domain = e.substr(at + 1); // 截取 at + 1 到末尾
hash.insert(loc + '@' + domain);
}
return hash.size();
}
};
6.题目链接 — LeetCode5.最长回文子串
样例
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
方法一:中心扩散法。判断回文串长度,取max
代码
class Solution {
public:
string longestPalindrome(string s) {
string ans;
for(int i = 0; i < s.size(); i++) {
// case 1: 遍历子回文串奇数情况
for(int j = i, k = i; j >= 0 && k < s.size() && s[j] == s[k]; j--, k++) {
if(ans.size() < k-j+1) ans = s.substr(j, k-j+1);
} // case 2: 遍历偶数情况, k从i+1开始
for(int j = i, k = i+1; j >= 0 && k < s.size() && s[j] == s[k]; j--, k++) {
if(ans.size() < k-j+1) ans = s.substr(j, k-j+1);
}
}
return ans;
}
};
7.题目链接 — LeetCode6.Z字形变换
样例
代码
class Solution {
public:
string convert(string s, int n) {
if(n == 1) return s;
string ans;
for(int i = 0; i < n; i++){
if(!i || i == n-1){ // 处理首尾
for(int j = i; j < s.size(); j += 2*(n-1)){
ans += s[j];
}
}
else{ // 处理中间部分(两个等差数列)
// 数列1:首项i, 数列2:首项 2(n-1)-i. 因为i+k = 2(n-1)
for(int j = i, k = 2*(n-1)-i; j<s.size() || k<s.size(); j += 2*(n-1), k+=2*(n-1)){
if(j < s.size()) ans += s[j];
if(k < s.size()) ans += s[k];
}
}
}
return ans;
}
};
8.题目链接 — LeetCode3. 无重复字符的最长子串
样例
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
思路:双指针算法 O(n)
- 定义两个指针 i,j(j <= i),当前扫描的子串为[ i, j ]
- 指针 i 后移,并把哈希表中 s[i] 的计数+1
- 假设 i 移动的区间[j, i]中 没有重复字符,则 i 移动后,若 s[i] 出现2次,则说明已经有重复了。因此不断后移 j,直到区间 [i, j] 中 s[i]的个数 = 1
代码
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> mp;
int ans = 0;
int n = s.size();
for(int i = 0, j = 0; i < n; i++){
mp[s[i]] ++ ;
while(mp[s[i]] > 1){ // 如果mp[s[i]] > 1, 说明有重复
mp[s[j]] --;
j++; // j指针不断后移, 直到mp[s[i]]只出现一次
}
ans = max(ans, i-j+1);
}
return ans;
}
};
【补充】:Trie 树
补充题目链接:AcWing 835. Trie字符串统计、
Trie树,图源ACWing: 四谷夕雨
【注】这里的son用的是二维数组
定义变量
int son[N][26]; // son存放的:子节点对应的idx。
// ★ son数组:
// [一维]:父节点的idx
// [二维]:元素值(a:0,b:1,c:2....)。
int cnt [N]; // “abc”中,‘c’对应的idx作为cnt数组的下标。数组的值是idx对应的个数。
int idx; // 结点编号 每用一个结点idx++
string str; // 待处理的字符串
Trie树 插入字符串
void insert(string str){
int p = 0; // p 为当前节点的idx
for (int i = 0; str[i]; i++){
int u = str[i] - '0';
if (!son[p][u]) son[p][u] = ++idx; // 若p的子结点不存在:创建新结点
p = son[p][u]; // p 指向 p的子结点
}
// 循环结束,此时p就是str中最后一个字符对应的trie树的位置idx
cnt[p]++;
}
Trie树 查询字符串
// 返回字符串在Trie树的个数
int query(string str){
int p = 0; // p 为当前节点的idx
for (int i = 0; str[i]; i++) {
int u = str[i] - '0';
if (!son[p][u]) return 0; // 如果不存在当前字符的子结点,说明不存在这个字符串
p = son[p][u]; // 沿着Trie树遍历 p -> p.child
}
// 循环结束,此时p就是str中最后一个字符对应的trie树的位置idx
return cnt[p];
9.题目链接 — LeetCode208. 实现 Trie (前缀树)
定义结构体
struct Node{
bool tag; // 结尾标记
Node *son[26]; // 存储字符 son:Node指针变量
Node(){ // 结构体的构造函数
tag = false;
for(int i = 0; i < 26; i++) son[i] = NULL;
}
}*root; // 定义root为结构体指针
Trie() {
root = new Node();
}
1 插入字符串
void insert(string word) {
auto p = root;
for(auto c : word){
int u = c - 'a';
if(p->son[u] == NULL) p->son[u] = new Node(); // 若不存在:创建子结点
p = p->son[u]; // p指向子结点
}
p->tag = true;
}
2 查询字符串是否存在
bool search(string word) {
auto p = root;
for(auto c : word){
int u = c - 'a';
if(p->son[u] == NULL) return false;
p = p->son[u];
}
return p->tag;
}
3 查询前缀是否存在
bool startsWith(string prefix) {
auto p = root;
for(auto c : prefix){
int u = c - 'a';
if(p->son[u] == NULL) return false;
p = p->son[u];
}
return true;
}