151. 反转字符串中的单词

151. 反转字符串中的单词

题目链接
代码随想录

分析

其实这个题的思路很简单,先移除多余空格,然后反转整个字符串,再反转单词对应的字符串。思路不难想到,但是想在 20 分钟内,在没有 IDE 的情况下没有错误的写出来,还是有难度的。
反转整个字符串这一步我们就不说了,跟 344. 反转字符串 一模一样,移除多余空格和反转字符串中的单词写起来还是有一点难度的

移除空格

移除空格部分的代码跟 27. 移除元素 的方法其实是一样的,就是双指针法。
常规的方法是先移除头部的空格,再移除尾部的空格,最后移除中间部分的空格,去除中间空格的时候,首先判断当前 fast 指针指向的字符,是不是空格,如果不是,则说明是目标字符,同时移动快慢指针,如果是,则查看上一个确定的字符(slow 指向的字符)是不是空格,如果不是,则也是目标字符,同时移动快慢指针,如果是,说明当前是连续空格字符,不是目标字符

// 移除空格
public String removeSpace(char[] chars){
    // 还是用双指针法
    int left = 0, right = chars.length-1;
    // 移除头部的空格
    while(chars[left]==' '){
        left++;
    }
    // 移除尾部的空格
    while(chars[right]==' '){
        right--;
    }
    // 此时 left 和 right 指向的都是不为空格的字符
    // slow 指的是下一个要插入的位置
    int slow =left+1;
    // fast 指向的是下一个需要检查的位置
    int fast =left+1;
    while(fast<=right){
	    // 如何移除中间的空格呢?
	    // 如果当前遍历字符不是空格,则同时移动快慢指针,
	    // 如果当前遍历字符是空格,而且上一个确定的字符不是空格,则同时移动快慢指针,
	    // 如果当前遍历字符是空格,而且上一个确定的字符也是空格,则仅移动快指针,即跳过此字符。
        if(chars[fast]!=' '||chars[slow-1]!=' '){
            chars[slow]=chars[fast];
            slow++;
            fast++;
        }else{
            fast++;
        }
    }
    // 此时slow指的是下一个位置的索引,copyOfRange的第三个参数指向的索引刚好是排除的。
    return new String(Arrays.copyOfRange(chars, left, slow));
}

其实也可以不用分三步,可以一步到位,但是比较难想

public String removeSpace(char[] chars){
    int slow = 0, fast = 0;
    while (fast < chars.length) {
        // 如果当前字符是空格,而且
        // 这是第一个字符或是最后一个字符,那肯定都是跳过
        // 这个  ' ' == chars[fast + 1] 是为了处理末尾连续多个空格
        if(' ' == chars[fast] && (fast == 0  ||  fast==chars.length-1 || ' ' == chars[fast + 1] || slow == 0 || ' ' == chars[slow - 1])){
            fast++;
        }else{
            chars[slow] = chars[fast];
            fast++;
            slow++;
        }
    }
    // 此时slow指的是下一个位置的索引,copyOfRange的第三个参数指向的索引刚好是排除的。
    return new String(Arrays.copyOfRange(chars, 0, slow));
}

另一种方式,直接对比

public char[] clearSpace(char[] raw){
    char[] result = new char[raw.length];
    // 指向result中下一个插入的位置
    int nextValIndex = 0;
    for(int i=0;i<raw.length;i++){
        if(nextValIndex==0 && raw[i]==' '){
            continue;
        }
        //去除中间空格
        if(raw[i]==' ' && result[nextValIndex-1] == ' '){
            continue;
        }
        result[nextValIndex] = raw[i];
        nextValIndex++;
    }
	// 去除末尾的空格
    if(result[nextValIndex-1] == ' '){
        nextValIndex--;
    }
    // 从 0 开始,不包含 nextValIndex
    return Arrays.copyOfRange(result,0,nextValIndex);
}

其实去除空格

反转单词

反转单词最重要的是找到待反转的单词的起点和重点,
如果上一个字符没有或者上一个字符是空格,那么当前就是单词的起点
如果当前字符不为空格,但是是字符串的末尾,或者不是末尾且下一个字符是空格,那么当前就是单词的末尾

public String reverseWordInStr(char[] chars) {
    char lastChar = 0;
    int wordStart = 0, wordEnd = 0;
    for (int i = 0; i < chars.length; i++) {
        // 如果lastChar不存在或者lastChar是空格,当前就是单词的起点
        if ((lastChar == 0 || ' ' == lastChar)) {
            wordStart = i;
        }
        // 如果当前不是空格,下一个是空格,或者当前就是字符串的末尾了
        if (' ' != chars[i] && (i == chars.length - 1 || ' ' == chars[i + 1])) {
            wordEnd = i;
            // 开始反转
            for (; wordStart < wordEnd;) {
                char temp = chars[wordStart];
                chars[wordStart] = chars[wordEnd];
                chars[wordEnd] = temp;
                wordStart++;
                wordEnd--;
            }
            wordStart = 0;
            wordEnd = 0;
        }
        lastChar = chars[i];
    }
    return new String(chars);
}

然后我找到一种更简单的思路,
因为我们前面已经去掉了空格,所以现在字符串中的空格就都是单词间的空格,因此我们只需要记录空格的位置,就可以确定单词的范围了

public char[]  reverseWords(char[] raw){
    int lastSpaceIndex=-1;
    for(int i=0;i<raw.length;i++){
        if(raw[i]== ' ' || i==raw.length-1){
            // 开始反转 left 到 right-1
            int left = lastSpaceIndex!=-1?lastSpaceIndex+1:0;
            int right = -1;
            if(raw[i]== ' '){
                right = i-1;
                lastSpaceIndex = i;
            }else if(i==raw.length-1){
                right = i;
            }
            while(left<right){
                char temp = raw[right];
                raw[right] = raw[left];
                raw[left] = temp;
                left++;
                right--;
            }
        }
    }
    return raw;
}

解题

class Solution {
    public String reverseWords(String s) {
        // 反转两次即可
        // 先把整个字符串反转,然后再把单词反转
        char[] chars = s.toCharArray();
        String cleanStr = removeSpace(chars);
        chars = cleanStr.toCharArray();
        int left = 0, right = chars.length - 1;
        for (; left < right;) {
            char temp = chars[left];
            chars[left] = chars[right];
            chars[right] = temp;
            left++;
            right--;
        }
        // 开始反转单词
        return reverseWordInStr(chars);
    }

    public String removeSpace(char[] chars) {
        // 还是用双指针法
        int left = 0, right = chars.length - 1;
        // 移除头部的空格
        while (chars[left] == ' ') {
            left++;
        }
        // 移除尾部的空格
        while (chars[right] == ' ') {
            right--;
        }
        // 此时 left 和 right 指向的都是不为空格的字符
        // slow 指的是下一个要插入的位置
        int slow = left + 1;
        // fast 指向的是下一个需要检查的位置
        int fast = left + 1;
        while (fast <= right) {
            // 如何移除中间的空格呢?
            // 如果当前遍历字符不是空格,则同时移动快慢指针,
            // 如果当前遍历字符是空格,而且上一个确定的字符不是空格,则同时移动快慢指针,
            // 如果当前遍历字符是空格,而且上一个确定的字符也是空格,则仅移动快指针,即跳过此字符。
            if (chars[fast] != ' ' || chars[slow - 1] != ' ') {
                chars[slow] = chars[fast];
                slow++;
                fast++;
            } else {
                fast++;
            }
        }
        // 此时slow指的是下一个位置的索引,copyOfRange的第三个参数指向的索引刚好是排除的。
        return new String(Arrays.copyOfRange(chars, left, slow));
    }

    public String reverseWordInStr(char[] chars) {
        char lastChar = 0;
        int wordStart = 0, wordEnd = 0;
        for (int i = 0; i < chars.length; i++) {
            // 如果lastChar不存在或者lastChar是空格,当前就是单词的起点
            if ((lastChar == 0 || ' ' == lastChar)) {
                wordStart = i;
            }
            // 如果当前不是空格,下一个是空格,或者当前就是字符串的末尾了
            if (' ' != chars[i] && (i == chars.length - 1 || ' ' == chars[i + 1])) {
                wordEnd = i;
                // 开始反转
                for (; wordStart < wordEnd;) {
                    char temp = chars[wordStart];
                    chars[wordStart] = chars[wordEnd];
                    chars[wordEnd] = temp;
                    wordStart++;
                    wordEnd--;
                }
                wordStart = 0;
                wordEnd = 0;
            }
            lastChar = chars[i];
        }
        return new String(chars);
    }

}

新版的思路,感觉更简单

public String reverseWords(String s) {
    // 先去除前后和中间的多余空格
    // 然后再将整个字符串反转,
    // 然后再将单个的单词反转
    char[] result = s.toCharArray();
    char[] trimSpace =    clearSpace(result);
    int left =0,right=trimSpace.length-1;
    while(left<right){
        char temp = trimSpace[right];
        trimSpace[right] = trimSpace[left];
        trimSpace[left] = temp;
        left++;
        right--;
    }
    char[] resultChar = reverseWords(trimSpace);
    return new String(resultChar);
}

public char[] clearSpace(char[] raw){
    char[] result = new char[raw.length];
    // 指向result中下一个插入的位置
    int nextValIndex = 0;
    for(int i=0;i<raw.length;i++){
        if(nextValIndex==0 && raw[i]==' '){
            continue;
        }
        if(raw[i]==' ' && result[nextValIndex-1] == ' '){
            continue;
        }
        result[nextValIndex] = raw[i];
        nextValIndex++;
    }
    if(result[nextValIndex-1] == ' '){
        nextValIndex--;
    }
    // 从 0 开始,不包含 nextValIndex
    return Arrays.copyOfRange(result,0,nextValIndex);
}

public char[]  reverseWords(char[] raw){
    int lastSpaceIndex=-1;
    for(int i=0;i<raw.length;i++){
        if(raw[i]== ' ' || i==raw.length-1){
            // 开始反转 left 到 right-1
            int left = lastSpaceIndex!=-1?lastSpaceIndex+1:0;
            int right = -1;
            if(raw[i]== ' '){
                right = i-1;
                lastSpaceIndex = i;
            }else if(i==raw.length-1){
                right = i;
            }
            while(left<right){
                char temp = raw[right];
                raw[right] = raw[left];
                raw[left] = temp;
                left++;
                right--;
            }
        }
    }
    return raw;
}

相关题

344. 反转字符串
232. 用栈实现队列