《STL 六大组件之容器探秘:深入剖析 string》

目录

  • 一、string 类简介
  • 二、string 类的常用接口
    • 1. 构造函数(constructor function)
    • 2. 与容量相关的接口(capacity)
    • 3. 与迭代器有关的接口(iterator)
    • 4. 与元素访问有关的接口(element access)
    • 5. 与内容修改有关的接口(modify)
    • 6. 与 string 对象操作有关的接口(operation)
    • 7. string 类的关系运算符重载接口
    • 8. string 类的输入输出
  • 三、与 string 类相关的精选习题
    • 1. 把字符串转换成整数(atoi)
    • 2. 字符串相加
    • 3. 仅仅反转字母
    • 4. 字符串中的第一个唯一字符
    • 5. 字符串最后一个单词的长度
    • 6. 验证回文串
    • 7. 反转字符串Ⅱ
    • 8. 反转字符串中的单词Ⅲ
    • 9. 字符串相乘

一、string 类简介

  string 是 C++ 标准库中的一个类,该类专门用来管理字符串。相比于 C 语言中使用字符数组来管理字符串,string 类使用方便且更加强大。
  不同于 C 风格字符串,string

二、string 类的常用接口

  string 类一共有一百多个接口,而常用的接口也就二十来个。所以需要牢牢掌握这常用的二十来个结构,剩下的接口在需要的时候会查询文档即可。

1. 构造函数(constructor function)

  以下是 C++ 标准库中 string 类的构造函数,上半部分是常用的构造函数,需要掌握且熟练运用;而下半部分不常用的构造函数只需了解即可。

(1)函数原型

// 构造函数

// 常用的构造函数
string();  // 默认构造函数
string(const string& str);  // 复制构造函数
string(const char* str)  // 用 C 字符串 s 初始化
string(const char* s, size_t n);  // 用 C 字符串 s 的前 n 个字符初始化
string(size_t n, char c);  // 用 n 个字符 c 初始化

// 不常用的构造函数
string (const string& str, size_t pos, size_t len = npos);  // 用 string 类对象 str 的下标为 pos 开始的长度为 len 的子串初始化
template <class InputIterator>  string  (InputIterator first, InputIterator last);  // 用迭代器[first, last)范围内的字符串初始化

(2)使用演示

// string 构造函数使用演示
void test1()
{
	// 默认构造函数
	string str1;
	cout << "str1 = " << str1 << endl;
	// 使用 C 字符串初始化
	string str2("apple");
	cout << "str2 = " << str2 << endl;
	// 复制构造函数
	string str3(str2);
	cout << "str3 = " << str3 << endl;
	// 用 C 字符串的前 n 个字符初始化
	string str4("aaabbb", 3);
	cout << "str4 = " << str4 << endl;
	// 用 n 个字符 c 初始化
	string str5(5, 'e');
	cout << "str5 = " << str5 << endl;
}

(3)运行结果
在这里插入图片描述

2. 与容量相关的接口(capacity)

  与 C 字符串相比 string 类的一个明显的优势在于它可以根据字符串的长度进行自动增容。而 C 字符串需要提前计算好字符串的长度而开辟相应大小的字符数组。

(1)函数原型

// 常用的与容量有关的接口
size_t size() const;  // 返回当前 string 类对象的长度(与其他 STL 组件保持一致)
size_t length() const;  // 返回当前 string 类对象的长度(未把 string 列为 STL 之前使用的成员函数)
void resize(size_t n);  // 把当前 string 对象的 size 修改至 n
void resize(size_t n, char c);  // 把当前 string 对象的 size 修改至 n,并把新增的字符全部设置为 c
size_t capacity() const;  // 返回当前 string 类对象的容量
void reserve(size_t n = 0);  // 把当前 string 类对象的容量修改为 n
bool empty() const;  // 判断当前 string 类对象是否为空,若为空返回 true,否则返回 false

// 不常用的与容量有关的接口
size_t max_size() const;  // 返回 string 类对象的最大容量(可以容纳的最长字符数)
void clear();  // 把当前 string 类对象的内容清空,长度变为 0,容量不变
void shrink_to_fit();  // 请求字符串减小容量来适应 size 的大小

(2)使用演示

// string 类与容量有关的函数使用演示
void test2()
{
	string str1("C++");
	// 原 size 和 capacity
	cout << "Size: " << str1.size() << endl;
	cout << "Capacity: " << str1.size() << endl;
	cout << "Length: " << str1.length() << endl;
	// 修改 size 并指定字符
	str1.resize(5, 'a');
	cout << "\nstr1 = " << str1 << endl;
	cout << "Size: " << str1.size() << endl;
	cout << "Capacity: " << str1.size() << endl;

	// 修改 capacity
	str1.reserve(18);
	cout << "\nstr1 = " << str1 << endl;
	cout << "Size: " << str1.size() << endl;
	cout << "Capacity: " << str1.size() << endl;

	// 清空并判断当前 string 对象是否为空
	str1.clear();
	cout << "\nstr1 = " << str1 << endl;
	cout << "Size: " << str1.size() << endl;
	cout << "Capacity: " << str1.size() << endl;
	if (str1.empty())
		cout << "Current string is empty.\n";
}

(3)运行结果
在这里插入图片描述

3. 与迭代器有关的接口(iterator)

  迭代器是所有 STL 容器都支持的一种访问手段,迭代器的实质可以是指针也可以是其他自定义类型。支持迭代器也就支持范围 for。

(1)函数原型

// 常用的迭代器接口
iterator begin();  // 返回普通 string 对象的开头迭代器
iterator end();  // 返回普通 string 对象的尾后迭代器
const_iterator begin() const;  // 返回 const string 对象的开头迭代器
const_iterator end() const;  // 返回 const string 对象的尾后迭代器
reverse_iterator rbegin();  // 返回普通 string 对象最后一个元素的迭代器
reverse_iterator rend();  // 返回普通 string 对象第一个元素前一个元素的迭代器
const_reverse_iterator rbegin() const;  // 返回 const string 对象最后一个元素的迭代器
const_reverse_iterator rend(); const  // 返回 const string 对象第一个元素前一个元素的迭代器

// 还有几个前缀 c 的不常用的迭代器接口,用来返回 const 类型的迭代器,也就等价于上述四个返回 const 迭代器的接口。感兴趣的可以自己去查询一下文档
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;
const_reverse_iterator crbegin() const noexcept;
const_reverse_iterator crend() const noexcept;

(2)使用演示
  对迭代器可以使用解引用操作符(*)获得当前位置的元素。下面展示可以访问 string 元素的几种方法。

// string 类迭代器相关函数使用演示
// string 类迭代器相关函数使用演示
void test3()
{
	string str("Good night, everyone !");
	// 使用迭代器正序遍历
	string::iterator it = str.begin();
	while (it != str.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	// 使用迭代器逆序遍历
	string::reverse_iterator rit = str.rbegin();
	while (rit != str.rend())
	{
		cout << *rit;
		++rit;
	}
	cout << endl;
	// 使用范围 for 遍历(编译器在编译阶段会替换成迭代器)
	for (const auto& it : str)
		cout << it;
	cout << endl;
	// 普通 string 类对象可以使用 const 迭代器
	string::const_iterator cit = str.begin();
	// const string 类不可以使用普通迭代器
	const string str1("C++");
	//it = str1.begin();  // 报错
	cit = str1.begin();  // 正确,const 对象只能使用 const 迭代器
}

(3)运行结果
在这里插入图片描述

4. 与元素访问有关的接口(element access)

  string 类通过使用下标运算符访问其类对象的元素,当然还有其他几个不常用的接口;

(1)函数原型

// 常用的访问元素有关的接口
char& operator[](size_t pos);  // 返回普通 string 对象 pos 下标处的字符引用
const char& operator[](size_t pos) const;  // 返回 const string 对象 pos 下标出的字符引用

// 不常用的访问元素有关的接口
// at 接口与上面两个函数的区别在于,at 检查下标时会发出异常,而下标运算符使用断言
char& at(size_t pos);
const char& at(size_t pos) const;
// 返回当前 string 对象的最后一个字符的引用
char& back();
const char& back() const;
// 返回当前 string 对象的第一个字符的引用
char& front();
const char& front() const;

(2)使用演示

// (4)string 类访问元素相关的接口
void test4()
{
	string str("little fool");
	// 使用下标运算符访问 str 的每个字符元素
	for (int i = 0; i < str.size(); ++i)
		cout << str[i];
	cout << endl;
}

(3)运行结果
在这里插入图片描述

5. 与内容修改有关的接口(modify)

  string 类修改内容的接口有很多个,但最常用的是 += 操作符重载,该操作符重载既可以在末尾拼接字符串,也可以在末尾拼接单个字符。

(1)函数原型

// 常用的 string 修改内容的接口
string& operator+=(const string& str);  // 在当前 string对象末尾拼接一个 string 对象 str
string& operator+=(const char* s);  // 在当前 string 对象末尾拼接一个 C 风格字符串 s
string& operator+=(char c);  // 在当前 string 对象末尾拼接一个字符 c
void push_back(char c);  // 在当前 string 对象末尾拼接一个字符 c
string& append(const string& str);  // 在当前 string 对象末尾拼接一个 string 对象
string& append(const char* s);  // 在当前 string 对象末尾拼接一个 C 风格字符串
string& append(size_t n, char c);  // 在当前 string 对象末尾拼接 n 个字符 c
// insert() 接口有多个重载,需要的时候需要查阅相关文档
string& insert(size_t pos, const string& str);  // 在当前 string 对象的下标 pos 处插入一个 string 对象 str
string& insert(size_t pos, const char& s);  // 在当前 string 对象的下标 pos 处插入一个 C 风格字符串
string& erase(size_t pos = 0, size_t len = npos);  // 删除当前 string 对象从 pos 位置开始的长度为 len 的子串
iterator erase(iterator p);  // 删除当前迭代器位置对应的字符,返回下一个字符的迭代器
void swap(string& str);  // 把当前 string 对象和 str 交换
void pop_back();  // 删除当前 string 对象的最后一个字符

// 不常用的 string 修改内容的接口
iterator erase(iterator first, iterator last);  // 删除该迭代器区间的所有字符,返回 last 后一个位置的迭代器
// assign() 接口,给当前 string 对象重新赋值,但 string 类主要使用赋值运算符重载给 string 对象重新赋值
string& assign(const string& str);
string& assign(const char* s);
// replace() 接口,替换当前 string 对象的部分内容
string& replace(size_t pos, size_t len, const string& str);  // 从当前 string 对象的下标 pos 开始,使用 str 的前 len 个字符替换

(2)使用演示

// (5)string 类修改内容有关的接口
void test5()
{
	string str("Hello");
	// string 类的 += 操作符重载演示
	str += ' ';
	str += "world";
	string str1(" !");
	str += str1;
	cout << "str = " << str << endl;
	// 尾插和尾删
	str.push_back('A');
	cout << "str = " << str << endl;
	str.pop_back();
	cout << "str = " << str << endl;
	// 在任意位置插入
	string str2("A");
	str2.insert(1, "apple");
	cout << "str2 = " << str2 << endl;
	int end = (int)str2.size();
	str2.insert(end, ".");
	cout << "str2 = " << str2 << endl;
	// 在任意位置删除
	int len = (int)str2.size() - 1;
	str2.erase(1, len);
	cout << "str2 = " << str2 << endl;
	// 交换两个 string 对象
	string str3("Java");
	string str4("python");
	cout << "str3 = " << str3 << endl;
	cout << "str4 = " << str4 << endl;
	str3.swap(str4);
	cout << "str3 = " << str3 << endl;
	cout << "str4 = " << str4 << endl;
}

(3)运行结果
在这里插入图片描述

6. 与 string 对象操作有关的接口(operation)

  虽然 stirng 类对象不需要以空字符结尾,但是由于 C++ 兼容 C,所以其结尾依旧保留空字符,而使用 c_str() 接口,可以获得其 C 风格字符串的形式,也就是字符指针(char*)。

(1)函数原型

// 常用的与 string 对象操作有关的接口
const char* c_str() const;  // 返回当前 string 对象首字符的地址
size_t find(const string& str, size_t pos = 0) const;  // 在当前 string 对象的 pos 位置开始,寻找 stirng 对象 str 首次出现的位置
size_t find(const char* s, size_t pos = 0) const;  // 在当前 string 对象 pos 位置开始,寻找 C 风格字符串 s 首次出现的位置
size_t find(char c, size_t pos = 0) const;  // 在当前 string 对象 pos 位置开始,寻找字符 c
// 下面三个函数和前面三个 find 函数一致,只不过是从后往前查找
size_t rfind(const string& str, size_t pos = 0) const;
size_t rfind(const char* s, size_t pos = 0) const;
size_t rfind(char c, size_t pos = 0) const;

// 不常用的与 string 对象操作相关的接口
string substr(size_t pos = 0, size_t len = npos) const;  // 返回当前 string 对象 pos 位置开始的长度为 len 的子串

(2)使用演示

// (6)与 string 对象操作相关的接口
void test6()
{
	string str("You are very good!");
	// 获取 C 风格字符串
	const char* s = str.c_str();
	cout << "str = " << str << endl;
	cout << "s  = " << s << endl;
	// 在 str 中查找字符
	size_t pos = str.find('a');
	for (size_t i = pos; i < str.size(); ++i)
		cout << str[i];
	cout << endl;
	// 在 str 中查找字符串
	pos = str.find("very");
	for (size_t i = pos; i < str.size(); ++i)
		cout << str[i];
	cout << endl;
}

(3)运行结果
在这里插入图片描述

7. string 类的关系运算符重载接口

  string 类重载了所有关系运算符,可以直接使用这些关系运算符对两个 string 对象进行比较,这些接口会按照字典顺序进行比较,返回布尔值。

(1)函数原型

// string 类关系运算符重载
bool operator<(const string& str) const;  // 当前 string 对象和 str 比较,若比 str 小则返回 true;否则返回 false
// 下面的接口同第一个接口
bool operator>(const string& str) const;
bool operator<=(const string& str) const;
bool operator>=(const string& str) const;
bool operator==(const string& str) const;
bool operator!=(const string& str) const;

(2)使用演示

// string 类关系运算符重载
void test7()
{
	string str1("C++");
	string str2("Java");
	// 开始比较
	cout << "str1 > str2 = " << (str1 > str2) << endl;
	cout << "str1 >= str2 = " << (str1 >= str2) << endl;
	cout << "str1 < str2 = " << (str1 < str2) << endl;
	cout << "str1 <= str2 = " << (str1 <= str2) << endl;
	cout << "str1 == str2 = " << (str1 == str2) << endl;
	cout << "str1 != str2 = " << (str1 != str2) << endl;
}

(3)运行结果
在这里插入图片描述

8. string 类的输入输出

  string 类的对象可以直接使用 cin 和 cout 搭配流提取运算符(>>)和流插入运算符(<<)进行输入和输出,因为 string 类重载了流插入和流提取运算符。string 类和 C 风格字符串在使用 cin 和流提取运算符进行输入时也只能输入一个单词,遇到空白就停止输入。
  如果需要获取多个单词的输入,就需要使用 istream 类的 getline 方法来获取一行输入。

(1)函数原型

istream& operator>>(istream& is, string& str);  // 流提取运算符重载
ostream& operator<<(ostream& os, const string& str);  // 流插入运算符重载

(2)使用演示

// (8)string 类的输入输出
void test8()
{
	string str;
	// 输入一个单词
	cout << "Please enter a word.\n";
	cin >> str;
	cout << "The word is " << str << endl;
	// 注意:上一次的输入会把换行符留在输入流,所以需要把遗留的换行符扔掉
	cin.get();
	// 输入一句话
	cout << "Please enter a sentence.\n";
	getline(cin, str);
	cout << "The sentence is " << str << endl;
}

(3)运行结果
在这里插入图片描述

三、与 string 类相关的精选习题

1. 把字符串转换成整数(atoi)

题目链接:link

题目描述: 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。

函数 myAtoi(string s) 的算法如下:

  1. 读入字符串并丢弃无用的前导空格
  2. 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
  3. 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
  4. 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
  5. 如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
  6. 返回整数作为最终结果。

注意:
本题中的空白字符只包括空格字符 ’ ’ 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。

示例:
在这里插入图片描述
提示:=

  1. 0 <= s.length <= 200
  2. s 由英文字母(大写和小写)、数字(0-9)、’ ‘、’+‘、’-’ 和 ‘.’ 组成

(1)解题思路:

  1. 首先使用 while 循环跳过前面的前导空格;
  2. 使用 if 语句检查下一个非空格字符是否为负号,如果不是负号则为正数;
  3. 开始读取数字,直到遇到第一个非数字字符。如果没有数字字符,则为 0;
  4. 如果整数超过 INT_MAX 则为 INT_MAX,如果整数小于 INT_MIN 则为 INT_MIN;
  5. 返回结果整数。

(2)解题代码:

class Solution {
public:
    int myAtoi(string str) {
        // 舍弃前面的前导空格
        int i = 0;
        while (' ' == str[i])
            ++i;
        // 判断正负号
        int flag = 1;
        if ('-' == str[i])
        {
            flag = -1;
            ++i;
        }
        else if ('+' == str[i])
        {
            flag = 1;
            ++i;
        }
        // 开始计算
        int len = str.size();
        long long result = 0;
        while (i < len)
        {
            // 是数字字符则加入计算
            if (isdigit(str[i]))
            {
                result = result * 10 + str[i] - '0';
                // 判断计算结果
                if (result > INT_MAX)
                {
                    if (1 == flag)
                        return INT_MAX;
                    else
                        return INT_MIN;
                }
            }
            else  // 否则结束计算
            {
                break;
            }
            // 下一个字符
            ++i;
        }
        // 返回计算结果
        return result * flag;
    }
};

2. 字符串相加

题目链接:link

题目描述:

  1. 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
  2. 你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

示例:
在这里插入图片描述

提示:

  1. 1 <= num1.length, num2.length <= 104
  2. num1 和num2 都只包含数字 0-9
  3. num1 和num2 都不包含任何前导零

(1)解题思路:

  1. 参考数字的加法运算,从两个字符串的最后一个字符开始相加,分别计算当前位置和进位,然后把当前位置拼接到结果字符串中,直到其中一个字符串达到末尾;
  2. 把剩下没到末尾的字符串剩下的字符继续拼接到结果字符串中,最后把整个字符串逆置;
  3. 返回结果字符串。

(2)解题代码:

class Solution {
public:
    string addStrings(string num1, string num2) {
        // 所需变量
        string result;
        int i1 = num1.size() - 1, i2 = num2.size() - 1;
        int carry = 0;
        // 开始拼接
        while (i1 >= 0 || i2 >= 0)
        {
            // 计算当前两位
            int n1 = (i1 >= 0? num1[i1--] - '0' : 0);
            int n2 = (i2 >= 0? num2[i2--] - '0' : 0);
            // 拼接
            result += (n1 + n2 + carry) % 10 + '0';
            // 计算进位
            carry = (n1 + n2 + carry) / 10;
        }
        // 如果两个字符串同时结束,但是 carry 不为 0
        if (carry)
            result += carry + '0';
        // 逆置结果字符串
        reverse(result.begin(), result.end());
        // 返回结果字符串
        return result;
    }
};

3. 仅仅反转字母

题目链接:link

题目描述: 给你一个字符串 s ,根据下述规则反转字符串:

  1. 所有非英文字母保留在原有位置。
  2. 所有英文字母(小写或大写)位置反转。
  3. 返回反转后的 s 。

示例:
在这里插入图片描述
提示:
4. 1 <= s.length <= 100
5. s 仅由 ASCII 值在范围 [33, 122] 的字符组成
6. s 不含 ‘"’ 或 ‘\’

(1)解题思路:

  1. 使用两个指针,分别指向字符串的首尾,当两个指针未相遇并且都指向字母字符时交换,当两个指针相遇或者错开时则结束;
  2. 返回交换完毕的字符串;

(2)解题代码:

class Solution {
public:
    string reverseOnlyLetters(string s) {
        int left = 0, right = s.size() - 1;
        // 开始交换
        while (left < right)
        {
            // 左右寻找字母字符
            while (left < right && !isalpha(s[left]))
                ++left;
            while (left < right && !isalpha(s[right]))
                --right;
            // 交换
            swap(s[left], s[right]);
            // 下一组
            ++left;
            --right;
        }
        // 返回交换完成的字符串
        return s;
    }
};

4. 字符串中的第一个唯一字符

题目链接:link

题目描述: 给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。

示例:
在这里插入图片描述
提示:

  1. 1 <= s.length <= 105
  2. s 只包含小写字母

(1)解题思路:

  1. 由于字符串只有小写字母,那么创建一个包含 26 个元素的 int 数组,对应 26 个小写字母的出现次数;
  2. 遍历一遍数组,计算每个字符出现的次数,再遍历一遍数组,找出第一个出现一次的字符;
  3. 返回对应的下标;

(2)解题代码:

class Solution {
public:
    int firstUniqChar(string s) {
        int times[26] = {0};
        // 计算每个字符出现的次数
        for (const auto& e : s)
        {
            ++times[e - 'a'];
        }
        // 找到第一个出现一次的字符
        int len = s.size();
        for (int i = 0; i < len; ++i)
        {
            if (1 == times[s[i] - 'a'])
                return i;
        }
        // 没有找到返回 -1
        return -1;
    }
};

5. 字符串最后一个单词的长度

题目链接:link

题目描述: 对于给定的若干个单词组成的句子,每个单词均由大小写字母混合构成,单词间使用单个空格分隔。输出最后一个单词的长度。

输入描述:
在一行上输入若干个字符串,每个字符串代表一个单词,组成给定的句子。
除此之外,保证每个单词非空,由大小写字母混合构成,且总字符长度不超过 103

输出描述:
在一行上输出一个整数,代表最后一个单词的长度。

(1)解题思路:

  1. 创建一个 string 对象接受输入,每次输入一个单子并计算其长度,直到遇到换行符;
  2. 返回此时的长度,也就是最后一个单词的长度。

(2)解题代码:

// 头文件
#include <iostream>
#include <string>

// using 声明
using std::string;
using std::cin;
using std::cout;
using std::endl;

int main() {
    int len = 0;
    string str;
    // 输入单词并计算长度
    while (cin >> str)
    {
        len = str.size();
    }
    // 输出最后一个单词的长度
    cout << len << endl;
}
// 64 位输出请用 printf("%lld")

6. 验证回文串

题目链接:link

题目描述: 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。

示例:
在这里插入图片描述

提示:

  1. 1 <= s.length <= 2 * 105
  2. s 仅由可打印的 ASCII 字符组成

(1)解题思路:

  1. 首先遍历一遍字符串,把所有大写字母转换成小写字母,然后把所有字母数字字符拼接到一个新的字符串后头;
  2. 然后使用两个指针正着和倒着遍历来验证新的字符串是否为回文字符串;
  3. 返回验证的结果;

(2)解题代码:

class Solution {
public:
    bool isPalindrome(string s) {
        string tmp;
        // 小写转大写并把所有的字母数字字符拼接到新的字符串后面
        for (int i = 0; i < s.size(); ++i)
        {
            if (isalnum(s[i]))
            {
                tmp += tolower(s[i]);
            }
        }
        // 验证新的字符串
        int left = 0, right = tmp.size() - 1;
        while (left < right)
        {
            // 判断当前这组字符
            if (tmp[left] != tmp[right])
            {
                return false;
            }
            // 下一组字符
            ++left;
            --right;
        }
        // 是回文串
        return true;
    }
};

7. 反转字符串Ⅱ

题目链接:link

题目描述: 给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例:
在这里插入图片描述

提示:

  1. 1 <= s.length <= 104
  2. s 仅由小写英文组成
  3. 1 <= k <= 104

(1)解题思路:

  1. while 循环,前后指针法,cur 指针往前走,pre 指针不动,当 cur 指针与 pre 指针的距离达到 2k-1 时,逆置前 k 个字符并更新 pre 指针的位置;
  2. 当 cur >= s.size() 时跳出循环,并根据此时 cur - pre 的值判断最后一组的情况;
  3. 返回逆置完成的字符串。

(2)解题代码:

// 逆置函数
void reverse(char* left, char* right)
{
    while (left < right)
    {
        char tmp = *left;
        *left = *right;
        *right = tmp;
        // 下一组
        ++left;
        --right;
    }
}

class Solution {
public:
    string reverseStr(string s, int k) {
        // 使用前后指针遍历字符串,每当两个指针相隔 2k 时,反转
        int pre = 0, cur = 0;
        int len = s.size();
        while (cur < len)
        {
            if (cur - pre == 2 * k - 1)
            {
                // 反转前 k 个字符
                reverse(&s[pre], &s[pre + k - 1]);
                // 更新 pre
                pre = cur + 1;
            }
            // cur 前进
            ++cur;
        }
        // 判断最后的一段字符
        if (cur - pre < k)
            reverse(&s[pre], &s[cur - 1]);
        else
            reverse(&s[pre], &s[pre + k - 1]);
        // 返回逆置完毕的字符串
        return s;
    }
};

8. 反转字符串中的单词Ⅲ

题目链接:link

题目描述: 给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例:
在这里插入图片描述

提示:

  1. 1 <= s.length <= 5 * 104
  2. s 包含可打印的 ASCII 字符。
  3. s 不包含任何开头或结尾空格。
  4. s 里 至少 有一个词。
  5. s 中的所有单词都用一个空格隔开。

(1)解题思路:

  1. while 循环,前后指针法,pre 不动,cur 前进,当 cur 遇到空格时,逆置前面的单词后更新 pre,直到 cur 超出 s 末尾;
  2. 返回逆置完毕的 s。

(2)解题代码:

// 逆置函数
void reverse(char* left, char* right)
{
    while (left < right)
    {
        char tmp = *left;
        *left = *right;
        *right = tmp;
        // 下一组
        ++left;
        --right;
    }
}

class Solution {
public:
    string reverseWords(string s) {
        int pre = 0, cur = 0, len = s.size();
        while (cur < len)
        {
            // 如果 cur 是空格,则逆置前面的单词
            if (' ' == s[cur])
            {
                reverse(&s[pre], &s[cur-1]);
                // 更新 pre
                pre = cur + 1;
            }
            // cur 前进
            ++cur;
        }
        // 逆置最后一个单词
        reverse(&s[pre], &s[cur-1]);
        // 返回逆置的字符串
        return s;
    }
};

9. 字符串相乘

题目链接:link

题目描述: 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

示例:
在这里插入图片描述
提示:

  1. 1 <= num1.length, num2.length <= 200
  2. num1 和 num2 只能由数字组成。
  3. num1 和 num2 都不包含任何前导零,除了数字0本身。

(1)解题思路:

  1. 用其中一个字符串的每一位去乘以另一个字符串,然后把结果存在一个临时字符串中,最后加到结果字符串中去;
  2. 需要对不同的位进行补 0,个位不补,十位补一个 0,以此类推;
  3. 字符串的加法前面的习题中已经写过了,本题只是在其基础上进行了一些复杂的变化,难度没有什么提升;
  4. 返回最终的结果字符串。

(2)解题代码:
  本题思路没有问题,但是代码写的一般般,大家可以参考自己修改完善一下。

// 字符串相加函数
string add_string(const string& num1, const string& num2)
{
    // 所需变量
    int i1 = num1.size() - 1;
    int i2 = num2.size() - 1;
    int carry = 0;
    string result;
    // 开始相加
    while (i1 >= 0 || i2 >= 0)
    {
        // 计算两个数字字符串当前位的值
        int n1 = (i1 >= 0 ? num1[i1] - '0' : 0);
        int n2 = (i2 >= 0 ? num2[i2] - '0' : 0);
        // 计算当前位
        int cur = n1 + n2 + carry;
        // 拼接当前位
        result += cur % 10 + '0';
        // 计算进位
        carry = cur / 10;
        // 下一组位
        --i1;
        --i2;
    } 
    // 判断是否存在进位
    if (carry)
        result += carry + '0';
    // 逆置
    reverse(result.begin(), result.end());
    // 返回结果
    return result;
}

class Solution {
public:
    string multiply(string num1, string num2) {
        // 若一个数为 0,则结果为 0
        if ("0" == num1 || "0" == num2)
            return "0";
        // 所需变量
        int i = num1.size() - 1;
        string result;
        // 用 num1 的每一位去乘以 num2
        while (i >= 0)
        {
            char cur = num1[i];
            string cur_result;
            int carry = 0;
            // 当前位开始相乘
            for (int j = num2.size() - 1; j >= 0; --j)
            {
                // 相乘
                int mul = (cur - '0') * (num2[j] - '0') + carry;
                // 计算本位并拼接到当前结果
                cur_result += mul % 10 + '0';
                // 计算进位
                carry = mul / 10;
            }
            // 补上进位
            if (carry)
                cur_result += carry + '0';
            // 逆置当前结果
            reverse(cur_result.begin(), cur_result.end());
            // 补零
            int n = num1.size() - i - 1;
            while (n--)
                cur_result += '0';
            // 把当前结果加到最终结果
            result = add_string(result, cur_result);
            // 下一位
            --i;
        }
        // 返回相乘的结果
        return result;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/972945.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深⼊理解指针(1)

1. 内存和地址 1.1 内存 我们知道计算机上CPU&#xff08;中央处理器&#xff09;在处理数据的时候&#xff0c;需要的数据是在内存中读取的&#xff0c;处理后的 数据也会放回内存中。 那这些内存空间如何高效的管理呢&#xff1f; 其实也是 把内存划分为⼀个个的内存单元&…

解决element-ui的el-select使用filterable属性时,下拉框展开后,点击箭头图标收不回去问题

问题&#xff1a;当el-select下拉组件设置filterable属性时&#xff0c;下拉框展开后&#xff0c;再点击箭头图标下拉框收不回去了 解决方法&#xff1a; 在el-select标签上新增事件 visible-change“selectVisibleChange” focus"selectFocus"的处理 <el-select…

Es的text和keyword类型以及如何修改类型

昨天同事触发定时任务发现es相关服务报了一个序列化问题&#xff0c; 今天早上捕获异常将异常堆栈全部打出来看&#xff0c;才发现是聚合的字段不是keyword类型的问题。 到kibbna命令行执行也是一样的错误 使用 /_mapping查看索引的字段类型&#xff0c;才发现userUniqueid是te…

EasyExcel实现excel导入(模版上传)

目录 效果pom.xmlapplication.ymlcontrollerservice依赖类前台vue代码某个功能如果需要添加大量的数据,通过一条条的方式添加的方式,肯定不合理,本文通过excel导入的方式来实现该功能,100条数据导入成功85条,失败15条,肯定需要返回一个表格给前台或者返回1个错误excel给前…

BFS算法——层层推进,最短之路,广度优先搜索算法的诗意旅程(下)

文章目录 引言一. 迷宫中离入口最近的出口1.1 题目链接&#xff1a;https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze/1.2 题目分析&#xff1a;1.3 思路讲解&#xff1a;1.4 代码实现: 二. 最小基因变化2.1 题目链接&#xff1a;https://leetcode.cn/problem…

Linux----Makefile基础

Makefile 是自动化构建工具 make 的配置文件&#xff0c;用于定义编译规则和依赖关系&#xff0c;实现高效增量编译。 初识makefile 1. 什么是 make&#xff1f; 定义&#xff1a; make 是一个命令行工具&#xff08;可执行程序&#xff09;&#xff0c;用于解析并执行 Makef…

leetcode876.链表的中间结点

目录 问题描述示例提示 具体思路思路一 代码实现 问题描述 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 题目链接&#xff1a;链表的中间结点 示例 提示 链表的结点数范围是 [1, 100]   1 &…

设计变更滞后导致生产计划混乱?PLM与MES集成实时同步

当产品设计在PLM系统中发生变更时&#xff0c;这些变更信息却无法及时、准确地传递到MES系统中。结果是&#xff0c;车间生产现场仍然按照旧的设计指令执行&#xff0c;导致生产出的产品与设计要求不符&#xff0c;不仅引发质量问题&#xff0c;还可能造成停工、物料浪费甚至客…

20250220解决使用top指令查看荣品PRO-RK3566开发板的CPU占用率为400%的问题

20250220解决使用top指令查看荣品PRO-RK3566开发板的CPU占用率为400%的问题 2025/2/20 19:14 缘起&#xff0c;使用荣品PRO-RK3566开发板配套的百度网盘中的SDK&#xff1a;Android13编译之后&#xff0c;查看RK3566的CPU占用率为400%。 开机就是400%&#xff0c;什么时候都是4…

巧用GitHub的CICD功能免费打包部署前端Node项目

近年来&#xff0c;随着前端技术的发展&#xff0c;前端项目的构建和打包过程变得越来越复杂&#xff0c;占用的资源也越来越多。我有一台云服务器&#xff0c;原本打算使用Docker进行部署&#xff0c;以简化操作流程。然而&#xff0c;只要执行sudo docker-compose -f deploy/…

web 通识3

目录 6 通向3.0区块链技术前沿发展 7.主流区块链项目介绍 9.区块链行业应用总览 6 通向3.0区块链技术前沿发展 隔离见证&#xff1a;将一部分信息放在别的地方&#xff0c;这样原本的地方就可以容纳更多的东西 隔离见证和树图都是通过扩大容量来提高性能 闪电网络&#xf…

Hadoop一 HDFS分布式文件系统

一 分布式文件存储 了解为什么海量数据需要使用分布式存储技术 100T数据太大&#xff0c;单台服务器无法承担。于是&#xff1a; 分布式服务器集群 靠数量取胜&#xff0c;多台服务器组合&#xff0c;才能Hold住&#xff0c;如下 分布式不仅仅是解决了能存的问题&#xff…

java练习(33)

ps:题目来自力扣 最强回文子串 给你一个字符串 s&#xff0c;找到 s 中最长的 回文 子串。 class Solution {public String longestPalindrome(String s) {if (s null || s.length() < 1) {return "";}int start 0, end 0;for (int i 0; i < s.length();…

分布式大语言模型服务引擎vLLM论文解读

论文地址&#xff1a;Efficient Memory Management for Large Language Model Serving with PagedAttention 摘要 大语言模型&#xff08;LLMs&#xff09;的高吞吐量服务需要一次对足够多的请求进行批处理。然而&#xff0c;现有系统面临困境&#xff0c;因为每个请求的键值…

日期类(完全讲解版)

1. 类的设计思想 Date 类的设计目的是为了封装和处理日期信息&#xff0c;它提供了对日期的基本操作&#xff0c;如日期加减、日期比较、日期合法性检查等。类中的私有成员 int _year, int _month, int _day 存储了日期的年、月、日。 类的声明和构造 Date 类的声明&#xff1…

微信小程序(uni)+蓝牙连接+Xprint打印机实现打印功能

1.蓝牙列表实现&#xff0c;蓝牙设备展示&#xff0c;蓝牙连接 <template><view class"container"><view class"container_top"><view class"l">设备名称</view><view class"r">{{state.phoneNam…

zookeeper集群配置

配置 一、配置myid文件 # 进入解压好的文件夹下面 touch myid vim myid # master节点写0&#xff0c;slave1节点写1&#xff0c;slave2节点写2二、配置zoo.cfg文件 1.在master节点编辑zookeeper配置文件 # 进入解压好的文件夹下面 cd conf/ cp zoo_sample.cfg zoo.cfg vim …

C++ Primer 类的静态成员

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

Ubuntu 服务器Llama Factory 搭建DeepSeek-R1微调训练环境

1.首先了解一下什么是LLM微调 LLM 微调指的是在已经预训练好的大型语言模型基础上&#xff0c;使用特定的任务数据或领域数据&#xff0c;通过进一步的训练来调整模型的参数&#xff0c;使其在特定任务或领域上能够表现得更好。简单来说&#xff0c;就是对一个已经具备了丰富语…

C++17 中的 std::to_chars 和 std::from_chars:高效且安全的字符串转换工具

文章目录 1. 传统转换方法的局限性2. std::to_chars&#xff1a;数值到字符串的高效转换函数原型&#xff1a;返回值&#xff1a;示例代码&#xff1a;输出&#xff1a; 3. std::from_chars&#xff1a;字符串到数值的高效解析函数原型&#xff1a;返回值&#xff1a;示例代码&…