151.颠倒字符串中的单词

记录新手小白的做题历程。


题目:

给你一个字符串 s ,颠倒字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

且题目样例中有其他的要求,首先,最前面的空格与后面的空格的不能存在,单词与单词之间只能有一个空格。

学习了一下别人的解法。

大佬代码

// 版本一
class Solution {
public:
    // 反转字符串s中左闭又闭的区间[start, end]
    void reverse(string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }

    // 移除冗余空格:使用双指针(快慢指针法)O(n)的算法
    void removeExtraSpaces(string& s) {
        int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针
        // 去掉字符串前面的空格
        while (s.size() > 0 && fastIndex < s.size() && s[fastIndex] == ' ') {
            fastIndex++;
        }
        for (; fastIndex < s.size(); fastIndex++) {
            // 去掉字符串中间部分的冗余空格,
            if (fastIndex - 1 > 0//这个条件是因为后面fast要减1,所以这里要大于1,而不需要等于0,因为第一个一定是字母
                    && s[fastIndex - 1] == s[fastIndex]//这个是重复空格
                    && s[fastIndex] == ' ') {//确定是空格
                continue;//继续移动前面的指针,不放到后面去
            } else {
                s[slowIndex++] = s[fastIndex];
            }
        }//这里结束以后,慢指针是指向空格或者是最后一个元素的
        if (slowIndex - 1 > 0 && s[slowIndex - 1] == ' ') { // 去掉字符串末尾的空格
            s.resize(slowIndex - 1);//是空
        } else {
            s.resize(slowIndex); // ,最后一个元素,重新设置字符串大小
        }
    }

    string reverseWords(string s) {
        removeExtraSpaces(s); // 去掉冗余空格
        reverse(s, 0, s.size() - 1); // 将字符串全部反转
        int start = 0; // 反转的单词在字符串里起始位置
        int end = 0; // 反转的单词在字符串里终止位置
        bool entry = false; // 标记枚举字符串的过程中是否已经进入了单词区间
        for (int i = 0; i < s.size(); i++) { // 开始反转单词
            if (!entry) {
                start = i; // 确定单词起始位置
                entry = true; // 进入单词区间
            }
            // 单词后面有空格的情况,空格就是分词符
            if (entry && s[i] == ' ' && s[i - 1] != ' ') {
                end = i - 1; // 确定单词终止位置
                entry = false; // 结束单词区间
                reverse(s, start, end);
            }
            // 最后一个结尾单词之后没有空格的情况
            if (entry && (i == (s.size() - 1)) && s[i] != ' ' ) {
                end = i;// 确定单词终止位置
                entry = false; // 结束单词区间
                reverse(s, start, end);
            }
        }
        return s;
    }

    // 当然这里的主函数reverseWords写的有一些冗余的,可以精简一些,精简之后的主函数为:
    /* 主函数简单写法
    string reverseWords(string s) {
        removeExtraSpaces(s);
        reverse(s, 0, s.size() - 1);
        for(int i = 0; i < s.size(); i++) {
            int j = i;
            // 查找单词间的空格,翻转单词
            while(j < s.size() && s[j] != ' ') j++;
            reverse(s, i, j - 1);
            i = j;
        }
        return s;
    }
    */
};

收获:

1.双指针可以用来跳过一个字符串中不需要的字符,这道题中去除空格的步骤很关键

2.resize(n)函数:调整容器的长度大小,使其能容纳n个元素。如果n小于容器的当前的size,则删除多出来的元素。如果n大于size,则将其它元素初始化。而resize(n,t)就是将多余空格初始化为t

记录自己写代码时的错误:

1.

 if(first>1&&s[first]==' '&&s[first-1]==' '){

                    first++;//这里不是移动first,不然就漏字符了,而是continue

    }

2.

            if(s[last]==' '){//错的,可能只有一个字母
                s.resize(last-1);
            }

            if(s[last]==‘ ’&&last>1)

3.reverse(s,0,n);//主函数的反转不能这么写,结尾应是n-1 

4.

        for(int i=0;i<n;i++){
            int j=i;//j为结尾
            while(i<n&&s[i]!=' ')//这里写错了,移动的应该是j,作为尾巴
                i++;
            reverse(s,j,i-1);//这下面少了一步,要将i=j,从一个单词结尾继续往下
        }

 下面是我的代码不知道那里出错了检查也检查不出来,有空复查。

class Solution {
public:
void reverse(string& s,int i,int j){
    for(;i<j;i++,j--){
        swap(s[i],s[j]);
    }
}
    void move(string& s,int size){
        int last=0,first=0;
        while(size>0&&s[first]==' '&&first<size){
            first++;
        }
        for(;first<size;first++){
            if(first>1&&s[first]==' '&&s[first-1]==' '){
                continue;
            }
            else{
                s[last++]=s[first];
            }
        }
        if(s[last]==' '&&last>1){
            s.resize(size-1);
        }
        else{
            s.resize(size);
        }
        return;
    }
    string reverseWords(string s) {
        int n=s.size();
        move(s,n);
        reverse(s,0,n-1);
        for(int i=0;i<n;i++){
            int j=i;
            while(s[j]!=' '&&j<n)
                j++;
            reverse(s,i,j-1);
            i=j;
        }
        return s;
    }
};

官方代码:

官方解答

方法一:

与上面大佬的类似,但写法更精简

class Solution {
public:
    string reverseWords(string s) {
        // 反转整个字符串
        reverse(s.begin(), s.end());

        int n = s.size();
        int idx = 0;
        for (int start = 0; start < n; ++start) {
            if (s[start] != ' ') {
                // 填一个空白字符然后将idx移动到下一个单词的开头位置
                if (idx != 0) s[idx++] = ' ';

                // 循环遍历至单词的末尾
                int end = start;
                while (end < n && s[end] != ' ') s[idx++] = s[end++];

                // 反转整个单词
                reverse(s.begin() + idx - (end - start), s.begin() + idx);

                // 更新start,去找下一个单词
                start = end;
            }
        }
        s.erase(s.begin() + idx, s.end());
        return s;
    }
};

方法二:双端队列

class Solution {
public:
    string reverseWords(string s) {
        int left = 0, right = s.size() - 1;
        // 去掉字符串开头的空白字符
        while (left <= right && s[left] == ' ') ++left;

        // 去掉字符串末尾的空白字符
        while (left <= right && s[right] == ' ') --right;

        deque<string> d;
        string word;

        while (left <= right) {
            char c = s[left];
            if (word.size() && c == ' ') {
                // 将单词 push 到队列的头部
                d.push_front(move(word));
                word = "";
            }
            else if (c != ' ') {
                word += c;
            }
            ++left;
        }
        d.push_front(move(word));
        
        string ans;
        while (!d.empty()) {
            ans += d.front();
            d.pop_front();
            if (!d.empty()) ans += ' ';
        }
        return ans;
    }
};

这串代码写得很漂亮! 

在这段代码的收获:

1.move函数的应用

点击链接

std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义,即意思是:告诉编译器,虽然我们有一个左值,但是我们希望可以像右值一样处理。

我个人的理解是,右值不能变,而有时候我的是左值(会变)(左值赋值需要拷贝)不想拷贝浪费空间,就直接用move化成右值直接将它的地址安在它要去的地方。

左值引用有持久的状态;右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。

由于右值引用只能绑定到临时对象(不管编译器怎么做,但这个我们需要遵守),所以:

  • 所引用的对象将要被销毁
  • 该对象没有其他用户(保证安全,在使用的时候一定要特别确定这一点)

而且,使用右值的代码可以自由地接管所引用的对象的资源。

在移动之后,要谨慎操作原对象,一般不操作,因为我们不确定移动操作做了哪些内容,原对象也是处于一种不确定的状态。

即这里的放入word可以不用move,但节省空间,用! 


下一题!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值