1. 程式人生 > 其它 >JS Leetcode 1370. 上升下降字串 題解分析,桶排序與charCodeAt fromCharCode妙用

JS Leetcode 1370. 上升下降字串 題解分析,桶排序與charCodeAt fromCharCode妙用

壹 ❀ 引

本題來自LeetCode1370. 上升下降字串,難度簡單,是一道考察對於字串遍歷熟練度的題目,題目描述如下:

給你一個字串 s ,請你根據下面的演算法重新構造字串:

從 s 中選出 最小 的字元,將它 接在 結果字串的後面。
從 s 剩餘字元中選出 最小 的字元,且該字元比上一個新增的字元大,將它 接在 結果字串後面。
重複步驟 2 ,直到你沒法從 s 中選擇字元。
從 s 中選出 最大 的字元,將它 接在 結果字串的後面。
從 s 剩餘字元中選出 最大 的字元,且該字元比上一個新增的字元小,將它 接在 結果字串後面。
重複步驟 5 ,直到你沒法從 s 中選擇字元。
重複步驟 1 到 6 ,直到 s 中所有字元都已經被選過。
在任何一步中,如果最小或者最大字元不止一個 ,你可以選擇其中任意一個,並將其新增到結果字串。

請你返回將 s 中字元重新排序後的 結果字串 。

示例 1:

輸入:s = "aaaabbbbcccc"
輸出:"abccbaabccba"

解釋:第一輪的步驟 1,2,3 後,結果字串為 result = "abc"
第一輪的步驟 4,5,6 後,結果字串為 result = "abccba"
第一輪結束,現在 s = "aabbcc" ,我們再次回到步驟 1
第二輪的步驟 1,2,3 後,結果字串為 result = "abccbaabc"
第二輪的步驟 4,5,6 後,結果字串為 result = "abccbaabccba"
示例 2:

輸入:s = "rat"
輸出:"art"

解釋:單詞 "rat" 在上述演算法重排序以後變成 "art"
示例 3:

輸入:s = "leetcode"
輸出:"cdelotee"

示例 4:

輸入:s = "ggggggg"
輸出:"ggggggg"
示例 5:

輸入:s = "spo"
輸出:"ops"

提示:

1 <= s.length <= 500
s 只包含小寫英文字母。

讓我們簡單分析題意,然後實現它。

貳 ❀ 桶排序與字串常規API

題目要求其實並不複雜,給定一個全部是小寫字母的字串,然後對此字串進行取值拼接操作。我們先取當前字串中的最小字元(每個字元只能使用一次),拼到一個空字串上,然後繼續找第二小的字串,繼續拼接操作。比如aab

第一次取最小是a,那麼第二次取最小時其實取得是b,因為這裡的最小的定義是比上次大但在剩餘字串中最小。直到取不到最小之後,我們又開始取剩餘字串最大的字元,繼續拼接操作,然後取第二大的字元繼續拼接,當找不到符合條件的字串之後,再執行前面的取最小操作,直到字串被取空。

aabbcc為例,它的過程其實就是這樣:

因為題目說明只包含小寫字母,因此可能存在的字元情況一共也就16種,我們完全可以統計出a-z每個字元的出現頻率,然後取最小就從左往右遍歷陣列取,取一個記得把這個字串的數字減一,遍歷到頭後說明取小操作結束。緊接著從右往左遍歷,開始取最大操作,重複上述操作,直到沒有字元可取為止。

因此到這裡我們就可以使用桶排序,我們可以建立一個長度為26的陣列,陣列下標0表示a的位置,小標1表示字母b的位置,以這種方式來把所有字母次數都統計一次,具體怎麼做呢?

字串中有一個API叫charCodeAt,它用於獲取一個字元的Unicode編碼,比如字母a的編碼為:

'a'.charCodeAt(0);//97
'z'.charCodeAt(0);//122

你會發現122-97=250-25一共正好26個數字,我們可以用每個字串的編碼減去97,比如'a'.charCodeAt(0)-97 = 0,它不就正好對應了陣列下標0的位置,通過這種方式,我們就可以得到每個字元出現的次數,以及有序的與陣列下標對應了。

問題又來了,假設我們得到了最終陣列[2,2,2],其實就是abc分別都出現了2次,當我們遍歷到陣列某個下標,知道它的值大於0,那就表示還有可以使用的字母,但是我們將這個陣列的下標反向解析成字串呢?其實與charCodeAt對應還有個API叫fromCharCode,它能將一個Unicode碼反向解析成字串,比如:

String.fromCharCode(97+0);//a
String.fromCharCode(97+1);//b

而上述程式碼所加的數字,其實就是我們遍歷到的當前的下標 i 。

OK,解釋了這些,我們可以來實現這段程式碼:

/**
 * @param {string} s
 * @return {string}
 */
var sortString = function (s) {
    // 建立一個有26個位置的空桶
    const bucket = new Array(26).fill(0);
    for (let i = 0; i < s.length; i++) {
        // 統計每個字母出現的次數
        bucket[s.charCodeAt(i) - 97]++;
    };
    let res = '';
    let len = s.length;
    while (len > 0) {
        // 取最小操作
        for (let i = 0; i < 26; i++) {
            // 如果有值,說明有字母可以使用
            if (bucket[i] > 0) {
                // 拼接字串
                res += (String.fromCharCode(i + 97));
                // 字串都是一次性的,用了得減掉
                bucket[i]--;
                // 我們得保證整體的字串長度也在遞減,這樣才知道什麼時候字串全部用完了。
                len--;
            }
        }
        //取最大操作
        for (let i = 25; i >= 0; i--) {
            if (bucket[i] > 0) {
                res += (String.fromCharCode(i + 97));
                bucket[i]--;
                len--;
            }
        }
    }
    return res;
};

那麼本題就說到這裡了