代码随想录第八天 LeetCode 344、541、剑指Offer 05、151、剑指Offer58 (字符串)

字符串简介

首先还是来简单介绍一下字符串,首先区别一下c++中char *、char []、string的区别:

  • char *是一个指向字符串的指针,可以直接使用字符串赋值,如char *s = "abc"
  • char []本质上是一个数组,变量名指向数组的第一个元素,如char str[5]。另外注意字符型数组最后一个元素会存放一个\0。例如char str[5] = {"abcde"};是不合法的,因为末尾必须有一个\0
  • string是c++中的一个类,拥有自己封装好的类成员函数。

下面复习一下string类的一些常用函数:

//初始化
//string也是类,可以像vector以及其他的c++容器类似的方式初始化
string s1("abc");
string s2(s1);
string s3(10,'a'); //初始化10个a
string s4 = "tuboshu"; //c风格的初始化

//获取长度
s1.size();
s1.length(); //两者本质上相同

//添加
s1.append(s2); //go中也是这样拼接的
s1.push_back('a');
s1.insert(...); //insert函数重载有很多但是不常用到

//查找
int index = s4.find("bo"); //返回一个index是"bo"字符穿首次出现的首字母的位置

//删除
s4.erase(s4.begin(),s4.begin() + 2); //删除指定位置
s4.erase(2); //删除从下标2到结束的字符串
s4.erase(2,2); //删除从下标2开始长度为2的字符串

//substr获取字串 substr(pos,len) pos的下标从0开始
string s5 = s4.substr(1); //获取s4的第2个元素到结束构成字串
string s6 = s4.substr(2,1); //从s4的第2个元素长度为1的字串
string s7 = s4.substr(s4.find("b"));

另外,因为重载了+运算符,所以string类型的拼接可以直接使用运算符操作。
参考文章:C++string类常用方法

344.反转字符串

两种方法:1.直接调用reverse()函数
2.双指针法,两个指针分别从头尾向内收缩完成交换:

	void reverseString(vector<char>& s) {
        int left = 0;
        int right = s.size()-1;
        while(left<right){
            swap(s[left++],s[right--]);
        }
    }

本题没什么好说的,可以对比一下206. 反转链表

541. 反转字符串 II

本题翻转的逻辑和上一题一模一样,只不过在遍历字符串时需要注意一些细节,也就是模拟的过程。另外需要处理一下遍历到末尾时的特殊情况。
代码如下:

	string reverseStr(string s, int k) {
        for(int i = 0; i < s.size(); ){
        	//分为两种情况,一种是剩下的字符小于k,另一种是大于等于k
            if(s.size() - i >= k){
                int left = i;
                int right = i + k - 1;
                while(left < right){
                    swap(s[left++],s[right--]);
                }
                //模拟过程的时候i每次增加2k
                i += 2 * k;
            }else{
                int left = i;
                int right = s.size() - 1;
                while(left < right){
                    swap(s[left++],s[right--]);
                }
                break;
            }
        }
        return s;
    }

翻转子字符串的逻辑这里可以直接使用reverse()函数代替更简洁。

剑指 Offer 05. 替换空格

很明显本题输出字符串的size和输入的不同,所以有一个resize()的操作。卡哥说不申请额外空间完成,这种类似移除元素一般用双指针法,但是本题是从后往前遍历,这样就可以避免从前往后遍历时的时间复杂度为O(n^2)。

先找出空格的个数,然后重新分配字符串长度,这里应该是原来的size加2*空格数量,因为空格本身也占用一个位置。。然后就是替换的逻辑。
代码如下:

class Solution {
public:
    string replaceSpace(string s) {
        int count = 0;
        int size = s.size();
        for(int i = 0; i < s.size(); i++){
            if(s[i] == ' ') count++;
        }
        if(count == 0) return s;
        s.resize(size + count * 2);
        int left = size - 1;
        int right = s.size() - 1;
        while(left >= 0){
            if(s[left] != ' '){
                s[right--] = s[left--];
                continue;
            }
            s[right--] = '0';
            s[right--] = '2';
            s[right--] = '%';
            left--;
        }
        return s;
    }
};

151. 反转字符串中的单词

在这里插入图片描述
本题的确很烦人,代码看起来很简洁,实际上背后隐藏了很多逻辑和情况。

大体思路首先应该去掉多余空格,包括头尾和中间的多余空格,然后再翻转,翻转的逻辑使用先整体再部分,也是很巧妙的一种思路。
仔细想一下可以发现去除多余空格可以将所有空格全部删除,并在两个单词之间补上需要的空格,这样就不需要分开讨论了,就变成了删除字符串元素的题,再做一些特殊处理,代码如下:

		int slow = 0 , fast = 0;
        //去空格
        for(;fast < s.size(); fast++){
            if(s[fast] != ' '){
                if(slow != 0) s[slow++] = ' ';
                while(fast < s.size() && s[fast] != ' '){
                    s[slow] = s[fast];
                    slow++;
                    fast++;
                }
            }
        }

使用双指针法,快指针指向主要用来遍历数组,慢指针指向需要更新当前元素的位置。这里为什么要在s[fast] != ' '里加一个while循环呢,因为进入条件后首先需要判断的是是否需要补空格,在一个完整的单词后补空格是需要移动慢指针的,所以当s[fast] != ' '也就是遍历遇到需要保留下来的元素时,应该把一个完整的单词处理完再进入下一次循环。或者反过来想一下,如果这样写:

	for(;fast < s.size(); fast++){
          if(s[fast] != ' '){
              if(slow != 0) s[slow++] = ' ';
              s[slow] = s[fast];
              slow++;
              fast++;
              }
          }
      }

这就是一个普通的移除元素的逻辑,这样会导致每次遇到一个字母都会在后面加一个空格,显然是不对的。在while循环中加入fast < s.size()判断是防止遍历到最后一个元素时fast++导致内存读取错误。

接下来就是部分翻转的逻辑:

//翻转
        int pos = 0;
        for(int i = 0; i < s.size(); i++){
            if(s[i] == ' '){
                reverse(s.begin() + pos, s.begin() + i);
                pos = i + 1;
            }
            if(i == s.size() - 1) reverse(s.begin() + pos, s.end());
        }

其实一开始我是这样写的:

//翻转
        int pos = 0;
        for(int i = 0; i < s.size(); i++){
            if(s[i] == ' '){
                reverse(s.begin() + pos, s.begin() + i);
                pos = i + 1;
            }
        }

这样会导致最后一个单词不能正常翻转,因为reverse()函数是左闭右开的,所以另外加一个特殊处理,或者像卡哥一样自己写一个左闭右闭的reverse函数。
最后是完整的代码:

class Solution {
public:
    string reverseWords(string s) {
        int slow = 0 , fast = 0;
        //去空格
        for(;fast < s.size(); fast++){
            if(s[fast] != ' '){
                if(slow != 0) s[slow++] = ' ';
                while(fast < s.size() && s[fast] != ' '){
                    s[slow] = s[fast];
                    slow++;
                    fast++;
                }
            }
        }
        s.resize(slow);
        reverse(s.begin(), s.end());
        
        //翻转
        int pos = 0;
        for(int i = 0; i < s.size(); i++){
            if(s[i] == ' '){
                reverse(s.begin() + pos, s.begin() + i);
                pos = i + 1;
            }
            if(i == s.size() - 1) reverse(s.begin() + pos, s.end());
        }

        return s;
    }
};

总结一下,双指针法的思想固然重要,但是想要能一次写出AC出来的代码,对细节和多种情况的把握才是关键。

剑指Offer58-II.左旋转字符串

在空间复杂度为O(1)的情况下,使用整体+部分翻转达成左旋效果,还是比较简单的。
代码如下:

string reverseLeftWords(string s, int n) {
        reverse(s.begin(), s.begin() + n);
        reverse(s.begin() + n, s.end());
        reverse(s.begin(), s.end());
        return s;
    }

还是要注意reverse()在翻转字符串时的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值