LeetCode 周賽上分之旅 #33 摩爾投票派上用場

周賽 354

T1. 特殊元素平方和(Easy)

  • 標(biāo)簽:模擬、數(shù)學(xué)

T2. 數(shù)組的最大美麗值(Medium)

  • 標(biāo)簽:排序耕餐、二分查找、同向雙指針

T3. 合法分割的最小下標(biāo)(Medium)

  • 標(biāo)簽:數(shù)學(xué)辟狈、前后綴分解

T4. 最長合法子字符串的長度(Hard)

  • 標(biāo)簽:同向雙指針

T1. 特殊元素平方和(Easy)

https://leetcode.cn/problems/sum-of-squares-of-special-elements/

題解一(模擬)

簡單模擬題肠缔,枚舉每個下標(biāo)檢查是否能被 n 整除夏跷,同時記錄結(jié)果。

class Solution {
public:
    int sumOfSquares(vector<int>& nums) {
        int ret = 0;
        int n = nums.size();
        for (int i = 0; i < nums.size(); i++) {
            if (n % (i + 1) == 0) ret += nums[i] * nums[i];
        }
        return ret;
    }
};

復(fù)雜度分析:

  • 時間復(fù)雜度:O(n)
  • 空間復(fù)雜度:O(1)

題解二(模擬 + 優(yōu)化)

事實上明未,當(dāng)下標(biāo) i 可以被 n 整除時槽华,那么有下標(biāo) n / i 也可以被 n 整除,因此我們只需要檢查 [0, \sqrt(n)] 的范圍趟妥。

  • 1猫态、將 nums[0] 和 nums[n - 1] 的平方值添加到結(jié)果中(如果數(shù)組長度不大于 1,則不需要添加 nums[n - 1] 的影響)煮纵;
  • 2懂鸵、從 2 到 sqrt(n) 的范圍內(nèi)遍歷所有元素下標(biāo) i,如果 n 能夠被 i 整除行疏,那么我們將 nums[i-1] 的平方值和 nums[n/i-1] 的平方值分別添加到結(jié)果中(如果 i 和 n/i 相等匆光,我們只添加其中一個值,以避免重復(fù))酿联;
class Solution {
public:
    int sumOfSquares(vector<int>& nums) {
        int ret = nums[0] * nums[0];
        int n = nums.size();
        if (n < 2) return ret;
        ret += nums[n - 1] * nums[n - 1];
        for (int i = 2; i <= sqrt(n); i++) {
            if (n % i != 0) continue;
            ret += nums[i - 1] * nums[i - 1];
            if (i != n / i) {
                ret += nums[n / i - 1] * nums[n / i - 1];
            }
        }
        return ret;
    }
};

復(fù)雜度分析:

  • 時間復(fù)雜度:O(\sqrt(n))
  • 空間復(fù)雜度:O(1)

其他語言解法見 LeetCode 題解頁:枚舉優(yōu)化的 O(sqrt(n) 時間解法(C++/Python/Kotlin)


T2. 數(shù)組的最大美麗值(Medium)

https://leetcode.cn/problems/maximum-beauty-of-an-array-after-applying-operation/

題解一(排序 + 二分查找)

根據(jù)題目操作描述终息,每個元素都可以修改為范圍在 [nums[i] - k, nums[i] + k] 之間的任意元素,我們把兩個元素的差視為元素的相似度贞让,那么差值小于 2*k 的兩個數(shù)就能夠轉(zhuǎn)換為相等數(shù)(增大較小數(shù)周崭,同時減小較大數(shù))。

由于美麗值和數(shù)組順序無關(guān)喳张,我們先對數(shù)組排序续镇,然后枚舉元素作為左值,再尋找最遠(yuǎn)可匹配的右值(nums[i] + 2 * k)销部,可以使用二分查找尋找不大于右值的最大元素摸航。

class Solution {
public:
    int maximumBeauty(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int ret = 0;
        for (int i = 0; i < nums.size(); i++) {
            int left = i;
            int right = nums.size() - 1;
            while (left < right) {
                int mid = (left + right + 1) / 2;
                if (nums[mid] > nums[i] + 2 * k) {
                    right = mid - 1;
                } else {
                    left = mid;
                }
            }
            ret = max(ret, left - i + 1);
        }
        return ret;
    }
};

復(fù)雜度分析:

  • 時間復(fù)雜度:O(nlgn) 瓶頸在排序,模擬時間為 O(nlgn)舅桩;
  • 空間復(fù)雜度:O(lgn) 瓶頸在排序酱虎。

題解二(排序 + 同向雙指針)

根據(jù)題目操作描述,每個元素都可以修改為范圍在 [nums[i] - k, nums[i] + k] 之間的任意元素擂涛,我們把這個范圍視為一個可選區(qū)間读串。那么問題的最大美麗值正好就是所有區(qū)間的最多重疊數(shù),這就是經(jīng)典的 leetcode 253. 會議室 II 問題

由于區(qū)間重疊數(shù)和順序無關(guān)撒妈,我們可以對所有元素排序(由于區(qū)間長度相等恢暖,等價于按照結(jié)束時間排序),使用同向雙指針求解:

  • 維護重疊區(qū)間的左右指針 i 和 j
  • 如果當(dāng)前區(qū)間 [j] 與左指針指向的區(qū)間不重疊踩身,則將左指針 i 向右移動胀茵,并記錄最大重疊數(shù)
class Solution {
public:
    int maximumBeauty(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int i = 0;
        int ret = 0;
        for (int j = 0; j < nums.size(); j++) {
            while (nums[j] - k > nums[i] + k) i++;
            ret = max(ret, j - i + 1);
        }
        return ret;
    }
};

復(fù)雜度分析:

  • 時間復(fù)雜度:O(nlgn) 瓶頸在排序,同向雙指針模擬時間為 O(n)挟阻;
  • 空間復(fù)雜度:O(lgn) 瓶頸在排序。

其他語言解法見 LeetCode 題解頁:會議室問題求最大重疊區(qū)間數(shù)、同向雙指針(C++/Python/Kotlin/TypeScript)


T3. 合法分割的最小下標(biāo)(Medium)

https://leetcode.cn/problems/minimum-index-of-a-valid-split/

題解一(數(shù)學(xué) + 前后綴分解)

根據(jù)題目描述附鸽,支配元素是指數(shù)組中的眾數(shù)脱拼,同時要求出現(xiàn)次數(shù)嚴(yán)格大于數(shù)組一半長度,所以支配元素可能是 -1坷备。其實熄浓,支配元素的定義與經(jīng)典題目 169. 多數(shù)元素劍指 Offer 39. 數(shù)組中出現(xiàn)次數(shù)超過一半的數(shù)字 定義是相同的。

容易證明省撑,無論數(shù)組如何分割赌蔑,子數(shù)組的支配元素要么不存在,要么就等于原數(shù)組的支配元素:

  • 假設(shè) cnt1 是左子數(shù)組的支配元素竟秫,cnt2 是右子數(shù)組的支配元素娃惯,那么右 cnt1 * 2 > len1 且 cnt2 * 2 > len2;
  • 由于兩個子數(shù)組的支配元素相同肥败,且滿足兩式相加右 (cnt1 + cnt2) * 2 > (len1 + len2)趾浅,說明子數(shù)組的支配元素與原數(shù)組相同。

因此馒稍,我們的算法是:

  • 計算原數(shù)組的支配元素
  • 并從左到右枚舉分割點皿哨,并記錄支配元素在左右子數(shù)組中的個數(shù),當(dāng)左右子數(shù)組中支配元素的數(shù)量條件成立時纽谒,返回下標(biāo)证膨。
class Solution {
public:
    int minimumIndex(vector<int>& nums) {
        // 計算支配元素
        unordered_map<int, int> cnts;
        int x = -1;
        for (int i = 0; i < nums.size(); i++) {
            ++cnts[nums[i]];
            if (x == -1 || cnts[nums[i]] > cnts[x]) {
                x = nums[i];
            }
        }
        // 枚舉分割點
        int leftXCnt = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] != x) continue;
            leftXCnt++;
            if (leftXCnt * 2 > i + 1 && (cnts[x] - leftXCnt) * 2 > nums.size() - 1 - i) return i;
        }
        return -1;
    }
};

復(fù)雜度分析:

  • 時間復(fù)雜度:O(n) 求支配元素和枚舉分割點的時間復(fù)雜度都是 O(n)
  • 空間復(fù)雜度:O(n) 散列表空間鼓黔。

題解二(摩爾投票優(yōu)化)

題解一中使用散列表求原數(shù)組的支配元素央勒,可以使用摩爾投票算法來優(yōu)化空間復(fù)雜度:

  • 我們將眾數(shù)的權(quán)重視為 +1,把其他數(shù)視為 -1请祖。
  • 首先我們維護一個候選數(shù) 订歪,然后遍歷數(shù)組的每個元素,如果 count == 0肆捕,說明它在當(dāng)前的權(quán)重最大刷晋,那么將它記為 candidate,對于接下來的元素慎陵,如果它等于 candidate眼虱,則 count ++,否則 count--席纽。
  • 最終得到的 candidate 就是眾數(shù)捏悬。
class Solution {
public:
    int minimumIndex(vector<int>& nums) {
        // 計算支配數(shù)
        int x = -1;
        int count = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (0 == count) x = nums[i];
            if (nums[i] == x) count++; else count --;
        }
        // 計算支配數(shù)出現(xiàn)次數(shù)
        int total = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == x) total ++;
        }
        // 枚舉分割點
        int leftXCnt = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] != x) continue;
            leftXCnt++;
            if (leftXCnt * 2 > i + 1 && (total - leftXCnt) * 2 > nums.size() - 1 - i) return i;
        }
        return -1;
    }
};

復(fù)雜度分析:

  • 時間復(fù)雜度:O(n) 求支配元素和枚舉分割點的時間復(fù)雜度都是 O(n)
  • 空間復(fù)雜度:O(1) 僅使用常量級別空間润梯。

其他語言解法見 LeetCode 題解頁:數(shù)學(xué)过牙、前后綴分解甥厦、摩爾投票 O(1) 空間(C++/Python/Kotlin)


T4. 最長合法子字符串的長度(Hard)

https://leetcode.cn/problems/length-of-the-longest-valid-substring/

題解一(暴力枚舉子串· 超出時間限制)

這道題中 forbidden[i] 字符串的長度不超過 10,說明檢查字符串匹配的時間常數(shù)是比較低的寇钉,我們先考慮暴力的解法刀疙。

  • 使用同向雙指針 i 和 j 枚舉子串,并檢查該子串是否合法扫倡;
  • 由于在內(nèi)存循環(huán)中移動 j 指針只是在 [i, j - 1] 的基礎(chǔ)上增加字符 nums[j]谦秧,所以在檢查的時候僅需要檢查 [i, j] 范圍中,以 nums[j] 為結(jié)尾的子字符串是否被禁用撵溃。同時疚鲤,由于 forbidden[i] 的最大長度為 10,所以在檢查時只需要檢查長度不超過 10 的子串缘挑。
class Solution {
    fun longestValidSubstring(word: String, forbidden: List<String>): Int {
        val forbiddenSet = forbidden.toHashSet()
        var ret = 0
        for (i in 0 until word.length) {
            for (j in i until word.length) {
                if (!check(forbiddenSet, word, i, j)) break // 后續(xù)子串不可能合法
                ret = Math.max(ret, j - i + 1)
            }
        }
        return ret
    }

    // return:是否合法
    private fun check(set: Set<String>, word: String, i: Int, j: Int): Boolean {
        // 檢查 [i,j] 中以新增字母 nums[j] 為右端點的所有子串方案是否被禁用
        for (k in j downTo i) {
            val key = word.substring(k, j + 1)
            if (set.contains(key)) return false
        }
        return true
    }
}

復(fù)雜度分析:

  • 時間復(fù)雜度:O(L + n^2·M^2) 構(gòu)造 forbiddenSet 散列表的時間復(fù)雜度為 O(L)集歇,其中 L 為 forbidden 中所有字符的總長度。枚舉子串的個數(shù)為 n^2卖哎,而檢查子串是否合法的時間復(fù)雜度是 O(M^2)鬼悠,其中 n 是 word 字符串的長度,而 M 是子串的最大長度亏娜,M = 10焕窝,因此枚舉階段的時間復(fù)雜度是 O(n^2·M^2)
  • 空間復(fù)雜度:O(L) 散列表空間维贺。

提示:我們可以使用滾動哈希優(yōu)化 check 的時間復(fù)雜度到 O(M)它掂,但由于 M 本身很小,優(yōu)化效果不高溯泣。

題解二(同向雙指針)

這道題需要結(jié)合 KMP 思想虐秋。

題解一中的 check 會重復(fù)計算多次子串,需要想辦法剪枝:

  • 由于我們是求最長子串垃沦,所以 [i + 1, j] 的結(jié)果不會由于 [i, j] 的結(jié)果客给。這說明了,如果 [i, j] 中存在不合法的子串肢簿,那么移動 i 指針 + 1 后再去重新枚舉 j 指針靶剑,不可能獲得更優(yōu)解,完全沒有必要枚舉 i 指針池充,只需要在 [i, j] 不合法的時候移動 i 指針 + 1桩引;
  • 同時,在 check 函數(shù)中最早出現(xiàn)的非法子串位置收夸,可以加快收縮 i 指針坑匠,直接將 i 指針指向最早出現(xiàn)的非法子串位置 + 1。
class Solution {
    fun longestValidSubstring(word: String, forbidden: List<String>): Int {
        // word = "leetcode", forbidden = ["de","le","e"]
        val forbiddenSet = forbidden.toHashSet()
        var ret = 0
        var i = 0
        for (j in 0 until word.length) {
            // 不合法
            while (true) {
                val pivot = check(forbiddenSet, word, i, j)
                if (-1 != pivot) i = pivot + 1 else break
            }
            ret = Math.max(ret, j - i + 1)
        }
        return ret
    }

    // return:最早的非法子串的起始位置
    private fun check(set: Set<String>, word: String, i: Int, j: Int): Int {
        // 檢查 [i,j] 中以新增字母 nums[j] 為右端點的所有子串方案是否被禁用
        for (k in Math.max(i, j - 10) .. j) {
            val key = word.substring(k, j + 1)
            if (set.contains(key)) return k
        }
        return -1
    }
}

復(fù)雜度分析:

  • 時間復(fù)雜度:O(L + n·M^2) check 函數(shù)最多僅調(diào)用 n 次卧惜;
  • 空間復(fù)雜度:O(L) 散列表空間厘灼。

推薦閱讀

LeetCode 上分之旅系列往期回顧:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末手幢,一起剝皮案震驚了整個濱河市捷凄,隨后出現(xiàn)的幾起案子忱详,更是在濱河造成了極大的恐慌围来,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匈睁,死亡現(xiàn)場離奇詭異监透,居然都是意外死亡,警方通過查閱死者的電腦和手機航唆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門胀蛮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人糯钙,你說我怎么就攤上這事粪狼。” “怎么了任岸?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵再榄,是天一觀的道長。 經(jīng)常有香客問我享潜,道長困鸥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任剑按,我火速辦了婚禮疾就,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艺蝴。我一直安慰自己猬腰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布猜敢。 她就那樣靜靜地躺著姑荷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锣枝。 梳的紋絲不亂的頭發(fā)上厢拭,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音撇叁,去河邊找鬼供鸠。 笑死,一個胖子當(dāng)著我的面吹牛陨闹,可吹牛的內(nèi)容都是我干的楞捂。 我是一名探鬼主播薄坏,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寨闹!你這毒婦竟也來了胶坠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤繁堡,失蹤者是張志新(化名)和其女友劉穎沈善,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椭蹄,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡闻牡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绳矩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罩润。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翼馆,靈堂內(nèi)的尸體忽然破棺而出割以,到底是詐尸還是另有隱情,我是刑警寧澤应媚,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布严沥,位于F島的核電站,受9級特大地震影響珍特,放射性物質(zhì)發(fā)生泄漏祝峻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一扎筒、第九天 我趴在偏房一處隱蔽的房頂上張望莱找。 院中可真熱鬧,春花似錦嗜桌、人聲如沸奥溺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浮定。三九已至,卻和暖如春层亿,著一層夾襖步出監(jiān)牢的瞬間桦卒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工匿又, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留方灾,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像裕偿,于是被迫代替她去往敵國和親洞慎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容