【LeetCode字串#03】圖解翻轉字串中的單詞,以及對於for使用的說明

2023-02-11 15:00:56

翻轉字串中的單詞

力扣題目連結(opens new window)

給定一個字串,逐個翻轉字串中的每個單詞。

範例 1:
輸入: "the sky is blue"
輸出: "blue is sky the"

範例 2:
輸入: " hello world! "
輸出: "world! hello"
解釋: 輸入字串可以在前面或者後面包含多餘的空格,但是反轉後的字元不能包括。

範例 3:
輸入: "a good example"
輸出: "example good a"
解釋: 如果兩個單詞間有多餘的空格,將反轉後單詞間的空格減少到只含一個。

思路

以範例1為例,一種或很樸素的想法是:將字串整個反轉

"the sky is blue" ---> "eulb si yks eht"

然後再逐個將單詞轉回來

那麼就有問題了,對於輸入格式規範的字串我們可以想上面那樣處理

但是題目所給的輸入有可能是不規範的

也就是說,字串的前面、後間都有可能插入多個空格,反轉之後還要將這些空格去除(也就是把格式統一為標準形式)

這就有點難搞

現在大致可以把解題思路分為三部分:

​ 1、將字串轉換為標準形式(去除多餘的空格,僅保留單詞間的必要空格)

​ 2、將標準字串整體反轉

​ 3、將反轉字串中的單詞反轉回正常順序

程式碼

按照思路來看,都寫在主函數裡不太現實,可以先把「去除空格」函數和「反轉字串」函數分別寫出來

去除空格函數

就根據思路來寫

和以前一樣,仍需要明確快慢指標的用途

void deleteExtraSpaces(string& s){
    //定義慢指標
    int slow = 0;
    //遍歷字串
    for(int fast = 0; fast < s.size(); ++fast){//++i和i++僅在效率上不同        
        if(s[fast] != ' '){
            //如果當前字元(單詞)不是第一個字元(單詞),那麼要在單詞前面加空格
            if(slow != 0){
                s[slow] = ' ';
                slow++;
            }
            //此時找到了單詞的開頭,需要將其按字元逐個移動到slow所指的位置
            //迴圈條件中需要再判斷當前fast是否指向空格,指到就停止移動
            //進入下一個for迴圈,fast指標從道歉位置後移一位
            while(fast < s.size() && s[fast] != ' '){
                s[slow] = s[fast];
                slow++;
                fast++;
            }
        }
    }
    //因為去除了多餘的空格,所以需要重新計算一下陣列的大小
    s.resize(slow);//此時slow即為標準化後字串陣列的長度       
}

上述程式碼的過程圖示如下:

這裡 for 有個坑,後面注意事項的時候細說

反轉函數

反轉字串 裡面用的一樣,當然也可以用c++提供的

注意:該函數可以反轉一條字串中指定位置的字元

void reverse(string& s, int start, int end){//注意要輸入字串的參照!!!
    //在for中同時維護兩個變數
    for(int i = start, j = end; i < j; i++, j--){
        swap(s[i], s[j]);
    }
}
完整程式碼

在主函數裡面使用這兩個函數完成解題邏輯

class Solution {
public:
    void deleteExtraSpaces(string& s){
        //定義慢指標
        int slow = 0;
        //遍歷字串
        for(int fast = 0; fast < s.size(); ++fast){//++i和i++僅在效率上不同        
            if(s[fast] != ' '){
                //如果當前字元(單詞)不是第一個字元(單詞),那麼要在單詞前面加空格
                if(slow != 0){
                    s[slow] = ' ';
                    slow++;
                }
                //此時找到了單詞的開頭,需要將其按字元逐個移動到slow所指的位置
                //迴圈條件中需要再判斷當前fast是否指向空格,指到就停止移動
                //進入下一個for迴圈,fast指標從道歉位置後移一位
                while(fast < s.size() && s[fast] != ' '){
                    s[slow] = s[fast];
                    slow++;
                    fast++;
                }
            }
        }
        //因為去除了多餘的空格,所以需要重新計算一下陣列的大小
        s.resize(slow);//此時slow即為標準化後字串陣列的長度       
	}
    
    void reverse(string& s, int start, int end){
        //在for中同時維護兩個變數
        for(int i = start, j = end; i < j; i++, j--){
            swap(s[i], s[j]);
        }
	}
    
    string reverseWords(string s) {
		//1、將字串轉換為標準形式,去除空格
        deleteExtraSpaces(s);
        //2、字串整體反轉
        reverse(s, 0, s.size() - 1);//記得減1
        //3、恢復每個單詞的正常順序
        //依舊使用快慢指標
        int slow = 0;//start,fast即為end
        for(int fast = 0; fast <= s.size(); ++fast){
            if(fast == s.size() || s[fast] == ' '){//到達空格或者字串結尾倒要反轉,不然會漏翻
                reverse(s, slow, fast - 1);
                slow = fast + 1;//從下一個單詞的頭開始
            }
        }
        return s;
    }
};

正確的思考方式應該是想清楚思路之後,實現每個部分需要的函數,然後再一塊搭起來

注意點

1、for迴圈的特性

本題中大量且靈活的體現了for迴圈的一些平時容易忽略的特性

迴圈變數
for(int i = 0; i < xxx; i++){
    
}

上述是一個典型的for迴圈結構

要注意的是,開頭的這句for(int i = 0; i < xxx; i++)

它只對迴圈變數 i 進行初始化(賦初值),以及在每次{ }內程式碼執行完畢後對迴圈變數執行迴圈條件(即i++

至於{ }內發生了什麼,for迴圈本身是不管的。

也就是說,如果我在{ }內對迴圈變數 i進行了操作,也是允許的

例如:

for(int i = 0; i < xxx; i++){
    i = 5;
}//那麼下輪迴圈,i 就從6開始,而不是1

在本題中,大量運用了該特性,例如:擷取遍歷過程中的單詞

++ii++

如果你瞭解前++和後++的區別的話,那你應該知道後++的實現需要藉助一個臨時變數temp

實際上,在for的日常使用中,使用++i還是i++在結果上是沒有區別的

但是如果是刷題的話,++i的執行效率會好一些,僅此而已

詳見

2、TBD