代码随想录算法训练营day8 | 344.反转字符串、541.反转字符串 II、卡码网:54.替换数字

news2024/12/26 18:20:56

文章目录

    • 344.反转字符串
      • 思路
    • 541.反转字符串 II
      • 思路
    • 卡码网:54.替换数字
      • 思路
      • 复习:字符串 vs 数组
    • 总结

今天是字符串专题的第一天,主要是一些基础的题目

344.反转字符串

建议: 本题是字符串基础题目,就是考察 reverse 函数的实现,同时也明确一下 平时刷题什么时候用 库函数,什么时候 不用库函数

题目链接:344. 反转字符串 - 力扣(LeetCode)

本题要编写一个函数,将输入的字符串反转过来。这道题考察的是reverse函数的实现原理,就不要直接使用reverse函数解题了。如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数

思路

这道题的思想与 206.反转链表 相同,都是使用双指针法字符串也是一种数组,所以元素再内存中是连续存储的,因此 反转字符串 比 反转链表要容易

整体思路

定义left、right指针(就是索引下标)

  • 初始:left指向字符串的第一个元素,right指向字符串的最后一个元素
  • 反转:交换s[left]s[right],然后收缩这两个指针:left++right--
  • 循环条件:当right > left时,就继续执行while循环,交换s[left]s[right]

反转过程 如图:

344.反转字符串

可以使用库函数swap执行交换操作,swap函数可以有两种实现

  • 一种是常见的交换数值:

    char tmp = s[left];
    s[left] = s[right];
    s[right] = tmp;
    
    
  • 另一种是通过位运算,CSAPP第二章的位运算部分讲过这个:

    s[left] ^= s[right];
    s[right] ^= s[left];
    s[left] ^= s[right]
    
    

代码实现

class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0, right = s.size()-1;
        while(right > left)
        {
            swap(s[left], s[right]);
            right--;
            left++;
        }
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

541.反转字符串 II

建议:本题又进阶了,自己先去独立做一做,然后在看题解,对代码技巧会有很深的体会。

题目链接:541. 反转字符串 II - 力扣(LeetCode)

思路

这道题就是设置了一点障碍,思想与前一道题是相同的,只是反转并不是一次完成的,每2k个字符才能反转一次。一种思路是将字符串“分割”:遍历字符串,设置count计数,每计数2k个字符,就进入反转程序,对这2k个字符的区间进行反转。另一种思路是在遍历的同时直接进行反转,对于一些特殊的情况我们单独判断。第二种思路更自然,我们选择第二种思路解题

整体思路

如果使用 344.反转字符串 中的双指针法反转字符串,我们使用current表示当前遍历到的位置:

初始:current指向字符串的第一个元素

反转:每次while循环开始,left赋为current,是反转区间的第一个元素。关键是确定right,剩余字符串的长度length 与 k 的大小比较决定了right的位置

  • 若length < k,题目要求将剩余字符全部反转。此时right = s.size()-1,指向这个字符串的末尾
  • 若k <= length < 2k,题目要求反转前 k 个字符,其余字符保持原样,那么right = current + k -1,指向反转区间的第k个字符
  • 若k >= 2k,题目要求反转这 2k 个字符中的前 k 个字符,则right = current + k - 1,指向反转区间的第k个字符

当完成一个区间的反转后,current向后移动2k个元素,即current += 2*k。如果 leng < k 或 k <= length < 2k,则移动2k个元素后current > s.size(),因此不会进入下一次循环。这保证了k <= length < 2k时,只反转前k个字符,而其他字符保持原样

代码实现

class Solution {
public:
    string reverseStr(string s, int k) {
        // 使用current指针指向当前遍历到的位置
        int current = 0;
        int left, right;
        while(current < s.size())
        {
            left = current;
            int length = s.size() - current;
            if(length <  k)
            {
                right = s.size()-1;
            }else   // 剩下的两种情况都是反转前k个字符
            {
                right = current + k - 1;
            }
            while(right > left)
            {
                swap(s[left], s[right]);
                right--;
                left++;
            }
            current += 2*k; // 如果剩余字符小于k个 或 小于 2k 但大于或等于 k 个,则下次循环直接退出了
        }
        return s;
    }
};

本题的关键在于对反转区间的划分,而不在于如何反转,因此可以直接使用reverse函数

代码实现

class Solution {
public:
    string reverseStr(string s, int k) {
        for (int i = 0; i < s.size(); i += (2 * k)) {
            // 1. 每隔 2k 个字符的前 k 个字符进行反转
            // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
            if (i + k <= s.size()) {
                reverse(s.begin() + i, s.begin() + i + k );
            } else {
                // 3. 剩余字符少于 k 个,则将剩余字符全部反转。
                reverse(s.begin() + i, s.end());
            }
        }
        return s;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

卡码网:54.替换数字

建议:对于线性数据结构,填充或者删除,后序处理会高效的多。好好体会一下

这道题是Carl刷题网站上的,题目链接:54. 替换数字(第八期模拟笔试) (kamacoder.com)

这个题目需要自己补全所有代码,包括导入库,实现main函数

思路

本题使用双指针法。我们首先遍历一遍字符串,统计所有出现过的数字字符数量,然后对这个字符串做扩展,预留需要填充的大小,如图所示:

img

然后我们利用双指针法在这个字符串上进行数字的替换,我们需要明确这两个指针的含义:

  • 第一个指针指向旧字符串的末尾(未扩展前的字符串),用来判断旧字符串中的字符是否为数字

  • 第二个指针指向新字符串的末尾,根据第一个指针指向的字符,填充相应的元素:

    • 如果第一个指针指向的是字母字符,则第二个指针填充这个字母字符,这两个指针同时向前移动
    • 如果第一个指针指向的是数字字符,则第二个指针填充“number”,然后两个指针同时向前移动

    其实整个过程可以看成是一个映射:

    • 第一个指针指向字母字符 -> 第二个指针填充字母字符
    • 第一个指针指向数字字符 -> 第二个指针填充"number"

思路如图:

img

你会发现我们是从后向前填充这个数组的,从前向后填充不行吗?

从前向后填充就是O(n2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动,注意:我们的旧字符串 在 新字符串的前面部分,因此从头填充是需要将旧字符串依次后移,否则会丢失旧字符串中的字符

其实很多数组填充类的问题,其做法都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作

这么做有两个好处:

  1. 不用申请新数组
  2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题

代码实现

#include<iostream>
using namespace std;

int main()
{
    // 由于题目可能逐个输入字符串,所以我们使用while循环读取输入的字符
    string s;
    while(cin >> s)
    {
        int sOldIndex = s.size()-1; // 第一个指针,初始指向旧字符串的末尾元素
        int count = 0;  // 统计数字字符的个数
        for(int i=0; i<s.size(); ++i)
        {
            if(s[i] >= '0' && s[i] <= '9')
            {
                count++;
            }
        }
        // 扩充字符串s的大小,也就是将每个数字替换为"number"之后的字符串大小
        s.resize(s.size() + count*5);
        int sNewIndex = s.size() - 1;
        // 从后向前将数字替换为"number",字母直接映射过来
        while(sOldIndex >= 0)
        {
            if(s[sOldIndex] >= '0' && s[sOldIndex] <= '9')
            {
                s[sNewIndex--] = 'r';
                s[sNewIndex--] = 'e';
                s[sNewIndex--] = 'b';
                s[sNewIndex--] = 'm';
                s[sNewIndex--] = 'u';
                s[sNewIndex--] = 'n';
            }else
            {
                s[sNewIndex--] = s[sOldIndex];
            }
            // sOldIndex向前移动一位
            sOldIndex--;
        }
        cout << s << endl;
    }
}

复习:字符串 vs 数组

复习一下字符串和数组有什么差别:

字符串是若干字符组成的有限序列,也可以理解是一个字符数组,但是很多语言对字符串做了特殊的规定,接下来说一说C/C++中的字符串

在C语言中,把一个字符串存入一个数组时,也把结束符 '\0’存入数组,并以此作为该字符串是否结束的标志。

例如这段代码:

char a[5] = "asd";
for (int i = 0; a[i] != '\0'; i++) {
}

在C++中,提供一个string类,string类会提供 size接口,可以用来判断string类字符串是否结束,就不用’\0’来判断是否结束。

例如这段代码:

string a = "asd";
for (int i = 0; i < a.size(); i++) {
}

那么vector< char > 和 string 又有什么区别呢?

其实在基本操作上没有区别,但是 string提供更多的字符串处理的相关接口,例如string 重载了+,而vector却没有。

所以想处理字符串,我们还是会定义一个string类型

总结

今天的题目比较简单,关键是双指针法字符串的操作,最后一道题是一道经典的数组扩充题目,需要好好体会

字符串的一些常用操作如下:

#include <iostream>
#include <string>

int main() {
    // 1. 构造字符串
    std::string s1; // 默认构造函数
    std::string s2("Hello, World!"); // 从C风格字符串构造
    std::string s3(s2); // 复制构造函数
    std::string s4(s2, 7, 5); // 从 s2 的第 7 个字符开始取 5 个字符

    // 2. 赋值操作
    s1 = s2; // 赋值运算符
    s3.assign("Hi there!"); // assign 赋值

    // 3. 访问元素
    char c1 = s2[1]; // 使用 operator[] 访问元素,结果为 'e'
    char c2 = s2.at(1); // 使用 at 访问元素,结果为 'e'
    char c3 = s2.front(); // 访问第一个字符,结果为 'H'
    char c4 = s2.back(); // 访问最后一个字符,结果为 '!'

    // 4. 迭代器
    std::cout << "Using iterator: ";
    for (auto it = s2.begin(); it != s2.end(); ++it) {
        std::cout << *it; // 使用迭代器输出字符串
    }
    std::cout << std::endl;

    std::cout << "Using reverse iterator: ";
    for (auto rit = s2.rbegin(); rit != s2.rend(); ++rit) {
        std::cout << *rit; // 使用反向迭代器输出字符串
    }
    std::cout << std::endl;

    // 5. 容量相关
    bool isEmpty = s2.empty(); // 检查字符串是否为空,结果为 false
    std::size_t size = s2.size(); // 获取字符串长度,结果为 13
    std::size_t length = s2.length(); // 获取字符串长度,结果为 13
    std::size_t capacity = s2.capacity(); // 获取容量
    s2.reserve(50); // 预留至少 50 个字符的存储空间
    s2.resize(10); // 将字符串大小调整为 10

    // 6. 修改操作
    s2.clear(); // 清空字符串
    s2 = "Hello, World!"; // 重新赋值
    s2.push_back('!'); // 在末尾添加字符
    s2.pop_back(); // 移除末尾的字符
    s2.insert(5, " World"); // 在位置 5 插入字符串
    s2.erase(5, 6); // 从位置 5 开始删除 6 个字符
    s2.replace(7, 5, "C++"); // 从位置 7 开始替换 5 个字符为 "C++"

    // 7. 查找操作
    std::size_t pos = s2.find("C++"); // 查找子字符串 "C++" 的第一次出现位置
    std::size_t rpos = s2.rfind("l"); // 从后向前查找字符 'l' 的位置
    std::size_t fpos = s2.find_first_of("aeiou"); // 查找第一个元音字母的位置
    std::size_t lpos = s2.find_last_of("aeiou"); // 查找最后一个元音字母的位置
    std::size_t fnpos = s2.find_first_not_of("Hello"); // 查找第一个不在 "Hello" 中的字符位置
    std::size_t lnpos = s2.find_last_not_of("Hello"); // 查找最后一个不在 "Hello" 中的字符位置

    // 8. 子字符串
    std::string sub = s2.substr(7, 5); // 从位置 7 开始获取长度为 5 的子字符串

    // 9. 比较操作
    int result = s2.compare(s3); // 比较 s2 和 s3 的内容

    // 输出结果
    std::cout << "s2: " << s2 << std::endl;
    std::cout << "pos: " << pos << std::endl;
    std::cout << "rpos: " << rpos << std::endl;
    std::cout << "fpos: " << fpos << std::endl;
    std::cout << "lpos: " << lpos << std::endl;
    std::cout << "fnpos: " << fnpos << std::endl;
    std::cout << "lnpos: " << lnpos << std::endl;
    std::cout << "sub: " << sub << std::endl;
    std::cout << "compare result: " << result << std::endl;

    return 0;
}

感谢chatgpt😸

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

链式法则和自动求导

向量链式法则 说明&#xff1a; 1.第一个式子&#xff0c; y是标量&#xff0c;u是标量&#xff0c;x是n维向量 2.第二个式子&#xff0c;y是标量&#xff0c;u是k维向量&#xff0c;x是n维向量&#xff0c;所以y对u求导是k维的行向量&#xff0c;u对x求导是k行n列的矩阵&…

Node 版本控制工具 NVM 的安装和使用(Windows)

遇到了一个项目&#xff0c;前端的node版本很低&#xff0c;需要我去降低node版本才能下载依赖运行&#xff0c;我当然不是傻乎乎的降版本了&#xff0c;而是使用node版本控制工具 NVM。 NVM&#xff08;Node Version Manager&#xff09; nvm 是一个命令行工具&#xff0c;用于…

【OSS对象存储】Springboot集成阿里云OSS + 私有化部署Minio

【OSS对象存储】Springboot集成阿里云OSS 私有化部署Minio 一、摘要二、POM依赖三、配置文件四、表结构设计五、代码实现5.1 代码包结构5.2 API封装5.3 增删改查 六、扩展6.1 Minio配置https访问 一、摘要 掌握阿里云OSS、私有化部署Minio两种对象存储的使用方式运用工厂策略…

STM32-寄存器ADC配置指南

目录 输入方式&#xff1a; 模拟看门狗功能&#xff1a; ADC中断 配置一个Demo 设置时钟 自校准 通道选择 采样时间选择 转换模式选择 断续模式 启动转换 软件触发 外部触发 转换结束 关于DMA 模拟看门狗 ​编辑ADC数据位置​编辑 在STM32F中&#xff0c;ADC可…

FM与AM的特点

1.是什么&#xff1f; FM&#xff08;调频&#xff09;&#xff1a;通过改变载波频率来传递信息AM&#xff08;调幅&#xff09;&#xff1a;通过改变载波的振幅来传递信息 2.分别有什么特点&#xff1f; 抗干扰能力&#xff1a; FM&#xff1a;由于FM信号的传输不依赖于载波的…

c++初阶知识——string类详解

目录 前言&#xff1a; 1.标准库中的string类 1.1 auto和范围for auto 范围for 1.2 string类常用接口说明 1.string类对象的常见构造 1.3 string类对象的访问及遍历操作 1.4. string类对象的修改操作 1.5 string类非成员函数 2.string类的模拟实现 2.1 经典的string…

【CI/CD】docker + Nginx自动化构建部署

CI/CD是什么 CI/CD 是持续集成&#xff08;Continuous Integration&#xff09;和持续部署&#xff08;Continuous Deployment&#xff09;或持续交付&#xff08;Continuous Delivery&#xff09;的缩写&#xff0c;它们是现代软件开发中用于自动化软件交付过程的实践。 1、…

自动驾驶系列—智能巡航辅助功能中的路口通行功能介绍

自动驾驶系列—智能巡航辅助功能中的车道中央保持功能介绍 自动驾驶系列—智能巡航辅助功能中的车道变换功能介绍 自动驾驶系列—智能巡航辅助功能中的横向避让功能介绍 自动驾驶系列—智能巡航辅助功能中的路口通行功能介绍 文章目录 2. 功能定义3. 功能原理4. 传感器架构5. 实…

【Redis进阶】集群

1. 集群分片算法 1.1 集群概述 首先对于"集群"这个概念是存在不同理解的&#xff1a; 广义的"集群"&#xff1a;表示由多台主机构成的分布式系统&#xff0c;称为"集群"狭义的"集群"&#xff1a;指的是redis提供的一种集群模式&…

牛客JS题(二)直角三角形

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; repeat格式化字符串 题干&#xff1a; 我的答案 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"></head><body><div classtriangle><…

C++树形结构(1 基础)

目录 一.基础&#xff1a; 1.概念&#xff1a; 2.定义&#xff1a; Ⅰ.树的相关基础术语&#xff1a; Ⅱ.树的层次&#xff1a; 3.树的性质&#xff1a; 二.存储思路&#xff1a; 1.结构体存储&#xff1a; 2.数组存储&#xff1a; 三.树的遍历模板&#xff1a; 四.信…

【BUG】已解决:NameError: name ‘python‘ is not defined

NameError: name ‘python‘ is not defined 目录 NameError: name ‘python‘ is not defined 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于…

【TortoiseGit】合并单个commit(提交)到指定分支上

0、前言 当我们用Git的时候经常用到多个分支&#xff0c;会经常有如下情况&#xff1a;一个dev分支下面会有多个test分支&#xff0c;而每个test分支由不同的开发者。而我们会有这样的需求&#xff1a; 当某个test分支完成了相应功能验证&#xff0c;就要把成功验证的功能代码…

【Git】上传代码命令至codeup云效管理平台

通过git命令上传本地代码库至阿里的codeup云效管理平台的代码管理模块&#xff0c;使用方便&#xff0c;且比github上传网络环境要求低&#xff0c;超大文件&#xff08;>100M&#xff09;的文件也可以批量上传&#xff0c;且上传速度喜人。 目录 &#x1f337;&#x1f33…

[Vulnhub] Acid-Reloaded SQLI+图片数据隐写提取+Pkexec权限提升+Overlayfs权限提升

信息收集 IP AddressOpening Ports192.168.101.158TCP:22,33447 $ nmap -p- 192.168.101.158 --min-rate 1000 -sC -sV Not shown: 65534 closed tcp ports (conn-refused) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.7p1 Ubuntu 5ubuntu1.3 (Ubuntu Lin…

【NOI-题解】1009 - 数组逆序1162 - 数组元素的删除1211 - 数组元素的插入1161. 元素插入有序数组1159. 数组元素的移动

文章目录 一、前言二、问题问题&#xff1a;1009 - 数组逆序问题&#xff1a;1162 - 数组元素的删除问题&#xff1a;1211 - 数组元素的插入问题&#xff1a;1161. 元素插入有序数组问题&#xff1a;1159. 数组元素的移动 三、感谢 一、前言 本章节主要对数组问题中数组元素移…

昇思25天学习打卡营第23天 | 基于MindSpore的红酒分类实验

学习心得&#xff1a;基于MindSpore的红酒分类实验 在机器学习的学习路径中&#xff0c;理解和实践经典算法是非常重要的一步。最近我进行了一个有趣的实验&#xff0c;使用MindSpore框架实现了K近邻&#xff08;KNN&#xff09;算法进行红酒分类。这个实验不仅加深了我对KNN算…

Jenkins+Gitlab持续集成综合实战

一、持续集成应用背景&#xff1a; DevOps&#xff1a;&#xff08;英文Development&#xff08;开发&#xff09;和Operations&#xff08;技术运营&#xff09;的组合&#xff09;是一组过程、方法与系统的统称&#xff0c;用于促进开发&#xff08;应用程序/软件工程&#…

设计模式|观察者模式

观察者模式是一种行为设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。当主题对象发生变化时&#xff0c;它的所有观察者都会收到通知并更新。观察者模式常用于实现事件处理系统、发布-订阅模式等。在项目中&#xff0c…

C语言 | Leetcode C语言题解之第279题完全平方数

题目&#xff1a; 题解&#xff1a; // 判断是否为完全平方数 bool isPerfectSquare(int x) {int y sqrt(x);return y * y x; }// 判断是否能表示为 4^k*(8m7) bool checkAnswer4(int x) {while (x % 4 0) {x / 4;}return x % 8 7; }int numSquares(int n) {if (isPerfect…