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=25
,0-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;
};
那麼本題就說到這裡了