12.26-1.5力扣字符串刷题

【1】38. 外观数列

日期:12.26

1.题目链接:38. 外观数列 - 力扣(LeetCode)https://leetcode.cn/problems/count-and-say/description/?envType=problem-list-v2&envId=string

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 个元素即可。

关键代码:

【2】58. 最后一个单词的长度

日期:12.27

1.题目链接:58. 最后一个单词的长度 - 力扣(LeetCode)https://leetcode.cn/problems/length-of-last-word/description/?envType=problem-list-v2&envId=string

2.类型:字符串,遍历

3.方法一:反向遍历(一次题解)

从字符串末尾开始向前扫描,避免处理开头的空格和中间的复杂情况

先跳过末尾的空格

然后统计最后一个单词的字符数

关键代码:

cpp 复制代码
         // 跳过末尾的所有空格
        while(s[index]==' '){
            index--;
        }        
        // 统计最后一个单词的长度
        int wordLength=0;
        while(index>=0&&s[index]!=' '){
            wordLength++;  
            index--;             
  }

【3】76. 最小覆盖子串

日期:12.28

1.题目链接:76. 最小覆盖子串 - 力扣(LeetCode)https://leetcode.cn/problems/minimum-window-substring/description/?envType=problem-list-v2&envId=string

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

1.题目链接:125. 验证回文串 - 力扣(LeetCode)https://leetcode.cn/problems/valid-palindrome/description/?envType=problem-list-v2&envId=string

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;

【5】171. Excel 表列序号

日期:12.30

1.题目链接:171. Excel 表列序号 - 力扣(LeetCode)https://leetcode.cn/problems/excel-sheet-column-number/description/?envType=problem-list-v2&envId=string

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;
        }

【6】187. 重复的DNA序列

日期:12.31

1.题目链接:187. 重复的DNA序列 - 力扣(LeetCode)https://leetcode.cn/problems/repeated-dna-sequences/description/?envType=problem-list-v2&envId=string

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; 

【7】273. 整数转换英文表示

日期:1.1

1.题目链接:273. 整数转换英文表示 - 力扣(LeetCode)https://leetcode.cn/problems/integer-to-english-words/description/?envType=problem-list-v2&envId=string

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

1.题目链接:290. 单词规律 - 力扣(LeetCode)https://leetcode.cn/problems/word-pattern/description/?envType=problem-list-v2&envId=string

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;

【9】282. 给表达式添加运算符

日期:1.3

1.题目链接:282. 给表达式添加运算符 - 力扣(LeetCode)https://leetcode.cn/problems/expression-add-operators/description/?envType=problem-list-v2&envId=string

2.类型:字符串,回溯

3.方法一:回溯(官方题解)

核心思想:使用回溯法枚举所有可能的表达式,同时通过乘法链的概念处理乘法的优先级。

乘法链(mul):记录当前乘法操作的结果

例如:在表达式 2 + 3 * 4 * 5 中:

处理完 2+3 后,res=5mul=3

遇到 *4res = 5 - 3 + 3*4 = 5 - 3 + 12 = 14mul = 3*4 = 12

遇到 *5res = 14 - 12 + 12*5 = 14 - 12 + 60 = 62mul = 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

1.题目链接:318. 最大单词长度乘积 - 力扣(LeetCode)https://leetcode.cn/problems/maximum-product-of-word-lengths/description/?envType=problem-list-v2&envId=string

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

1.题目链接:344. 反转字符串 - 力扣(LeetCode)https://leetcode.cn/problems/reverse-string/description/?envType=problem-list-v2&envId=string

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]);  // 交换左右指针指向的字符
        }
相关推荐
闲看云起7 小时前
LeetCode-day5:三数之和
算法·leetcode·职场和发展
Xの哲學7 小时前
Linux 文件系统一致性: 从崩溃恢复到 Journaling 机制
linux·服务器·算法·架构·边缘计算
wtmReiner7 小时前
山东大学数值计算2026.1大三上期末考试回忆版
笔记·算法
黛色正浓7 小时前
leetCode-热题100-滑动窗口合集(JavaScript)
javascript·算法·leetcode
漫随流水7 小时前
leetcode算法(145.二叉树的后序遍历)
数据结构·算法·leetcode·二叉树
Tony_yitao7 小时前
22.华为OD机试真题:数组拼接(Java实现,100分通关)
java·算法·华为od·algorithm
2501_941875287 小时前
在东京复杂分布式系统中构建统一可观测性平台的工程设计实践与演进经验总结
c++·算法·github
sonadorje7 小时前
梯度下降法的迭代步骤
算法·机器学习
漫随流水7 小时前
leetcode算法(94.二叉树的中序遍历)
数据结构·算法·leetcode·二叉树
范纹杉想快点毕业8 小时前
嵌入式通信核心架构:从状态机、环形队列到多协议融合
linux·运维·c语言·算法·设计模式