LeetCode 周賽上分之旅 #46 經(jīng)典二分答案與質(zhì)因數(shù)分解

LeetCode 周賽 363

T1. 計(jì)算 K 置位下標(biāo)對應(yīng)元素的和(Easy)

  • 標(biāo)簽:位運(yùn)算

T2. 讓所有學(xué)生保持開心的分組方法數(shù)(Medium)

  • 標(biāo)簽:貪心吼旧、排序饱亿、計(jì)數(shù)排序

T3. 最大合金數(shù)(Medium)

  • 標(biāo)簽:二分查找

T4. 完全子集的最大元素和(Hard)

  • 標(biāo)簽:數(shù)學(xué)腌逢、質(zhì)因素分解萨西、散列表

T1. 計(jì)算 K 置位下標(biāo)對應(yīng)元素的和(Easy)

https://leetcode.cn/problems/sum-of-values-at-indices-with-k-set-bits/description/

題解(模擬)

簡單模擬題妄壶。

寫法 1:

class Solution {
    fun sumIndicesWithKSetBits(nums: List<Int>, k: Int): Int {
        var ret = 0
        for (i in nums.indices) {
            if (Integer.bitCount(i) == k) ret += nums[i]
        }
        return ret
    }
}

寫法 2:

class Solution {
    fun sumIndicesWithKSetBits(nums: List<Int>, k: Int): Int {
        return nums.indices.fold(0) { acc, it -> if (Integer.bitCount(it) == k) acc + nums[it] else acc}
    }
}

復(fù)雜度分析:

  • 時間復(fù)雜度:O(n) Java Integer#bitCount 的時間復(fù)雜度是 O(1)
  • 空間復(fù)雜度:O(1) 僅使用常數(shù)級別空間霞扬。

T2. 讓所有學(xué)生保持開心的分組方法數(shù)(Medium)

https://leetcode.cn/problems/happy-students/description/

問題分析

思考選哪個:

  • 條件 1: 如果選中的學(xué)生 nums[i] 越小佑笋,那么越容易滿足選中人數(shù) > nums[i]翼闹;
  • 條件 2: 如果未選中的學(xué)生 nums[i] 越大,那么越容易滿足選中人數(shù) < nums[i]蒋纬;

因此猎荠,在合法的選擇方案中,應(yīng)該優(yōu)先選擇越小的學(xué)生蜀备。

題解(排序 + 貪心)

先對數(shù)組排序关摇,再枚舉分割點(diǎn)驗(yàn)證條件 1 與條件 2:

6,0,3,3,6,7,2,7 

排序 => 

0,2,3,3,6,6,7,7
|0,2,3,3,6,6,7,7
0|2,3,3,6,6,7,7
0,2|3,3,6,6,7,7
0,2,3|3,6,6,7,7

對于分割點(diǎn) i 的要求是:

  • 條件 1:i + 1 > nums[i],利用有序性質(zhì)只需要判斷已選列表的最大值 nums[i]碾阁;
  • 條件 2:i + 1 < nums[i + 1]输虱,利用有序性質(zhì)只需要判斷未選列表的最小值 nums[i + 1]
  • 最后針對全選和都不選的情況特殊判斷脂凶。
class Solution {
    fun countWays(nums: MutableList<Int>): Int {
        nums.sort()
        val n = nums.size
        var ret = 0
        // 都不選
        if (nums[0] > 0) ret += 1
        // 都選
        if (nums[n - 1] < n) ret += 1
        // 選一部分
        for (i in 0 until n - 1) {
            if (nums[i] < i + 1 && nums[i + 1] > i + 1) ret += 1
        }
        return ret
    }
}

復(fù)雜度分析:

  • 時間復(fù)雜度:O(nlgn) 瓶頸在排序宪睹;
  • 空間復(fù)雜度:O(lgn) 排序遞歸棧空間蚕钦。

T3. 最大合金數(shù)(Medium)

https://leetcode.cn/problems/maximum-number-of-alloys/description/

問題分析

初步分析:

  • 問題目標(biāo): 求在預(yù)算限制下最大可以制造的合金數(shù)量亭病;
  • 關(guān)鍵信息: 所有合金都需要由同一臺機(jī)器制造,這樣難度就降低很多了嘶居。

容易發(fā)現(xiàn)原問題的單調(diào)性:

  • 如果合金數(shù) x 可以制造罪帖,那么合金數(shù) x - 1 一定可以制造;
  • 如果合金數(shù) x 不可制造邮屁,那么合金數(shù) x + 1 一定不可制造整袁。

因此,可以用二分答案來解決問題:

  • 合金數(shù)的下界:0
  • 合金數(shù)的上界:2 * 10^8樱报,即金錢和初始金屬的最大值葬项;

現(xiàn)在需要思考的問題是: 「如何驗(yàn)證合金數(shù) x 可以構(gòu)造」

由于所有合金都需要由同一臺機(jī)器制造,判斷很簡單迹蛤,只需要先計(jì)算目標(biāo)數(shù)量需要的每種金屬的初始金屬數(shù)是否足夠民珍,不足則花金錢購買襟士。如果花費(fèi)超過限制則不可制造。

題解(二分答案)

基于以上問下嚷量,我們枚舉機(jī)器陋桂,使用二分查找尋找可以制造的合金數(shù)的上界:

class Solution {
    fun maxNumberOfAlloys(n: Int, k: Int, limit: Int, composition: List<List<Int>>, stock: List<Int>, cost: List<Int>): Int {
        var ret = 0
        // 枚舉方案
        for (com in composition) {
            fun check(num: Int): Boolean {
                // 計(jì)算需要的金屬原料
                var money = 0L
                for (i in 0 until n) {
                    // 原料不足,需要購入
                    money += max(0L, 1L * com[i] * num - stock[i]) * cost[i] // 注意整型溢出
                    if (money > limit.toLong()) return false
                }
                return true
            }

            var left = 0
            var right = 2*1e8.toInt()
            while (left < right) {
                val mid = (left + right + 1) ushr 1
                if (check(mid)) {
                    left = mid
                } else {
                    right = mid - 1
                }
            }
            ret = max(ret, left)
        }
        return ret
    }
}

復(fù)雜度分析:

  • 時間復(fù)雜度:O(k·n·lgU) 其中 k 為機(jī)器數(shù)蝶溶,n 為金屬種類嗜历,U 為二分上界;
  • 空間復(fù)雜度:O(1) 除結(jié)果數(shù)組外僅使用常量級別空間抖所。

T4. 完全子集的最大元素和(Hard)

https://leetcode.cn/problems/maximum-element-sum-of-a-complete-subset-of-indices/description/

問題分析

初步分析:

  • 問題目標(biāo): 求解滿足條件的目標(biāo)子集的元素最大和梨州;
  • 目標(biāo)子集: 子集元素的下標(biāo)兩兩相乘的乘積是完全平方數(shù),允許僅包含一個元素的子集田轧;

觀察測試用例 2:

  • 對于下標(biāo) 1 和下標(biāo) 4:兩個完全平方數(shù)的乘積自然是完全平方數(shù)暴匠;
  • 對于下標(biāo) 2 和下標(biāo) 828 都包含質(zhì)因子 22 的平方自然是完全平方數(shù)傻粘;

由此得出結(jié)論:

  • 核心思路: 我們消除每個下標(biāo)中的完全平方數(shù)因子每窖,再對剩余的特征分組,能夠構(gòu)造目標(biāo)子集的方案有且只能出現(xiàn)在相同的特征分組中(否則弦悉,子集中一定存在兩兩相乘不是完全平方數(shù)的情況)窒典。
{2 | 6} x 需要相同的因子
{6 | 6} ok 

思考實(shí)現(xiàn):

  • 預(yù)處理: 預(yù)處理覆蓋所有測試用例下標(biāo)的特征值
  • 質(zhì)因素分解: 有 2 種基礎(chǔ)算法:

樸素算法:枚舉 [2, \sqrt{n}] 將出現(xiàn)次數(shù)為奇數(shù)的質(zhì)因子記錄到特征值中,時間復(fù)雜度是 O(\sqrt{n})

private val U = 1e4.toInt()
private val core = IntArray(U + 1)
init {
    for (num in 1 .. U) {
        // 質(zhì)因素分解
        var prime = 2
        var x = num
        var key = 1
        while (prime * prime <= x) {
            var cnt = 0
            while (x % prime == 0) {
                x /= prime
                cnt ++
            }
            if (cnt % 2 == 1) key *= prime // 記錄特征值
            prime ++
        }
        if (x > 1) key *= x // 記錄特征值
        core[num] = key
    }
}

篩法:枚舉質(zhì)因子稽莉,將記錄質(zhì)因子的整數(shù)倍的特征值瀑志。

private val U = 1e4.toInt()
private val core = IntArray(U + 1) { 1 }
private val isMark = BooleanArray(U + 1)
init {
    // 質(zhì)因素分解
    for (i in 2 .. U) {
        // 檢查是否為質(zhì)數(shù),這里不需要調(diào)用 isPrime() 函數(shù)判斷是否質(zhì)數(shù)肩祥,因?yàn)樗鼪]被小于它的數(shù)標(biāo)記過后室,那么一定不是合數(shù)
        if (isMark[i]) continue
        for (num in i .. U step i) {
            isMark[num] = true
            var x = num
            var cnt = 0
            while (x % i == 0) {
                x /= i
                cnt ++
            }
            if (cnt % 2 != 0) core[num] *= i // 記錄特征值
        }
    }
}

題解一(質(zhì)因素分解 + 分桶)

組合以上技巧缩膝,枚舉下標(biāo)做質(zhì)因數(shù)分解混狠,將數(shù)值累加到分桶中,最后返回最大分桶元素和疾层。

class Solution {

    companion object {
        private val U = 1e4.toInt()
        private val core = IntArray(U + 1)
        init {
            for (num in 1 .. U) {
                // 質(zhì)因素分解
                var prime = 2
                var x = num
                var key = 1
                while (prime * prime <= x) {
                    var cnt = 0
                    while (x % prime == 0) {
                        x /= prime
                        cnt ++
                    }
                    if (cnt % 2 == 1) key *= prime // 記錄特征值
                    prime ++
                }
                if (x > 1) key *= x // 記錄特征值
                core[num] = key
            }
        }
    }

    fun maximumSum(nums: List<Int>): Long {
        var ret = 0L
        val buckets = HashMap<Int, Long>()
        for (i in 1 .. nums.size) {
            val key = core[i]
            buckets[key] = buckets.getOrDefault(key, 0) + nums[i - 1]
            ret = max(ret, buckets[key]!!)
        }
        return ret
    }
}

復(fù)雜度分析:

  • 時間復(fù)雜度:預(yù)處理時間為 O(U\sqrt{U})将饺,單次測試用例時間為 O(n)
  • 空間復(fù)雜度:O(U) 預(yù)處理空間痛黎,單次測試用例空間比較松的上界為 O(n)予弧。

題解二(找規(guī)律)

題解一的時間復(fù)雜度瓶頸在之因素分解。

繼續(xù)挖掘數(shù)據(jù)特征湖饱,我們觀察同一個分桶內(nèi)的數(shù)據(jù)規(guī)律:

假設(shè)分桶中的最小值為 x掖蛤,那么將分桶的所有元素排序后必然是以下序列的子序列:{x, 4 * x, 9 * x, 16 * x…},由此發(fā)現(xiàn)規(guī)律:我們可以枚舉分桶的最小值井厌,再依次乘以完全平方數(shù)序列來計(jì)算蚓庭,既可以快速定位分桶中的元素致讥,而不需要預(yù)處理質(zhì)因數(shù)分解。

那怎么度量此算法的時間復(fù)雜度呢器赞?

顯然垢袱,該算法一個比較松上界是 O(n·C),其中 C 為數(shù)據(jù)范圍內(nèi)的完全平方數(shù)個數(shù)港柜,C = 100请契。嚴(yán)格證明參考羊神題解,該算法線性時間復(fù)雜度 O(n)夏醉。

class Solution {

    companion object {
        // 預(yù)處理完全平方數(shù)序列
        private val s = LinkedList<Int>()
        init {
            for (i in 1 .. 100) {
                s.add(i * i)
            }
        }
    }

    fun maximumSum(nums: List<Int>): Long {
        val n = nums.size
        var ret = 0L
        // 枚舉分桶最小值
        for (i in 1 .. n) {
            var sum = 0L
            for (k in s) {
                if (k * i > n) break
                sum += nums[k * i - 1]
            }
            ret = max(ret, sum)
        }
        return ret
    }
}

復(fù)雜度分析:

  • 時間復(fù)雜度:O(n) 線性算法爽锥;
  • 空間復(fù)雜度:O(C) 預(yù)處理完全平方數(shù)序列空間,可以優(yōu)化畔柔。

推薦閱讀

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末释树,一起剝皮案震驚了整個濱河市肠槽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奢啥,老刑警劉巖秸仙,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桩盲,居然都是意外死亡寂纪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門赌结,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捞蛋,“玉大人,你說我怎么就攤上這事柬姚∧馍迹” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵量承,是天一觀的道長搬设。 經(jīng)常有香客問我,道長撕捍,這世上最難降的妖魔是什么拿穴? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮忧风,結(jié)果婚禮上默色,老公的妹妹穿的比我還像新娘。我一直安慰自己狮腿,他們只是感情好腿宰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布弟蚀。 她就那樣靜靜地躺著,像睡著了一般酗失。 火紅的嫁衣襯著肌膚如雪义钉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天规肴,我揣著相機(jī)與錄音捶闸,去河邊找鬼。 笑死拖刃,一個胖子當(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
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡如迟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年收毫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殷勘。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡此再,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玲销,到底是詐尸還是另有隱情输拇,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布痒玩,位于F島的核電站淳附,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蠢古。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一别凹、第九天 我趴在偏房一處隱蔽的房頂上張望草讶。 院中可真熱鬧,春花似錦炉菲、人聲如沸堕战。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘱丢。三九已至薪介,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間越驻,已是汗流浹背汁政。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缀旁,地道東北人记劈。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像并巍,于是被迫代替她去往敵國和親目木。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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