「LeetCode-比較字符串最小字母出現頻次」代碼優化記錄

原由

我昨天刷到一道 LeetCode,成功提交後發現執行時間仍是有點兒太長,才戰勝了 5% 的 JS 用戶,這可不行,我得自我探索和突破一下,必定是我算法和寫法太 low 了!算法

那道題目是這樣的: 數組

(圖片)1170. 比較字符串最小字母出現頻次
想動手試試的能夠點這裏: leetcode 題目 - 1170. 比較字符串最小字母出現頻次

個人思路是這樣,很常規:bash

  1. 寫一個函數用來計算最小字母的出現頻次函數

  2. 新建一個空數組 answer和計數變量 time性能

  3. 雙重遍歷 querieswords ,分別對這兩個數組的數組項進行頻次的計算,進行對比後,符合條件的則time+1,遍歷完內層後,把最後的計數值推入 answer 中,遍歷完外層後,返回 answer測試

如下是個人代碼實現:優化

/**
 * @param {string[]} queries
 * @param {string[]} words
 * @return {number[]}
 */
var numSmallerByFrequency = function (queries, words) {
    const answer = []
    
    // 雙重遍歷開始
    for (let i = 0; i < queries.length; i++) {
    
        // 計數器 time 用來記錄 queries[i] 與 words 數組每項對比後符合條件的個數
        let times = 0
        
        // 計算 queries[i] 最小字母重複出現的頻次
        const queries_statisticsFrequency = statisticsFrequency(queries[i])
        
        for (let j = 0; j < words.length; j++) {
            
            // 計算 words[j] 最小字母重複出現的頻次
            const words_statisticsFrequency = statisticsFrequency(words[j])
            
            if (queries_statisticsFrequency < words_statisticsFrequency) {
                times += 1
            }
        }
        answer.push(times)
    }
    return answer
};

/**
 * 此函數用來求得最小字母的出現頻次
 * 1. 先將字符串 s 轉爲數組,並按字母順序排列
 * 2. 遍歷數組,求得最小字母重複出現的頻次,並返回頻次數
 */
var statisticsFrequency = function (s) {

    let frenquencyObj = {}
    const arrayFromS = s.split('').sort()

    for(let i=0;i<arrayFromS.length;i++){
        const value = frenquencyObj[arrayFromS[i]]
        frenquencyObj[arrayFromS[i]] = value ? value+1 : 1
        
        /** 若是下一個字母和目前的這個字母不同,
         *  則說明最小字母重複出現的頻次已累計完畢,能夠直接返回值了
         */
        if(arrayFromS[i] !== arrayFromS[i+1]){
            return frenquencyObj[arrayFromS[0]]
        }
    }
}
複製代碼

提交了三次,兩次失敗,失敗緣由是某些測試用例超過了時間限制,我用了一些方案修改後提交第三次才成功:ui

  1. 將字符串轉爲數組時,使用 s.split('') 比使用 Array.from(s) 更加高效。
  2. 一開始使用 Array.map 進行循環,改爲 Array.forEach 後更加高效。
  3. 使用 for 循環來替代了 Array.forEach,這只是爲了優化算法,在符合某種條件的狀況下,不須要繼續循環,使用 for 能夠跳出循環體。
  4. 一開始是在雙重循環裏直接拿 statisticsFrequency(queries[i])statisticsFrequency(words[j]) 寫在 if 語句裏進行對比,致使每次都要重複計算這兩個值。後來把值提到 if 以外計算,並賦值給變量,拿變量進行對比,也節省了一些時間。

優化

如何找出字符串中最小的字符出現的次數

/**
 * 此函數用來求得最小字母的出現頻次
 * 1. 先將字符串 s 轉爲數組,並按字母順序排列
 * 2. 遍歷數組,求得最小字母重複出現的頻次,並返回頻次數
 */
var statisticsFrequency = function (s) {
    const arrayFromS = s.split('').sort()
    let time = 0

    for (let i = 0; i < arrayFromS.length; i++) {
        time += 1
        if (arrayFromS[i] !== arrayFromS[i + 1]) {
            return time
        }
    }
}
複製代碼

這麼一改,終於能夠戰勝 7.69% 的 JS 用戶了。spa

回想一下,原來爲何還要弄個對象來保存?code

用對象來保存,能夠知道最小的字符是什麼,一共出現了幾回。可是這道題其實並無這個必要,不須要知道什麼是最小的字符。

除了雙重遍歷,是否有更好的方法呢?

  1. 先分別對兩個數組元素計算出最小字符出現的頻次,存入到新數組(queriesCount / wordsCount)中
  2. wordsCount 中的數組進行降序排列
  3. 嘗試使用二分法,進行遍歷對比:拿 queriesCount 的數組項和被一次次二分的 wordsCount 進行對比
/**
 * @param {string[]} queries
 * @param {string[]} words
 * @return {number[]}
 */
var numSmallerByFrequency = function (queries, words) {
    const answer = []

    // 計算 queries 數組元素最小字符出現的頻次,並存到一個新數組中
    const queriesCount = queries.map(item => {
        return statisticsFrequency(item)
    })

    // 計算 words 數組元素最小字符出現的頻次,降序排列後,存到一個新數組中
    const wordsCount = words.map(item => {
        return statisticsFrequency(item)
    }).sort(compare).reverse()

    // 利用二分法,進行遍歷對比
    queriesCount.forEach(item => {
        let front = 0,
            end = wordsCount.length,
            mid
        while (front <= end) {

            mid = parseInt((front + end) / 2)
            
            if (mid !== front && mid !== end) {
                // 二分法
                if (item >= wordsCount[mid]) {
                    end = mid
                } else {
                    front = mid
                }
            }
            // 當查找 wordsCount[0] 時,若是 item >= wordsCount[mid],說明沒有符合小於條件的狀況,應該返回 0
            else if(mid === 0 && item >= wordsCount[mid]) {
                answer.push(0)
                return
            }
            else {
                // 因爲 mid 記錄的實際上是數組的下標,下標從 0 開始,因此這裏返回時應該 +1
                answer.push(mid + 1)
                return
            }
        }
    })
    return answer
};

// 用於數組項排序
var compare = function (a,b){
    return a-b
}
複製代碼

這下子好了,擊敗了 80.77% 的 JS 用戶了!

延伸

說明:如下代碼並無通過大量的測試用例測試以及性能測試

如何找出字符串中出現次數最多的字符

例如:

  1. 輸入 'abfeigeaabjgeba',結果應該返回 a。解釋:由於 a 出現了 4 次,其餘字符出現的次數都小於 4 次。
  2. 輸入 'bfeigeaabjgeba',結果應該返回 abe。解釋由於 abe 都出現了 3 次,其餘字符出現的次數都小於 3 次。

代碼實現以下:

var maxTimeCharacter = function (s) {
    let frenquencyObj = {},
        maxTime = 0,
        character = ''
    const arrayFromS = s.split('').sort()

    arrayFromS.forEach(item => {
        // 把字符當作 key,把出現的次數當作 value
        let value = frenquencyObj[item]
        frenquencyObj[item] = value ? (++value) : 1

        if (maxTime <= value) {
            maxTime = value
            if (character !== item) {
                character += item
            } else {
                character = item
            }
        }
    })
    return character
}
複製代碼

如何找出字符串中出現次數大於 1 且最小的字符

例如:

  1. 輸入 'abfeigeaabjgeba',結果應該返回 a。解釋:由於 a 出現了 4 次,其餘字符出現的次數都小於 4 次。
  2. 輸入 'bfeigeaabjgeba',結果應該返回 a。解釋由於 abe 都出現了 3 次,其餘字符出現的次數都小於 3 次,可是 a 字符是其中最小的字符

代碼實現以下:

var moreTimeCharacter = function (s) {
    let frenquencyObj = {},
        maxTime = 0,
        character = ''
    const arrayFromS = s.split('').sort()

    arrayFromS.forEach(item => {
        // 把字符當作 key,把出現的次數當作 value
        let value = frenquencyObj[item]
        frenquencyObj[item] = value ? (++value) : 1

        if (maxTime <= value) {
            maxTime = value
            if (character !== item) {
                character += item
            } else {
                character = item
            }
        }
    })
    return character.charAt(0)
}
複製代碼
相關文章
相關標籤/搜索