【1】38. 外观数列
日期:12.26
2.类型:字符串
3.方法一:遍历(半解)
从第一项 "1" 开始
对于每一项,使用双指针统计连续相同字符的数量
将 计数 + 字符 拼接成新字符串
关键代码:
cpp
for(int i=2;i<=n;++i){
string curr="";
int start=0;
int pos=0;
while(pos<prev.size()){
// 找到连续相同字符的结束位置
while(pos<prev.size()&&prev[pos]==prev[start]){
pos++;
}
// 将"计数 + 字符"添加到当前项
curr +=to_string(pos-start)+prev[start];
start=pos;
}
//将当前项设为下一轮的前一项
prev=curr;
}
2.方法二:枚举查表(一次题解)
题目中给定的数量限制为 1≤n≤30,由于数量很小,只需要依次生成前 30 个 countAndSay 字符串并将其存储为静态数组,每次直接查询数组中的第 n 个元素即可。
关键代码:

日期:12.27
2.类型:字符串,遍历
3.方法一:反向遍历(一次题解)
从字符串末尾开始向前扫描,避免处理开头的空格和中间的复杂情况
先跳过末尾的空格
然后统计最后一个单词的字符数
关键代码:
cpp
// 跳过末尾的所有空格
while(s[index]==' '){
index--;
}
// 统计最后一个单词的长度
int wordLength=0;
while(index>=0&&s[index]!=' '){
wordLength++;
index--;
}
【3】76. 最小覆盖子串
日期:12.28
2.类型:字符串,滑动窗口,哈希表
3.方法一:滑动窗口(官方题解)
用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l 指针。在任意时刻,只有一个指针运动,而另一个保持静止。在 s 上滑动窗口,通过移动 r 指针不断扩张窗口。当窗口包含 t 全部所需的字符后,如果能收缩,就收缩窗口直到得到最小窗口。

可以用一个哈希表表示 t 中所有的字符以及它们的个数,用一个哈希表动态维护窗口中所有的字符以及它们的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数,那么当前的窗口是「可行」的。
关键代码:
cpp
while(r<int(s.size())){
// 右指针向右移动一位,扩大窗口
++r;
// 如果当前字符是t中的字符,更新cnt中的计数
if(r<s.size()&&ori.find(s[r])!=ori.end()){
++cnt[s[r]];
}
// 当窗口满足条件(包含t的所有字符)时,尝试收缩窗口
while(check()&&l<=r){
// 更新最小窗口记录
if(r-l+1<len){
len=r-l+1; // 更新最小长度
ansL=l; // 记录左边界
}
// 收缩窗口:左指针向右移动
if(ori.find(s[l])!=ori.end()){
--cnt[s[l]]; // 如果移出的字符是t中的字符,更新计数
}
++l; // 左指针右移
}
}
【4】125. 验证回文串
日期:12.29
2.类型:字符串,双指针
3.方法一:滑动窗口(半解)
预处理阶段:
目的:过滤掉所有非字母数字字符,并将所有字母统一为小写
关键函数:
isalnum(ch):检查字符是否为字母(a-z, A-Z)或数字(0-9)
tolower(ch):将大写字母转换为小写,其他字符不变
回文判断阶段:
方法:将预处理后的字符串与其逆序版本比较
原理:回文串正着读和反着读都一样
关键代码:
cpp
for (char ch: s){
// isalnum(ch) 检查字符是否是字母或数字
if(isalnum(ch)){
// tolower(ch) 将字符转换为小写
sgood+=tolower(ch);
}
}
// sgood.rbegin() 返回反向迭代器,指向字符串的最后一个字符
// sgood.rend() 返回反向迭代器,指向字符串第一个字符的前一个位置
// string(sgood.rbegin(), sgood.rend()) 使用反向迭代器构造逆序字符串
string sgood_rev(sgood.rbegin(), sgood.rend());
return sgood==sgood_rev;
日期:12.30
2.类型:字符串,进制转换
3.方法一:进制转换(半解)
将列标题看作一个26进制数(A=1, B=2, ..., Z=26),然后将其转换为十进制数。
公式:对于长度为n的列标题字符串S = S₁S₂...Sₙ:
result = Sₙ×26⁰ + Sₙ₋₁×26¹ + ... + S₁×26ⁿ⁻¹
其中Sᵢ是第i个字符对应的数值(A=1, B=2, ..., Z=26)
关键代码:
cpp
for(int i=columnTitle.size()-1;i>=0;i--){
// 将当前字符转换为对应的数值
// 'A' - 'A' + 1 = 1
// 'B' - 'A' + 1 = 2
// ...
// 'Z' - 'A' + 1 = 26
int k=columnTitle[i]-'A'+1;
// 累加当前位的贡献:数值 × 权重
number+=k*multiple;
multiple*=26;
}
日期:12.31
2.类型:字符串,哈希表,滑动窗口
3.方法一:哈希表(一次题解)
滑动窗口:每次取长度为10的子串
哈希表:记录每个子串出现的次数
结果收集:当某个子串第二次出现时,将其加入结果列表
-
当计数等于2时:说明该子串至少出现2次,且是第一次发现它重复
-
当计数大于2时:已经添加过了,不需要重复添加
-
这样可以保证结果中每个重复子串只出现一次
关键代码:
cpp
for(int i=0;i<=n-L;++i){
// 从位置i开始,截取长度为L(10)的子串
string sub=s.substr(i,L);
// 当计数等于2时,说明这个子串是第二次出现,将其加入结果
if(++cnt[sub]==2){
ans.push_back(sub);
}
}
return ans;
日期:1.1
2.类型:字符串,递归,数学
3.方法一:递归(半解)
核心思路:分段处理 + 递归
分段处理:将数字按每3位(千分位)分组处理
- Billion(十亿)段:1,000,000,000
- Million(百万)段:1,000,000
- Thousand(千)段:1,000
- 个位段:1-999
递归转换:将0-999的数字递归转换为英文表示
转换规则:
-
0-9:直接使用
singles数组 -
10-19:直接使用
teens数组 -
20-99:十位数 + 个位数(可能为空)
-
100-999:百位数 + "Hundred" + 剩余部分
关键代码:
cpp
string numberToWords(int num){
if (num==0){
return "Zero";
}
string sb;
// 从大到小处理:Billion -> Million -> Thousand -> 个位
for(int i=3,unit=1000000000;i>=0;i--,unit/=1000){
int curNum=num/unit;
if(curNum!=0){
num-=curNum*unit;
// 递归转换当前千分位的数字(0-999)
string curr;
recursion(curr, curNum);
// 加上千分位单位(如 "Thousand", "Million", "Billion")
curr=curr + thousands[i] + " ";
sb = sb + curr;
}
}
// 移除末尾可能多余的空格
while (sb.back()==' '){
sb.pop_back();
}
return sb;
}
void recursion(string & curr, int num){
if(num==0){
return;
}else if(num<10){
curr=curr+singles[num]+" ";
}else if(num<20){
curr=curr+teens[num-10]+" ";
}else if(num<100){
// 20-99:先添加十位数部分,然后递归处理个位数
curr=curr+tens[num/10]+" ";
recursion(curr, num%10);
}else{
// 100-999:先添加百位数部分,然后递归处理剩余部分
curr=curr+singles[num/100]+" Hundred ";
recursion(curr,num%100);
}
}
【8】290. 单词规律
日期:1.2
2.类型:字符串,哈希表
3.方法一:哈希表(半解)
可以利用哈希表记录每一个字符对应的字符串,以及每一个字符串对应的字符。然后枚举每一对字符与字符串的配对过程,不断更新哈希表,如果发生了冲突,则说明给定的输入不满足双射关系
关键代码:
cpp
for(auto ch:pattern){
if(i>=m){
return false;
}
int j=i;
while(j<m&&str[j]!=' '){
j++;
}
// 提取单词(从i到j-1)
const string &tmp=str.substr(i,j-i);
// 如果单词已经映射过,但映射的字符不是当前字符,返回false
if(str2ch.count(tmp)&&str2ch[tmp]!=ch){
return false;
}
// 如果字符已经映射过,但映射的单词不是当前单词,返回false
if(ch2str.count(ch)&&ch2str[ch]!=tmp){
return false;
}
str2ch[tmp]=ch;
ch2str[ch]=tmp;
i=j+1;
}
// 如果i>=m,说明str也正好遍历完,否则str有多余单词
return i >= m;
日期:1.3
2.类型:字符串,回溯
3.方法一:回溯(官方题解)
核心思想:使用回溯法枚举所有可能的表达式,同时通过乘法链的概念处理乘法的优先级。
乘法链(mul):记录当前乘法操作的结果
例如:在表达式 2 + 3 * 4 * 5 中:
处理完 2+3 后,res=5,mul=3
遇到 *4:res = 5 - 3 + 3*4 = 5 - 3 + 12 = 14,mul = 3*4 = 12
遇到 *5:res = 14 - 12 + 12*5 = 14 - 12 + 60 = 62,mul = 12*5 = 60
前导零处理:
(j == i || num[i] != '0') 确保:
如果当前位置是0,只能取一位数字(不能有多位数的0,如"05")
如果当前位置不是0,可以取多位数字
关键代码:
cpp
function<void(string&, int, long, long)> backtrack=[&](string &expr, int i, long res, long mul) {
if(i==n){
if(res==target){
ans.emplace_back(expr);
}
return;
}
int signIndex=expr.size();
if(i>0){
expr.push_back(0); // 占位,下面填充符号(0是空字符,稍后会被替换)
}
long val=0;
// 数字可以有前导零,但如果有前导零,该数字只能是0
for(int j=i;j<n&&(j==i||num[i]!='0');++j){
val=val*10+(num[j]-'0');
expr.push_back(num[j]);
if(i==0){
backtrack(expr,j+1,val,val);
}else{
// 加法
expr[signIndex]='+';
// 新的乘法链 = 当前值(因为加法会开始新的乘法链)
backtrack(expr,j+1,res+val,val);
// 减法
expr[signIndex]='-';
// 新的乘法链 = -当前值(因为减法相当于加上负数)
backtrack(expr, j+1,res-val,-val);
// 乘法
expr[signIndex]='*';
// 新结果 = 之前结果 - 之前乘法链 + 之前乘法链 × 当前值
// 新的乘法链 = 之前乘法链 × 当前值
backtrack(expr,j+1,res-mul+mul*val,mul*val);
}
}
expr.resize(signIndex);
};
【10】318. 最大单词长度乘积
日期:1.4
2.类型:字符串,位运算
3.方法一:位运算(官方题解)
核心思想:使用位掩码表示单词
将26个小写字母映射到32位整数的26个位上
如果一个单词包含某个字母,就将对应的位置1
这样可以用一个32位整数唯一表示一个单词的字母组成
关键代码:
cpp
// 为每个单词计算位掩码
for(int i=0;i<length;i++){
string word=words[i];
int wordLength=word.size();
// 遍历单词中的每个字符,构建位掩码
for(int j=0;j<wordLength;j++){
// 将字符转换为0-25的索引,然后将对应位置1
masks[i] |= 1 << (word[j]-'a');
}
}
int maxProd=0;
for(int i=0;i<length;i++){
for(int j=i+1;j<length;j++){
// 如果两个单词的位掩码按位与结果为0,说明它们没有公共字母
if((masks[i] & masks[j])==0){
// 计算两个单词长度的乘积,并更新最大值
maxProd=max(maxProd,int(words[i].size()*words[j].size()));
}
}
}
【11】344. 反转字符串
日期:1.5
2.类型:字符串,双指针
3.方法一:双指针(一次题解)
将 left 指向字符数组首元素,right 指向字符数组尾元素。
当 left < right:
交换 s[left] 和 s[right];
left 指针右移一位,即 left = left + 1;
right 指针左移一位,即 right = right - 1。
当 left >= right,反转结束,返回字符数组即可。
关键代码:
cpp
for(int left=0,right=n-1;left<right;++left,--right){
swap(s[left], s[right]); // 交换左右指针指向的字符
}