LeetCode 周賽上分之旅 #38 結(jié)合排序不等式的動(dòng)態(tài)規(guī)劃

雙周賽 110

T1. 取整購買后的賬戶余額(Easy)

  • 標(biāo)簽:模擬

T2. 在鏈表中插入最大公約數(shù)(Medium)

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

T3. 使循環(huán)數(shù)組所有元素相等的最少秒數(shù)(Medium)

  • 標(biāo)簽:貪心阅悍、散列表

T4. 使數(shù)組和小于等于 x 的最少時(shí)間(Hard)

  • 標(biāo)簽:排序不等式好渠、動(dòng)態(tài)規(guī)劃、貪心

T1. 取整購買后的賬戶余額(Easy)

https://leetcode.cn/problems/insert-greatest-common-divisors-in-linked-list/

題解(模擬)

閱讀理解題节视。

其實(shí)就是將 purchaseAmount 向最近的 10 的倍數(shù)四舍五入拳锚,再用 100 減去它。

class Solution {
    fun accountBalanceAfterPurchase(purchaseAmount: Int): Int {
        return 100 - (purchaseAmount + 5) / 10 * 10 
    }
}

復(fù)雜度分析:

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

T2. 在鏈表中插入最大公約數(shù)(Medium)

https://leetcode.cn/problems/insert-greatest-common-divisors-in-linked-list/

題解(數(shù)學(xué))

久違的鏈表題寻行。

題目相對簡單霍掺,其實(shí)就是依次處理每兩個(gè)節(jié)點(diǎn),并插入一個(gè)新的最大公約數(shù)節(jié)點(diǎn)拌蜘。以下提供兩個(gè)寫法:

  • 構(gòu)造新鏈表:
class Solution {
    fun insertGreatestCommonDivisors(head: ListNode?): ListNode? {
        val dummy = ListNode(-1)
        var rear = dummy
        var p = head
        while (null != p) {
            rear.next = p
            rear = p
            val next = p.next
            if (null != next) {
                val newNode = ListNode(gcb(p.`val`, next.`val`))
                newNode.next = next
                p.next = newNode
                rear.next = newNode
                rear = newNode
            }
            p = next
        }
        return dummy.next
    }

    private fun gcb(a:Int, b:Int) :Int {
        var x = a
        var y = b
        while (y != 0) {
            val temp = x % y
            x = y
            y = temp
        }
        return x
    }
}
  • 在原鏈表上插入:
class Solution {
    fun insertGreatestCommonDivisors(head: ListNode?): ListNode? {
        var p = head
        while (null != p?.next) {
            val next = p.next
            val newNode = ListNode(gcb(p.`val`, next.`val`))
            newNode.next = next
            p.next = newNode
            p = next
        }
        return head
    }

    private fun gcb(a:Int, b:Int) :Int {
        var x = a
        var y = b
        while (y != 0) {
            val temp = x % y
            x = y
            y = temp
        }
        return x
    }
}

復(fù)雜度分析:

  • 時(shí)間復(fù)雜度:O(nlgU) 其中單次最大公約數(shù)的計(jì)算時(shí)間復(fù)雜度為 O(lgU)杆烁,U 為數(shù)值上界;
  • 空間復(fù)雜度:O(1) 不考慮輸出空間简卧。

T3. 使循環(huán)數(shù)組所有元素相等的最少秒數(shù)(Medium)

https://leetcode.cn/problems/minimum-seconds-to-equalize-a-circular-array/

題解(貪心 + 散列表)

根據(jù)題目要求兔魂,我們可以通過將數(shù)字復(fù)制到相鄰位置上,以實(shí)現(xiàn)數(shù)組中所有元素都相等举娩。因此入热,如果我們選擇數(shù)字 x 為最終元素,那么決定替換秒數(shù)的關(guān)鍵在與數(shù)組中不等于 x 的最長子數(shù)組長度晓铆。

所以,我們的算法是計(jì)算以每種數(shù)字 x 為目標(biāo)的方案中绰播,最短的不等于 x 的最長子數(shù)組長度骄噪,并除以 2 向上取整的到結(jié)果。

class Solution {
    fun minimumSeconds(nums: List<Int>): Int {
        // 最大間隔的最小值
        val n = nums.size
        // lens:記錄每種數(shù)字的最長間隔
        val lens = HashMap<Int, Int>()
        // preIndexs:記錄每種數(shù)字的上次出現(xiàn)位置
        val preIndexs = HashMap<Int, Int>()
        // 記錄最后出現(xiàn)位置(環(huán)形數(shù)組邏輯)
        for ((i, e) in nums.withIndex()) {
            preIndexs[e] = i
        }
        for ((i, e) in nums.withIndex()) {
            lens[e] = Math.max(lens.getOrDefault(e, 0), (i - preIndexs[e]!! - 1 + n) % n)
            preIndexs[e] = i
        }
        var ret = n
        for ((_, len) in lens) {
            ret = Math.min(ret, (len + 1) / 2)
        }
        return ret
    }
}

復(fù)雜度分析:

  • 時(shí)間復(fù)雜度:O(n) 線性遍歷蠢箩;
  • 空間復(fù)雜度:O(n) 散列表空間链蕊。

T4. 使數(shù)組和小于等于 x 的最少時(shí)間(Hard)

https://leetcode.cn/problems/minimum-time-to-make-array-sum-at-most-x/

題解(DP + 排序不等式)

  • 時(shí)間的上界: 假設(shè)題目的最少時(shí)間超過數(shù)組長度 n事甜,那么根據(jù)抽屜原理必然有某個(gè)位置重復(fù)置零兩次,那么第一次操作的貢獻(xiàn)就丟失了滔韵,因此逻谦,題目的時(shí)間上界不應(yīng)該超過 n,即每個(gè)位置最多置零一次陪蜻;
  • 二分答案(X): 數(shù)組元素和小于等于 x 與操作時(shí)間 t 不具備單調(diào)性邦马,因此不能使用二分答案的思路;
  • 逆向思維: 令 s1 = sum(nums1), s2 = sum(nums2)宴卖,假設(shè)經(jīng)過 t 時(shí)間且不進(jìn)行任何操作滋将,那么元素總和將變成 s1 + s2 *t。現(xiàn)在需要從 [0, n-1] 中非重復(fù)地選擇 t 個(gè)位置症昏,假設(shè)在第 x 秒選擇位置 [i]随闽,那么對最終元素總和減少的貢獻(xiàn)度為 nums1[i] + x·nums2[i]。
  • 排序不等式: 現(xiàn)在的問題是「選擇哪些數(shù)」以及「如何分配選擇時(shí)間」使得減少的貢獻(xiàn)度盡可能大:假設(shè)選擇位置 [i]肝谭、[j] 和 [k]掘宪,那么貢獻(xiàn)度為:
    • nums1[i] + nums2[i] * x
    • nums1[j] + nums2[j] * y
    • nums1[k] + nums2[k] * z
    • 無論如何分配,加法左邊的貢獻(xiàn)度是恒定的攘烛,問題關(guān)鍵在與如何使得加法右邊的貢獻(xiàn)度盡可能大魏滚;
    • 直觀地觀察,容易想到應(yīng)該將元素值更大的元素分配到更靠后的位置上医寿,使其置零時(shí)貢獻(xiàn)更多栏赴;
    • 驗(yàn)證證明可以根據(jù) 排序不等式 ,假設(shè)有兩組有序序列 a 和 b靖秩,每一項(xiàng)正序相乘并累加的和是最大的须眷。
  • 動(dòng)態(tài)規(guī)劃(選哪個(gè)): 定義 dp[i][j] 表示到第 [i] 個(gè)元素為止操作 j 次時(shí)的最大貢獻(xiàn)度
    • 目標(biāo):滿足 dp[n][j] 小于等于 x 的最小 j 值
    • 狀態(tài)轉(zhuǎn)移方程(選和不選):dp[i][j] = max{dp[i - 1][j], dp[i - 1][j - 1] + nums1[i] + nums2[i] * j}
  • 排序: 將元素按照 nums2 正序排序,對于選擇 [i] 位置且選擇 j 次的方案沟突,分配在第 j 次選擇上的貢獻(xiàn)度是最大的花颗。
class Solution {
    fun minimumTime(nums1: List<Int>, nums2: List<Int>, x: Int): Int {
        val INF = -0x3F3F3F3F // 減少判斷
        val n = nums1.size
        // 排序
        val ids = Array<Int>(n) {it}
        Arrays.sort(ids) {i1, i2 ->
            nums2[i1] - nums2[i2]
        } 
        // 動(dòng)態(tài)規(guī)劃
        val dp = Array(n + 1) { IntArray(n + 1) { INF }}
        dp[0][0] = 0 // 初始狀態(tài)
        for (i in 1 .. n) { // 枚舉物品
            for (j in 0 .. i) { // 枚舉次數(shù)
                dp[i][j] = dp[i - 1][j]
                if (j > 0) dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + nums1[ids[i - 1]] + nums2[ids[i - 1]] * j)
            }
        }
        // println(dp[n].joinToString())
        // 輸出
        val s1 = nums1.sum()
        val s2 = nums2.sum()
        for (t in 0 .. n) {
            if (s1 + s2 * t - dp[n][t] <= x) return t
        }
        return -1
    }
}

滾動(dòng)數(shù)組優(yōu)化:

class Solution {
    fun minimumTime(nums1: List<Int>, nums2: List<Int>, x: Int): Int {
        val INF = -0x3F3F3F3F // 減少判斷
        val n = nums1.size
        // 排序
        val ids = Array<Int>(n) {it}
        Arrays.sort(ids) {i1, i2 ->
            nums2[i1] - nums2[i2]
        } 
        // 動(dòng)態(tài)規(guī)劃
        val dp = IntArray(n + 1) { INF }
        dp[0] = 0 // 初始狀態(tài)
        for (i in 1 .. n) { // 枚舉物品
            for (j in i downTo 1) { // 枚舉次數(shù)(逆序)
                dp[j] = Math.max(dp[j], dp[j - 1] + nums1[ids[i - 1]] + nums2[ids[i - 1]] * j)
            }
        }
        // println(dp[n].joinToString())
        // 輸出
        val s1 = nums1.sum()
        val s2 = nums2.sum()
        for (t in 0 .. n) {
            if (s1 + s2 * t - dp[t] <= x) return t
        }
        return -1
    }
}

復(fù)雜度分析:

  • 時(shí)間復(fù)雜度:O(n^2) 其中排序時(shí)間為 O(nlgn),動(dòng)態(tài)規(guī)劃時(shí)間為 O(n^2)惠拭;
  • 空間復(fù)雜度:O(n) DP 數(shù)組空間扩劝。

推薦閱讀

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市职辅,隨后出現(xiàn)的幾起案子棒呛,更是在濱河造成了極大的恐慌,老刑警劉巖域携,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件簇秒,死亡現(xiàn)場離奇詭異,居然都是意外死亡秀鞭,警方通過查閱死者的電腦和手機(jī)趋观,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門扛禽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人皱坛,你說我怎么就攤上這事编曼。” “怎么了剩辟?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵掐场,是天一觀的道長。 經(jīng)常有香客問我抹沪,道長刻肄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任融欧,我火速辦了婚禮敏弃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘噪馏。我一直安慰自己麦到,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布欠肾。 她就那樣靜靜地躺著瓶颠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刺桃。 梳的紋絲不亂的頭發(fā)上粹淋,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音瑟慈,去河邊找鬼桃移。 笑死,一個(gè)胖子當(dāng)著我的面吹牛葛碧,可吹牛的內(nèi)容都是我干的借杰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼进泼,長吁一口氣:“原來是場噩夢啊……” “哼蔗衡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乳绕,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤绞惦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后洋措,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翩隧,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堆生。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雷酪,死狀恐怖淑仆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哥力,我是刑警寧澤蔗怠,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站吩跋,受9級特大地震影響寞射,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锌钮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一桥温、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梁丘,春花似錦侵浸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至值漫,卻和暖如春澳腹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杨何。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工酱塔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晚吞。 一個(gè)月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓延旧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親槽地。 傳聞我的和親對象是個(gè)殘疾皇子迁沫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

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