算法 1.9.1 反轉(zhuǎn)字符串中的單詞 III【leetcode 557】

題目描述

給定一個字符串,你需要反轉(zhuǎn)字符串中每個單詞的字符順序,同時仍保留空格和單詞的初始順序。

示例:
? 輸入:"Let's take LeetCode contest"
? 輸出:"s'teL ekat edoCteeL tsetnoc"

提示:
? 在字符串中,每個單詞由單個空格分隔,并且字符串中不會有任何額外的空格。


數(shù)據(jù)結(jié)構(gòu)

  • 字符串、字符數(shù)組

算法思維

  • 遍歷、逆序、雙指針

解題要點

  • 如何優(yōu)化實現(xiàn)一個單詞中的字符反轉(zhuǎn)


解題思路


一. Comprehend 理解題意
  • 單詞的順序不變
  • 顛倒每個單詞內(nèi)部字符的順序

二. Choose 選擇數(shù)據(jù)結(jié)構(gòu)與算法
雙指針 解法
  • 數(shù)據(jù)結(jié)構(gòu):指針、字符串
  • 算法思維:遍歷、逆序、雙指針
解題思路
  1. 聲明雙指針和緩存
  2. 遍歷字符串
    ? 如果不是:讀到了字符串末尾 或者 讀到了空格
    ? ? 判斷首指針是否為空
    ? ? ? 如果為空就賦值成當(dāng)前位置
    ? ? 不管首指針是否為空,尾指針都和遍歷位置對齊
    ? 如果:讀到了末尾,或者讀到了空格
    ? ? 判斷首指針是否為空
    ? ? ? 如果不為空,就逆序遍歷首尾指針之間的所有字符,存入緩沖區(qū)
    ? ? ? 每讀完一個單詞都要添加一個空格,并且將首尾指針復(fù)原
  3. 最終去掉緩沖區(qū)末尾的空格后,作為字符串返回
邊界問題
  • 為了討論最后一個字符非空的情況,將邊界條件設(shè)為:i <= s.length()
細節(jié)問題
  • 需要非空判斷
  • 遍歷完整個字符串,需要判斷當(dāng)前首尾指針中間是都有數(shù)據(jù)
    如果有數(shù)據(jù),要額外寫入一次到緩存區(qū)中,避免丟失最后一個單詞

三. Code 編碼實現(xiàn)基本解法
class Solution {
    public String reverseWords(String s) {

        int len = s.length();

        //0.非空判斷
        if (len == 0) return "";

        //1.聲明雙指針和緩存
        int first = -1, last = -1;
        StringBuffer sb = new StringBuffer();

        char[] arr = s.toCharArray();
        //2.遍歷字符串
        for (int i = 0; i <= len; i++) {
            //3.如果不是讀到了字符串末尾,或者讀到了空格
            if (i != len && arr[i] != ' ') {
                //4.就判斷首指針是否為空,如果為空就賦值成當(dāng)前位置
                if (first == -1) {
                    first = i;
                }
                //5.不管首指針是否為空,尾指針都和遍歷位置對齊
                last = i;
            } else if (first != -1) { //6.如果讀到了末尾,或者讀到了空格,且首指針不為空
                //7.逆序遍歷首尾指針之間的所有字符,存入緩沖區(qū)
                while (last >= first) {
                    sb.append(arr[last]);
                    last--;
                }
                //8.每讀完一個單詞都要添加一個空格,并且將首尾指針復(fù)原
                sb.append(' ');
                first = -1;
                last = -1;
            }
        }

        //9.最終去掉緩沖區(qū)末尾的空格后,作為字符串返回
        return sb.substring(0,sb.length()-1);
    }
}

執(zhí)行耗時:8 ms,擊敗了 56.64% 的Java用戶
內(nèi)存消耗:39 MB,擊敗了 64.81% 的Java用戶
時間復(fù)雜度:O(n) -- 兩次字符串的遍歷 O(n)
空間復(fù)雜度:O(n) -- 一個緩沖區(qū) O(n),兩個指針 O(1)

四. Consider 思考更優(yōu)解
改變算法思維
  • 使用遍歷的方式反轉(zhuǎn)字符效率太低
  • 使用 StringBuffer 作為緩沖區(qū)存取數(shù)據(jù),消耗時間和空間
  • 題目給定了條件:“每個單詞由單個空格分隔”
    因此可以使用 雙指針 + 字符數(shù)組 的方式直接 原地反轉(zhuǎn) 字符

五. Code 編碼實現(xiàn)最優(yōu)解
優(yōu)化解法:原地反轉(zhuǎn)
class solution{
    //使用雙指針,原地反轉(zhuǎn)單詞
    public String reverseWords(String s) {
        if (s == null || s.length() == 0) {//邊界檢查
            return s;
        }
        char[] chars = s.toCharArray();
        int len = chars.length;
        //指向單詞的初始字符
        int start = 0;
        int end = 0;//指向單詞的結(jié)尾字符或者是終止索引
        //截取單詞進行 單詞翻轉(zhuǎn)
        for (int i = 0; i < len; i++) {
            if (chars[i] == ' ') {//如果查詢到了空格,一個單詞結(jié)束
                end = i - 1;
                reverse(chars, start, end);
                start = i + 1;//反轉(zhuǎn)單詞之后把start指向下一個單詞的起始
            } else if (i == len - 1) {//如果查詢到了數(shù)組最后一個字符,一個單詞結(jié)束
                reverse(chars, start, len - 1);
            }
        }
        return new String(chars);
    }

    //雙指針原地翻轉(zhuǎn)方法,給定反轉(zhuǎn)的源數(shù)組,給定起始索引和終止索引
    public void reverse(char[] s, int start, int end) {
        while (start < end) {//如果起始索引小于終止索引,進行反轉(zhuǎn)
            //交換雙指針對應(yīng)的字符
            char temp = s[start];
            s[start] = s[end];
            s[end] = temp;

            start++;
            end--;
        }
    }

}

執(zhí)行耗時:3 ms,擊敗了 99.88% 的Java用戶
內(nèi)存消耗:39.2 MB,擊敗了 34.70% 的Java用戶
時間復(fù)雜度:O(n) -- 字符串的遍歷 O(n)
空間復(fù)雜度:O(1) -- 字符數(shù)組 O(1),雙指針 O(1)

六. Change 變形與延伸

=== 待續(xù) ===

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容