【c++】深入理解string类(3):典型OJ题

作者:zzzsde日期:2025/10/2

一 仅仅反转字母

链接如下:https://leetcode-cn.com/problems/reverse-only-letters/submissions/

思路:

这道题目的核心就是交换,我们发现这个逻辑和当时在数据结构里学的快速排序非常类似:两个指针,一个指向开头,一个指向结尾,如果前一个指针的值小于后面指针的值,就交换。相应的在这道题目:如果前一个指针和后一个指针都是字母,那就交换。

所以我们需要先写一个判断是否是字母的函数。(c语言库中有这个函数,如果记得这个函数的名称和使用方法可以直接使用,如果不记得也可以像博主一样现写一个)。还需要写一个交换函数。注意:c++的模板中已经定义了这个函数,所以不需要我们自己实现。

代码实现如下:

1class Solution {
2public:
3bool isLetter(char ch)
4   {
5        if(ch >= 'a' && ch <= 'z')
6            return true;
7        if(ch >= 'A' && ch <= 'Z')
8            return true;
9        return false;
10   }
11
12    string reverseOnlyLetters(string s)
13     {
14     size_t start=0,end=s.size()-1;
15     while(start<end)
16     {
17        while(start<end && !isLetter(s[start]))
18        ++start;
19        while(start<end && !isLetter(s[end]))
20        --end;
21
22        swap(s[start],s[end]);
23        start++;
24        end--;
25     }
26     return s;
27    }
28};
  1. end = s.size() - 1 中的括号
    s.size() 是调用字符串对象ssize()成员函数,用于获取字符串的长度,括号()在这里表示调用函数,即使没有参数也必须写上
  2. s[start] 中的方括号[]
    这是 C++ 中访问字符串(或数组)元素的语法,s[start] 表示获取字符串s中索引为start的字符

二 找字符串中第一个只出现一次的字符

链接如下:https://leetcode-cn.com/problems/first-unique-character-in-a-string

思路:

我们可以用一个数组去映射26个字母出现的次数

代码实现如下:

1class Solution {
2public:
3    int firstUniqChar(string s) {
4        int count[26]={0};
5        for(auto ch: s)
6        {
7            count[ch-'a']++;
8        }
9        for(size_t i=0;i<s.size();i++)
10        {
11            if(count[s[i]-'a']==1)
12            return i;
13        }
14        return -1;
15    }
16};

这道题目非常的经典,很多年前就有了,许多公司现在的笔试题都还在考这道题,对于思维有一点的要求。


三 字符串里面最后一个单词的长度

链接如下:https://www.nowcoder.com/practice/8c949ea5f36f422594b306a2300315da?tpId=37&&tqId=21224&rp=5&ru=/activity/oj&qru=/ta/huawei/question-ranking

题目意思为:给你一个字符串,去找最后一个单词的长度,相当于需要你去寻找空格。这道题目是一个标准的IO型题目,不是接口型题目。

思路:

(1)寻找空格。但是如果有多个空格怎么办?例如:have a nice day。那我们就可以倒着去找空格,怎么倒着去找空格呢,string中有一个接口已经帮我们实现了:

(2)那我们在string类中怎么去实现输入和输出呢?也有接口已经帮我们实现了:

就相当于运算符重载

(3)当我们找到最后一个空格的时候,怎么计算最后一个单词的长度呢?

这里有一个小妙招:

当区间是左闭右开的时候:如[0,10),这个时候区间的长度就是所求的长度。

当区间是左闭右闭的时候:如[0,9],这个时候的区间的长度就比所求的长度少一

字符串最后一个字符的位置是size()-1 因为size指向的是最后一个字符的下一个位置假设寻找到的空格所在位置是pos

这个时候pos所指向的位置也不是我们所求区间的第一个位置,而是我们所求位置的前一个位置,所以只需要pos+1就可以了。这个时候区间为左闭右开[pos,size)

我们来尝试写一下代码:

1#include<iostream>
2#include<string>
3using namespace std;
4
5int main() {
6    string str;
7    cin >> str;
8
9    size_t pos = str.rfind(' ');
10    cout << str.size() - (pos + 1) << endl;
11}

但是当我们提交之后,发现测试用例不通过,为什么呢?

注意,在C++中输入的时候,如果有多组输入,用空格或换行当作他们的间隔。(scanf也有这个问题)

那么怎么解决呢?C++帮我们想好了:

getline的作用是读取一整行,默认遇到换行才结束。

getline还有一个很厉害的功能:可以自己设定遇到哪个字符结束输入,这个符号放置在第三个参数的位置,第一个参数是istream类型,就是流插入运算符。

所以调整后的代码如下:

1#include<iostream>
2#include<string>
3using namespace std;
4
5int main() {
6    string str;
7    getline(cin,str);
8    size_t pos=str.rfind(' ');
9    if(pos!=str.size())
10    {
11        cout<< str.size()-(pos+1)<<endl;
12    }
13    else {
14    cout<<str.size()<<endl;
15    }
16}

四 字符串相加

链接如下:https://leetcode-cn.com/problems/add-strings

有些同学看到这道题的思路是:将两个字符串设置成auto类型,相加之和转化为字符串再输出,但是这样的想法是不对的。如果这个个字符串对应的数字比较小可以这样计算,但是如果这两个字符串对应的是几十亿的大数字呢,还能这样计算吗?显然不能,因为这样就越界了。

正确思路:

我们分别从最后一个数字计算相加,大于10的进位,再算前一位数字,这样循环。直到两个字符串都遍历完。

注意:这里有一个易错点:是需要两个字符串都遍历完了才行,而不是只有一个遍历完了就行。所以循环条件要用或 || 而不是且&&(因为如果使用且的话,只要有一个遍历完了就会退出循环,这样会出错)

我们先来给出代码,再来一步一步解答:

1class Solution {
2public:
3    string addStrings(string num1, string num2) {
4       int  end1=num1.size()-1,end2=num2.size()-1;
5       string retStr;
6      int carry=0;
7      while(end1>=0 || end2>=0)
8      {
9        int val1=end1>=0?num1[end1--]-'0':0;
10        int val2=end2>=0?num2[end2--]-'0':0;
11        int ret=val1+val2+carry;
12        carry=ret/10;
13        ret=ret%10;
14
15         retStr.insert(0,1,ret+'0');
16      }
17      if(carry)
18      {
19        retStr.insert(0,1,'1' );
20      }
21      return retStr;
22    
23    }
24};

我们一句一句解释:

1int end1=num1.size()-1,end2=num2.size()-1;

这里设置了两个变量,分别指向两个字符串的最后一个字符。

1int carry=0;

carry代表的英语意思是进位,将进位初始设置为0

1string retStr;

设置了一个新的字符串,用来保存每次相加之和的值

1while(end1>=0 ||end2 >=0)

循环条件:当两个字符串有任意一个没有遍历完都会进入循环

12int val1 = end1 >= 0? num1[end1--] : 0;
3int val2 = end2 >= 0? num2[end2--] : 0;
4
5

设置了两个整型变量来存储当前num1[end1] 对应的数字,end1-- 是先取值再将 end11,指向下一位);否则 val10(比如 num1 已经处理完,高位补 0)。

这里的高位补0的逻辑可能有点绕,博主再来讲解一下:

举个生活中的例子:计算 123 + 45 时,我们会自然地把它们对齐成:

这里其实就是给 45 的高位(百位)补了一个 0,变成 045 再与 123 相加。

在代码中,"高位补 0" 的逻辑体现在这里:

  • end1 < 0 时(num1 已处理完所有字符),val10,相当于给 num1 的高位补 0
  • end2 < 0 时(num2 已处理完所有字符),val20,相当于给 num2 的高位补 0
1int ret=val1+val2+carry;
2carry = ret/10;
3ret = ret%10;
4
5
  • int ret = val1 + val2 + carry;:当前位的两个数字加上进位,得到总和 ret
  • carry = ret / 10;:计算进位(比如 ret = 15,则 carry = 1)。
  • ret = ret % 10;:得到当前位的结果(比如 ret = 15,则当前位结果为 5)。
1retStr.insert(0,1,ret+'0');

头插操作:把当前位的结果插入到 retStr 的开头。

头插操作(在字符串开头插入字符)是为了保证最终结果的数字顺序正确。我们可以通过一个具体例子来理解:

而代码的计算顺序是从低位到高位(先算个位,再算十位,最后算百位):

  1. 先算个位:3 + 5 = 8 → 得到数字 "8"
  2. 再算十位:2 + 4 = 6 → 此时需要把 6 放在 8 的前面,变成 "68"
  3. 最后算百位:1 + 0 = 1 → 再把 1 放在 68 的前面,变成 "168"

这里的 "把新数字放在前面" 就是头插操作

我们在这里复习一下insert这个接口的使用

在指定位置插入多个相同字符

string& insert(size_t pos, size_t n, char c);

第一个参数是要插入的位置,第二个参数是插入字符的个数,第三个参数是需要插入的字符

其他的使用方法我们在这里暂不复习

1if(carry)
2{
3  retStr.inset(0,1,'1');
4}

当两个字符串都遍历结束时,可能还有进位会遗漏,这个时候就需要判断一下。如果这个时候carry不为0,那么头插一个1。

注意:这篇代码有一个易遗漏的,易错的点:为什么运算输出和输入的时候都要加上‘0’ ?

在代码中,'0' 代表字符 '0'(ASCII 码值为 48),它的作用是将数字(整数)转换为对应的字符形式

当我们计算出某一位的结果 ret(比如 ret = 5)时,需要将这个数字存储到字符串中。但字符串只能直接存储字符,不能直接存储整数。

此时通过 '0' + ret 的运算:

  • 字符 '0' 的 ASCII 码是 48
  • ret = 5 时,'0' + 5 = 48 + 5 = 53,而 53 恰好是字符 '5' 的 ASCII 码
  • 因此 '0' + ret 的结果就是数字 ret 对应的字符形式

到此,这道题我们就完成了。

但是这个代码还可不可以优化呢?


优化思路:

我们来算一下这篇代码的时间复杂度是多少?

答案是 O(N^2)

这里常犯的一个错误是很多的同学都会把这里的时间复杂度算成O(n)

原代码中使用 insert(0, ...) 头插操作,每次头插都需要将 retStr 中已有的所有字符后移一位,时间复杂度是 O(N^2)(假设结果字符串长度为 N,每次头插的时间复杂度是 O(N),共执行 N 次左右)

那么我们可不可以把时间复杂度优化成o(n)呢?

可以的。

思路:

我们将头插操作替换成尾插操作(尾插操作的时间复杂度时O(1) ),当遍历和进位都完成之后,再反转字符串就可以了。

还可以提前分配内存,避免后面频繁扩容,影响频率。原因:两个长度分别为 mn 的字符串相加,结果的最大长度是 max(m, n) + 1

尾插的实现用+=:

在 C++ 的 std::string 中,+= 是用于在字符串末尾追加字符或字符串的运算符,它本质上就是一种尾插操作

+= 用于单个字符时,和 push_back() 效率相同,都是 O(1) 级别的操作(均摊时间复杂度),不会像头插那样需要移动已有字符

我们来实现代码:

1class Solution {
2public:
3    string addStrings(string num1, string num2) {
4       int  end1=num1.size()-1,end2=num2.size()-1;
5       string retStr;
6      int carry=0;
7      retStr.reserve(max(num1.size(),num2.size())+1);
8      while(end1>=0 || end2>=0)
9      {
10        int val1=end1>=0?num1[end1--]-'0':0;
11        int val2=end2>=0?num2[end2--]-'0':0;
12        int ret=val1+val2+carry;
13        carry=ret/10;
14        ret=ret%10;
15
16         retStr+=ret+'0';
17      }
18      if(carry)
19      {
20        retStr+='1';
21      }
22      reverse(retStr.begin(),retStr.end());
23      return retStr;
24    
25    }
26};

注意!!!!!一定要区分reserve和reverse,一个时扩容,一个是逆置,二者不可混为一谈

倒数第二行代码逆置的时候,使用了迭代器,所以迭代器的使用其实是重要的。


【c++】深入理解string类(3):典型OJ题》 是转载文章,点击查看原文


相关推荐


ASCII 码表
IMPYLH2025/10/2

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是一种字符编码标准,用于表示一组特定的 95 个(英语)可打印字符和 33 个控制字符(共 128 个代码点)。 二进制八进制十进制十六进制字符010 00000403220space (no visible glyph)010 00010413321!010 00100423422"010 00110433523#010 01000443624$010 01


软件设计师软考备战:第五篇 软件工程与项目管理
软考和人工智能学堂10/1/2025

​​软件工程​​是应用系统化、规范化、可量化的方法开发、运行和维护软件的学科。其目标是提高软件质量、降低开发成本、保证开发进度。​​软件危机​​的表现:项目超出预算项目超过计划完成时间软件质量低下软件通常不满足需求项目无法管理,代码难以维护软件工程与项目管理是软件设计师必须掌握的核心知识,不仅关系到软件开发的成功,也直接影响软件质量和项目效益。通过系统学习软件开发全过程和项目管理方法,能够提高软件开发的专业水平和管理能力。​​思考题​。


[wps_clear]wps清理残余 ——注册表不干净
拾贰_Python9/30/2025

WPS卸载后常见的注册表残留位置包括:HKEY_CURRENT_USER和HKEY_LOCAL_MACHINE下的Kingsoft目录、HKEY_CLASSES_ROOT中与WPS相关的条目。此外,HKEY_CLASSES_ROOT\Applications下可能存在wpp.exe相关项,以及HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts.wps等文件关联记录的残留。这些注册表项需要手动清理以实现完全


2181、合并零之间的节点
Lenyiin2025/10/2

2181、[中等] 合并零之间的节点 1、问题描述: 给你一个链表的头节点 head ,该链表包含由 0 分隔开的一连串整数。链表的 开端 和 末尾 的节点都满足 Node.val == 0 。 对于每两个相邻的 0 ,请你将它们之间的所有节点合并成一个节点,其值是所有已合并节点的值之和。然后将所有 0 移除,修改后的链表不应该含有任何 0 。 返回修改后链表的头节点 head 。 2、代码思路: 跳过第一个节点:链表的开头和结尾都包含值为 0 的节点,我们从第二个节点开始处理(即


磁盘的理解&&CHS和LBA地址转换
阑梦清川2025/10/3

1.对于磁盘的理解 首先就是我们的操作系统课本上面学习的这个磁盘的基本结构,比如下面的这个磁盘,磁头,磁头臂以及柱面,扇区的相关的概念; 针对于这个部分的内容,我自己也没有什么经验可以分享,因为这个东西就是固定的,唯一需要注意的就是结合图区进行理解,注意分别代表的是我们的图片里面画出来的这个磁盘的那一个具体的部分; 根据上面的内容,我们想要确定扇区只需要 CHS 三个部分即可,C 代表的就是我们的柱面,H 表示的是磁头,S 表示的是扇区,下面的这个是 ima 给出来的具体介绍,不懂就多去问问


从传输层协议到 UDP:轻量高效的传输选择
渡我白衣2025/10/5

前言 在计算机网络中,传输层是一个关键的层级,它为应用进程之间的通信提供了端到端的传输服务。常见的传输层协议有 TCP 和 UDP。前者强调可靠、面向连接的传输,而后者则提供轻量级、无连接的通信方式。传输层位于网络层之上、应用层之下,为进程之间提供端到端的数据传输服务。要理解 UDP 协议,我们需要先了解 传输层的功能与常见协议,再深入探讨为什么 UDP 在今天的网络环境中仍占据着举足轻重的地位。 一、传输层的基本概念 在 OSI 七层模型 和 TCP/IP 五层模型 中,传输层是第四层,它的主


Linus 眼中,编程 AI 的真实价值如何?
飞哥数智谈2025/10/6

今天刷到了一段视频,是 Linux 之父 Linus Torvalds 与 VMware 副总裁兼首席开源官 Dirk Hohndel 的一段对话。 内容挺有意思,分享给大家。 主要有两个话题: AI 只是打了鸡血的自动纠错 AI 幻觉带来了 bug 话题是由 Dirk Hohndel 提出,由 Linus Torvalds 进行回答的。 AI 是打了鸡血的自动纠错 针对这一点,Linus Torvalds 认为这一说法有一定的合理性,但 AI 在编程领域的真正潜力是可以成为一个识别“明显愚


Vue3 EffectScope 源码解析与理解
excel2025/10/7

1. 全局变量与基础类定义 activeEffectScope:表示当前正在运行的 effect 作用域。 EffectScope 类:用来管理一组副作用(ReactiveEffect),提供生命周期控制。 import type { ReactiveEffect } from './effect' import { warn } from './warning' // 当前全局正在运行的作用域 export let activeEffectScope: EffectScope | und


tcp服务器
liuy96152025/10/9

🧩 一、总体架构思路 TCP 服务器的基本流程: 创建监听套接字 → 绑定 IP 和端口 → 监听端口 → 接受连接 → 通信收发 → 关闭连接 伪代码框架: int main() { int listen_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字 bind(listen_fd, …); // 绑定地址和端口 listen(listen_fd, SOMAXCONN);


对《DDD本质论》一文的解读
canonical_entropy2025/10/10

在《DDD本质论:从哲学到数学,再到工程实践的完整指南之理论篇》中,我们建立了一套从第一性原理出发的DDD理论体系。由于原文理论密度较高、概念间关系精微,为帮助读者更清晰地把握其思想脉络,我们设计了一项思想实验,并借助AI进行体系梳理与对比。 我们首先向AI提出以下问题: DDD领域驱动设计的概念有哪些?这些概念之间的相互关系是什么。如果要逐一去掉这些概念,你会按照什么顺序,为什么? 换言之,如果从第一性原理出发,如何逐步推导出这些概念的相互关系? 不要把任何技术看作是一个不可切分的整体,任何

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0