DAY8:字符串(三)替换空格+字符串基础补充

剑指offer05.替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

思路:

在cpp中不能直接将字符串赋值给字符,也就是说,不能通过 s[i]="%20"这样的方法来进行替换。s[i] 是一个字符,将字符串 “%20” 直接赋值给它是不可能的。

同时,为了降低空间复杂度,我们最好在原字符串上进行操作

错误示例:

string replaceSpace(string s) {
          for(int i=0;i<s.size();i++){
            if(s[i]==' '){
                s[i]="%20"; //错误,字符串不能直接赋值给字符
            }
        }
        return s;
方法一:可以调用replace

(但是这道题目调用replace直接解决,没有意义)

class Solution {
public:
    string replaceSpace(string s) {
        size_t p = 0; //size_t存储字符串中空格的位置
        //注意size_t就是s.find()变量的返回值
        while ((p = s.find(" ", p)) != std::string::npos) {
             s.replace(p, 1, "%20");
        }
        return s;
    }
};

replace方法的时间复杂度与空间复杂度

replace的时间复杂度是O(n),因为它需要先找到要替换的位置,这个过程就是**O(n)**的。而且,如果替换后的字符串长度和原来不一样,那么还需要移动后面的字符,这个过程也是O(n)的。所以,replace的时间复杂度是O(n)。

示例:

void replaceAll(std::string& str, const std::string& from, const std::string& to) {
    if(from.empty())
        return;
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
}

replace方法的空间复杂度取决于你是用什么容器来存储字符串的。如果你用的是**std::string,那么replace方法的空间复杂度是O(1),因为它不会分配新的内存**,而是直接修改原字符串。但是,如果你用的是其他容器,比如**std::vector,那么replace方法的空间复杂度可能是O(n)**,因为它可能需要重新分配内存来适应新的字符串长度。

示例:

// replace algorithm example
#include <iostream>     // std::cout
#include <algorithm>    // std::replace
#include <vector>       // std::vector

int main () {
  int myints[] = { 10, 20, 30, 30, 20, 10, 10, 20 };
  std::vector<int> myvector (myints, myints+8); // 10 20 30 30 20 10 10 20

  std::replace (myvector.begin(), myvector.end(), 20, 99); // 10 99 30 30 99 10 ...

}
字符串s.find()和map中find()方法的区别?

1.字符串的s.find()方法

std::stringfind 函数和 std::mapfind 函数有不同的用法和目的。

std::string::find 函数是用于在字符串中查找子串的位置。它的语法如下:

size_t find(const std::string& str, size_t pos = 0) const;
  • str 是要查找的子串,可以是一个字符串或字符。
  • pos 是可选参数,指定从哪个位置开始搜索子串。默认值是0,表示从字符串的起始位置开始搜索。
  • 它返回找到的子串的第一个字符在字符串中的位置索引,如果未找到则返回 std::string::npos

示例:

std::string s = "Hello, World!";
size_t pos = s.find("World");  // pos = 7,找到了子串"World"

2.map的.find()方法

std::map::find 函数是用于在 std::map 容器中查找给定键的迭代器。它的语法如下:

iterator find(const Key& key);
  • key 是要查找的键。
  • 它返回指向包含给定键的元素的迭代器,如果未找到则返回 end() 迭代器。

示例:

std::map<int, std::string> myMap;
myMap[1] = "One";
myMap[2] = "Two";

auto it = myMap.find(2);  // it 指向键为 2 的元素

总结来说,std::string::find 用于在字符串中查找子串的位置,而 std::map::find 用于在 std::map 容器中查找给定键的迭代器。它们的用法和返回值不同,但都提供了在特定容器中进行查找的功能。

size_t的用法

size_t 和 map中的auto 在这里都用于存储返回的结果,但它们的用途和含义有所不同。

  • size_t 是一种无符号整数类型,通常用于表示数组的大小、索引或字符串的长度。在字符串的 find 函数中,size_t 用于表示找到的子串的位置索引或无效位置(std::string::npos
  • auto 是一个类型推导关键字,它可以根据右侧表达式的类型自动推导出变量的类型。在 std::mapfind 函数中,auto 用于自动推导返回的迭代器的类型。

虽然在这两个情况下都可以使用 size_tauto 存储返回的迭代器或位置索引,但它们的用法和意义是不同的。size_t 是一个特定的整数类型,而 auto 是一种类型推导机制。

size_type用法

std::string::find 函数的返回类型是 std::string::size_type,而不是 size_t

std::string::size_type 是一个无符号整数类型,用于表示字符串的大小和索引。它是根据特定平台上字符串的最大长度进行定义的。在大多数情况下,std::string::size_type 的别名是 size_t,因此两者在大多数情况下是等价的。

方法二:扩充空间(常规解法)

1.先把数组扩充到填充了%20字符之后的大小

2.再按照双指针法从后向前替换空格

从后向前替换的原因

如果扩充空间之后,再从前向后填充,就是**O(n^2)**的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。

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

这样做有两个好处:

  1. 不用申请新数组。空间复杂度为O(1),不分配新的内存
  2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
class Solution {
public:
    string replaceSpace(string s) {
        //先计算有多少个空格
        int count=0;
        int oldSize = s.size(); //记住旧的数组长度
        for(int i=0;i<s.size();i++){
            if(s[i]==' '){
                count++;
            }
        }
        s.resize(s.size()+count*2);
        int newSize = s.size(); //新的数组长度
        
        //从后往前遍历,用新的数组部分存放旧的部分
        for(int i=newSize-1,j=oldSize-1;j<i;i--,j--){ //i可以重复赋值
            if(s[j]!=' '){
                s[i]=s[j];
            }
            else{
                s[i]='0';
                s[i-1]='2';
                s[i-2]='%';
                i=i-2;//注意这里的i只会比j多出两个格子,不是3个!
            }
        }
        return s;
        
    }
};
注意

本题目中i只比j多出两个空格,因此i往后移动的时候应该是i-=2。

边界条件直接猜测空格在最后一个即可

截止目前,加上本题已经做了8道双指针相关的题目,分别是:

补充1:url

当在URL中传递参数时,某些字符(例如空格、特殊字符等)需要进行编码,以便在URL中进行传输和解析时不会造成歧义或错误。URL编码是将这些字符转换为特定格式的编码表示。

URL编码使用百分号(%)后跟两个十六进制数字来表示特定字符的编码。**对于空格字符,其编码为%20。**这意味着在URL中,空格字符会被替换为%20,以确保在接收方解码时能够正确还原为空格字符。

例如,考虑以下URL:https://example.com/search?query=my search string。在这个URL中,空格字符存在于查询参数中的字符串my search string。为了将这个URL传递给服务器或进行处理,空格字符需要进行编码。因此,这个URL在进行URL编码后的结果可能如下所示:https://example.com/search?query=my%20search%20string在编码后的URL中,空格字符被替换为%20

URL编码是确保在URL中传递特殊字符和保留字符时的一种常见做法,以避免URL解析和传输中的问题。编码和解码过程是互逆的,可以通过URL解码将编码后的字符重新还原为其原始形式。

补充2:字符串和数组的区别

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

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

示例:

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

这里如果是cpp,同样含义的代码应该用string类类型来写,比如:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string a = "asd"; // 定义一个string对象a,初始化为"asd"
    for (int i = 0; i < a.size(); i++) // 用一个for循环遍历a,用a.size()获取字符串的长度
    {
        // 循环体里可以对a[i]做任何操作,比如打印
        cout << a[i] << endl;
    }
    return 0;
}

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

示例:

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

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

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

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

补充3:char数组创建字符串的注意事项

要保证char数组用’\0’结尾才能转换为 string,要么直接就用string 才创建结果字符串
在这里插入图片描述
在这里插入图片描述
以上两种方式都可以,注意char数组如果要存放字符串,最后一位需要变成’\0’

当你有一个char数组存储字符串时,确实需要在最后一个字符后面加上’\0’字符。例如:

char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

或者你可以直接初始化字符串,编译器会自动在末尾添加’\0’字符:

char str[] = "Hello";

在这个例子中,str实际上是一个包含6个字符的数组,字符是:‘H’, ‘e’, ‘l’, ‘l’, ‘o’, 和 ‘\0’。

如果你忘记在字符串末尾加’\0’,那么任何试图读取这个字符串的函数(如printf,strcpy等)可能会继续读取内存,直到它找到一个’\0’字符。这可能导致读取超出数组边界的内存,这是一个常见的导致程序错误的原因,称为缓冲区溢出。

在C++中,也可以使用 std::string 类来存储和操作字符串,它会自动处理这种终止字符的问题,使得字符串的使用更加方便和安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值