LeetCode 双周赛 99,纯纯送分场!

大家好,我是小彭。

昨晚是 LeetCode 第 99 场双周赛,你参加了吗?这场周赛整体难度很低,第 4 题评论区普遍认为是 1 字头,纯纯手速场。

2578. 最小和分割

题目地址

https://leetcode.cn/problems/split-with-minimum-sum/

题目描述

给你一个正整数 num ,请你将它分割成两个非负整数 num1num2 ,满足:

  • num1num2 直接连起来,得到 num 各数位的一个排列。
    • 换句话说,num1num2 中所有数字出现的次数之和等于 num 中所有数字出现的次数。
  • num1num2 可以包含前导 0 。

请你返回 num1num2 可以得到的和的 最小 值。

注意:

  • num 保证没有前导 0 。
  • num1num2 中数位顺序可以与 num 中数位顺序不同。

题解(排序 + 贪心)

第一题相对有点思维。

  • 思考 1:越高位的数字对结果的影响越大,所以优先排列最小的数字;
  • 思考 2:如果划分两个数字的长度不均,会放大最终的值;

算法:对数字排序,从小到大分别排列到两个数字上。

class Solution {
    fun splitNum(num: Int): Int {
        val array = "$num".toCharArray()
        array.sort()
        var num1 = 0
        var num2 = 0
        for (index in array.indices step 2) {
            num1 = num1 * 10 + (array[index] - '0')
            if (index + 1 < array.size) {
                num2 = num2 * 10 + (array[index + 1] - '0')
            }
        }
        return num1 + num2
    }
}

简化写法:

class Solution {
    fun splitNum(num: Int): Int {
        val array = "$num".toCharArray().sorted()
        var nums = Array(2) { StringBuilder() }
        for (index in array.indices) {
            nums[index % 2].append(array[index])
        }
        return "${nums[0]}".toInt() + "${nums[1]}".toInt()

复杂度分析:

  • 时间复杂度:O(mlgm) 其中 mnum 数字的位数,即 m = lg\,num。排序时间为 O(mlgm),拆分时间为 O(m)
  • 空间复杂度:O(m) 字符串空间为 O(m),排序递归栈空间为 O(lgm)

2579. 统计染色格子数

题目地址

https://leetcode.cn/problems/count-total-number-of-colored-cells/

题目描述

有一个无穷大的二维网格图,一开始所有格子都未染色。给你一个正整数 n ,表示你需要执行以下步骤 n 分钟:

  • 第一分钟,将 任一 格子染成蓝色。
  • 之后的每一分钟,将与蓝色格子相邻的 所有 未染色格子染成蓝色。

下图分别是 1、2、3 分钟后的网格图。

题解(找规律)

找规律题。这道题可以用重力加速度类比,重力加速度的 G 是 9.8m/s,而这里的 G 是 4格/s。

  • 最开始只有一格,我们先放到一边单独计算,有 f(1) = 1
  • 从 (n = 1) 递推到 (n = 2) 时的速度为 4,因此 f(2) = 4 + 1 = 5
  • 从 (n = 2) 递推到 (n = 3) 时的速度为 8,因此 f(3) = 8 + f(2) = 13
  • 以此类推,从 (n - 1) 递推到 (n) 时的速度是 4 *(n - 1),即 f(n) = f(n - 1) + 4(n - 1)

显然有:

f(n)=\begin{cases} 1, &n=1\\ f(n-1) + 4(n-1) & n>1 \end{cases}

可以看到, n > 1 的分支是一个从 0 开始的等差数列,等差数列求和公式知道吧,整理得:f(n) = 1 + 4 * n * (n - 1) / 2 = 2n^2 - 2n + 1

class Solution {
    fun coloredCells(n: Int): Long {
        return 2 * n * n - 2 * n + 1
    }
}

复杂度分析:

  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

2580. 统计将重叠区间合并成组的方案数

题目地址

https://leetcode.cn/problems/count-ways-to-group-overlapping-ranges/

题目描述

给你一个二维整数数组 ranges ,其中 ranges[i] = [starti, endi] 表示 startiendi 之间(包括二者)的所有整数都包含在第 i 个区间中。

你需要将 ranges 分成 两个 组(可以为空),满足:

  • 每个区间只属于一个组。
  • 两个有 交集 的区间必须在 同一个 组内。

如果两个区间有至少 一个 公共整数,那么这两个区间是 有交集 的。

  • 比方说,区间 [1, 3][2, 5] 有交集,因为 23 在两个区间中都被包含。

请你返回将 ranges 划分成两个组的 总方案数 。由于答案可能很大,将它对 109 + 7 取余 后返回。

题解(排序 + 贪心)

这道题我第一时间想到了这两道题:

后来在评论区看到更接近的原题,好嘛,怪不得印象很深。

脑海中有闪现过并查集,但显然没有必要。

算法:将区间看成时间段,先按照开始时间对区间排序,然后在遍历区间的同时维护已经占用的最晚结束时间 preEnd。如果当前区间的开始时间早于 preEnd,说明区间重合。遍历完数组后求出集合个数 m,求 m 个元素放到 2 个位置的方案数,显然每个位置有 m 中可能,因此结果是 2^m

class Solution {
    fun countWays(ranges: Array<IntArray>): Int {
        val MOD = 1000000007
        Arrays.sort(ranges) { e1, e2 ->
            e1[0] - e2[0]
        }
        var m = 0
        var preEnd = -1
        for (range in ranges) {
            if (range[0] > preEnd) {
                // 无交集
                m++
            }
            preEnd = Math.max(preEnd, range[1])
        }
        return pow(2, m, MOD)
    }

    private fun pow(x: Int, n: Int, mod: Int): Int {
        var ans = 1
        for (count in 0 until n) {
            ans = (ans * x) % mod
        }
        return ans
    }
}

复杂度分析:

  • 时间复杂度:O(nlgn + n + lgm) 其中 nnums 数组的长度,m 是无交集区间的集合个数,幂运算时间为 O(m)
  • 空间复杂度:O(lgn) 排序递归栈空间。

2581. 统计可能的树根数目

题目地址

https://leetcode.cn/problems/count-number-of-possible-root-nodes/

题目描述

Alice 有一棵 n 个节点的树,节点编号为 0n - 1 。树用一个长度为 n - 1 的二维整数数组 edges 表示,其中 edges[i] = [ai, bi] ,表示树中节点 aibi 之间有一条边。

Alice 想要 Bob 找到这棵树的根。她允许 Bob 对这棵树进行若干次 猜测 。每一次猜测,Bob 做如下事情:

  • 选择两个 不相等 的整数 uv ,且树中必须存在边 [u, v]
  • Bob 猜测树中 uv父节点

Bob 的猜测用二维整数数组 guesses 表示,其中 guesses[j] = [uj, vj] 表示 Bob 猜 ujvj 的父节点。

Alice 非常懒,她不想逐个回答 Bob 的猜测,只告诉 Bob 这些猜测里面 至少k 个猜测的结果为 true

给你二维整数数组 edges ,Bob 的所有猜测和整数 k ,请你返回可能成为树根的 节点数目 。如果没有这样的树,则返回 0

题解(记忆化递归)

这是换根 DP 问题,这道题相对简单了,只要掌握图的基本结构和递归的基本思想就能实现。

首先是建图的部分,显然 edges 是无向图,guesses 是有向图。我们的算法基本框架应该是:枚举每个根节点,计算 guesses 中猜测正确的边的个数,如果猜测次数 ≥ k 则记录 1 次结果。现在的问题是如果优化查询的时间复杂度,我们观察依次从 0 到 n - 1 修改根节点会发生什么?

我们发现: 每次调整中只有条边的结构关系变化。比如从根 0 调整到根 1 时,只有 0 → 1 被修改为 1 → 0,而其他边都没有变化,存在重叠子结构 / 重叠子问题。

定义 f(u, v) 表示在 u → v 的子结构中猜测正确的边数,例如在示例 2 中,f(1, 2) = 1。显然在已知 f(1,2) 的结果后,在以节点 1 为根节点的情况中不需要重复计算,达到了剪枝的目的。

编码部分有两个细节:

  • 起点需要特殊处理,我们考虑起点是用 u → v 开始的子结构,起点 u 可以采用特殊值 n
  • 注意空间压缩,显然使用领接表比临接矩阵更优。备忘录可以使用移位压缩,Key = u * mod + v,由于题目数据范围是 10000,所以 mod 就取 100000。
class Solution {
    private val graph = HashMap<Int, MutableList<Int>>()
    private val guessGraph = HashMap<Int, MutableList<Int>>()

    fun rootCount(edges: Array<IntArray>, guesses: Array<IntArray>, k: Int): Int {
        // 无向图
        for (edge in edges) {
            graph.getOrPut(edge[0]) { LinkedList<Int>() }.add(edge[1])
            graph.getOrPut(edge[1]) { LinkedList<Int>() }.add(edge[0])
        }
        // 有向图
        for (guess in guesses) {
            // 过滤不存在边(题目没有这种用例)
            if (!graph.containsKey(guess[0]) || !graph[guess[0]]!!.contains(guess[1])) continue
            guessGraph.getOrPut(guess[0]) { LinkedList<Int>() }.add(guess[1])
        }
        val n = edges.size + 1
        // 空间溢出:val memo = Array(n + 1) { IntArray(n) { -1 } }
        val memo = HashMap<Long, Int>()
        var count = 0
        // 枚举所有根
        for (root in 0 until n) {
            if (dfs(memo, 100000, n, root) >= k) count++
        }
        return count
    }

    // 记忆化递归
    private fun dfs(memo: HashMap<Long, Int>, mod: Int, u: Int, v: Int): Int {
        // 空间压缩
        val key = 1L * u * (mod) + v
        // 备忘录
        if (memo.containsKey(key)) return memo[key]!!
        var count = 0
        for (to in graph[v]!!) {
            // 过滤指向父节点的边
            if (to == u) continue
            // 检查猜测
            if (guessGraph.containsKey(v) && guessGraph[v]!!.contains(to)) count++
            // 递归
            count += dfs(memo, mod, v, to)
        }
        memo[key] = count
        return count
    }
}

复杂度分析:

  • 时间复杂度:O(1) 其中 nedges 数组的长度,mguesses 数组的长度。建图占用 O(n + m + 2*n),在记忆化递归下每条边的子结构最多访问 2 次,即总共有 2n 个子问题,所以查询的复杂度是 O(2*n)
  • 空间复杂度:O(n + m + 2*n) 建图占用 O(n + m),备忘录最多记录 n 条边的两个方向的子结构,递归栈最大为 n

就说这么多,今天单周赛加油💪🏻。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容