草庐IT

LeetCode 周赛 335,纯纯手速场!

彭旭锐 2023-09-17 原文

大家好,我是小彭。

昨晚是 LeetCode 第 335 场周赛,你参加了吗?这场周赛整体难度不高,有两道模板题,第三题和第四题应该调换一下位置。


2582. 递枕头(Easy)

题目地址

https://leetcode.cn/problems/pass-the-pillow/

题目描述

n 个人站成一排,按从 1n 编号。

最初,排在队首的第一个人拿着一个枕头。每秒钟,拿着枕头的人会将枕头传递给队伍中的下一个人。一旦枕头到达队首或队尾,传递方向就会改变,队伍会继续沿相反方向传递枕头。

  • 例如,当枕头到达第 n 个人时,TA 会将枕头传递给第 n - 1 个人,然后传递给第 n - 2 个人,依此类推。

给你两个正整数 ntime ,返回 t

题解一(模拟)

简单模拟题。

class Solution {
    fun passThePillow(n: Int, time: Int): Int {
        var index = 1
        var flag = true
        for (count in 0 until time) {
            if (flag) {
                if (++index == n) flag = !flag
            } else {
                if (--index == 1) flag = !flag
            }
        }
        return index
    }
}

复杂度分析:

  • 时间复杂度:
  • 空间复杂度:

题解二(数学)

以 n = 4 为例,显然每 n - 1 次传递为一轮,则有 time % (n - 1) 分辨出奇数轮 / 偶数轮。其中偶数轮是正向传递,奇数轮是逆向传递。

  • 偶数轮:2 → 3 → 4,time = 1 时传递到 2 号;
  • 奇数轮:3 → 2 → 1。
class Solution {
    fun passThePillow(n: Int, time: Int): Int {
        val mod = n - 1
        return if (time / mod % 2 == 0) {
            (time % mod) + 1
        } else {
            n - (time % mod)
        }
    }
}

复杂度分析:

  • 时间复杂度:
  • 空间复杂度:

2583. 二叉树中的第 K 大层和(Medium)

题目地址

https://leetcode.cn/problems/kth-largest-sum-in-a-binary-tree/

题目描述

给你一棵二叉树的根节点 root 和一个正整数 k

树中的 层和 是指 同一层 上节点值的总和。

返回树中第 k 大的层和(不一定不同)。如果树少于 k 层,则返回 -1

注意,如果两个节点与根节点的距离相同,则认为它们在同一层。

题解(BFS + 堆)

BFS 模板题,使用小顶堆记录最大的 k 个数。

class Solution {
    fun kthLargestLevelSum(root: TreeNode?, k: Int): Long {
        if (null == root) return 0L
        val heap = PriorityQueue<Long>()

        // BFS
        val queue = LinkedList<TreeNode>()
        queue.offer(root)
        while (!queue.isEmpty()) {
            var levelSum = 0L
            for (count in 0 until queue.size) {
                val node = queue.poll()
                levelSum += node.`val`
                if (null != node.left) {
                    queue.offer(node.left)
                }
                if (null != node.right) {
                    queue.offer(node.right)
                }
            }
            if (heap.size < k) {
                heap.offer(levelSum)
            } else if (heap.peek() < levelSum) {
                heap.poll()
                heap.offer(levelSum)
            }
        }

        return if (heap.size >= k) heap.peek() else -1L
    }
}

复杂度分析:

  • 时间复杂度: 其中 是节点数。二叉树每个节点最多入队一次,二叉树最大有 层,小顶堆维护 个数的时间复杂度为
  • 空间复杂度: 小顶堆空间 ,递归栈空间最大

2584. 分割数组使乘积互质(Medium)

题目地址

https://leetcode.cn/problems/split-the-array-to-make-coprime-products/

题目描述

给你一个长度为 n 的整数数组 nums ,下标从 0 开始。

如果在下标 i分割 数组,其中 0 <= i <= n - 2 ,使前 i + 1 个元素的乘积和剩余元素的乘积互质,则认为该分割 有效

  • 例如,如果 nums = [2, 3, 3] ,那么在下标 i = 0 处的分割有效,因为 29 互质,而在下标 i = 1 处的分割无效,因为 63 不互质。在下标 i = 2 处的分割也无效,因为 i == n - 1

返回可以有效分割数组的最小下标 i ,如果不存在有效分割,则返回 -1

当且仅当 gcd(val1, val2) == 1 成立时,val1val2 这两个值才是互质的,其中 gcd(val1, val2) 表示 val1val2 的最大公约数。

题解(质因子分解)

这道题是这场周赛中最复杂的题目,应该放在 T4。

因为多个数相乘的结果会溢出(如果题目中存在 0 还会干扰),所以这道题不能用前后缀分解的思路。 比较容易想到的思路是做质因子分解:显然合法分割数点的左右两边不能有公共质因子,否则子集的乘积必然是非互质的。

举个例子,在数组 [1, 2, 3, 2, 5] 中,将质因子 2 划分到不同子集的方案是错误的:

  • [1 | 2, 3, 2, 5]:错误分割
  • [1 , 2 | 3, 2, 5]:正确分割
  • [1 , 2, 3 | 2, 5]:正确分割
  • [1 , 2, 3, 2 | 5]:错误分割

脑海中有闪现过状态压缩,但题目输入数据较大无法实现,只能有散列表记录质因子信息。因此我们的算法是:先对 nums 数组中的每个元素做质因数分解,然后枚举所有分割点,统计左右子集中质因子的出现次数。如果出现同一个质因子再左右子集中的出现次数同时大于 1,说明分割点不成立。

class Solution {
    fun findValidSplit(nums: IntArray): Int {
        val n = nums.size
        // 质因子计数
        val leftCount = HashMap<Int, Int>()
        val rightCount = HashMap<Int, Int>()
        // 质因子分解
        val primeMap = HashMap<Int, HashSet<Int>>()
        for (num in nums) {
            // 对 num 做质因数分解
            primeMap[num] = HashSet<Int>()
            var x = num
            var prime = 2
            while (prime * prime <= x) {
                if (x % prime == 0) {
                    // 发现质因子
                    primeMap[num]!!.add(prime)
                    rightCount[prime] = rightCount.getOrDefault(prime, 0) + 1
                    // 消除所有 prime 因子
                    while (x % prime == 0) x /= prime
                }
                prime++
            }
            if(x > 1) {
                // 剩下的质因子
                primeMap[num]!!.add(x)
                rightCount[x] = rightCount.getOrDefault(x, 0) + 1 
            }
        }
        // 枚举分割点
        outer@ for (index in 0..n - 2) {
            for (prime in primeMap[nums[index]]!!) {
                leftCount[prime] = leftCount.getOrDefault(prime, 0) + 1
                rightCount[prime] = rightCount[prime]!! - 1
            }
            for ((prime, count) in leftCount) {
                if (rightCount[prime]!! != 0) continue@outer
            }
            return index
        }
        return -1
    }
}

复杂度分析:

  • 时间复杂度: 其中 数组的长度,U 是数组元素的最大值, 范围内的质数个数 。时间复杂度分为两部分,质因数分解占用 ,枚举分割点的每轮循环需要枚举所有质数,占用
  • 空间复杂度: 质因子分解映射表和计数表。

题解二(质因数分解 + 合并区间)

思路来源:灵茶山艾符的题解

统计每种质因子在数组中出现的起始位置 left 和终止位置 right,如果分割点位于 [left, right) 区间,那么左右两子集一定会存在公共质因子。

因此我们的算法是:将质数的分布看成一个连续区间,按照区间起始位置对所有区间排序。遍历区间并维护最大区间终止位置 preEnd,如果当前区间与 preEnd 不连续,则说明以当前位置为分割点的方案不会拆分区间,也就找到目标答案。

如果按照这个思路理解,这道题本质上和 55. 跳跃游戏 类似。

class Solution {
    fun findValidSplit(nums: IntArray): Int {
        // 质因子区间 <首次出现位置,末次出现位置>
        val primeMap = HashMap<Int, IntArray>()
        // 质因数分解
        for ((index, num) in nums.withIndex()) {
            // 对 num 做质因数分解
            var x = num
            var prime = 2
            while (prime * prime <= x) {
                if (x % prime == 0) {
                    // 发现质因子
                    primeMap.getOrPut(prime) { intArrayOf(index, index) }[1] = index
                    // 消除所有 prime 因子
                    while (x % prime == 0) x /= prime
                }
                prime++
            }
            if (x > 1) {
                // 剩下的质因子
                primeMap.getOrPut(x) { intArrayOf(index, index) }[1] = index
            }
        }
        // 区间排序
        val areaList = primeMap.values.toMutableList()
        Collections.sort(areaList) { e1, e2 ->
            e1[0] - e2[0]
        }
        // 枚举区间
        var preEnd = 0
        for (area in areaList) {
            if (area[0] > preEnd) return area[0] - 1
            preEnd = Math.max(preEnd, area[1])
        }
        return -1
    }
}

复杂度分析:

  • 时间复杂度: 质因数分解时间 ,排序时间 ,枚举区间时间
  • 空间复杂度: 质因子区间数组占用 ,排序递归栈空间

题解三(合并区间 + 排序优化)

题解二中的排序时间可以优化。

由于我们是从前往后分解 nums 数组,每分解一个质因子 prime 时,它一定可以更新该质数区间的末次出现位置。所以我们不用等到最后再做一次区间排序,直接在做质因数分解时维护 preEnd。在题解二中,我们是从区间的维度维护 preEnd,现在我们直接从 nums 数组的维度维护 preEnd。

class Solution {
    fun findValidSplit(nums: IntArray): Int {
        val n = nums.size
        // start[p] 表示质数 p 首次出现为止
        val start = HashMap<Int, Int>()
        // end[i] 表示以 i 为左端点的区间的最大右端点
        val end = IntArray(n)
        // 质因数分解
        for ((index, num) in nums.withIndex()) {
            // 对 num 做质因数分解
            var x = num
            var prime = 2
            while (prime * prime <= x) {
                if (x % prime == 0) {
                    // 发现质因子
                    if (!start.containsKey(prime)) {
                        start[prime] = index
                    } else {
                        end[start[prime]!!] = index
                    }
                    // 消除所有 prime 因子
                    while (x % prime == 0) x /= prime
                }
                prime++
            }
            if (x > 1) {
                // 剩下的质因子
                if (!start.containsKey(x)) {
                    start[x] = index
                } else {
                    end[start[x]!!] = index
                }
            }
        }
        var preEnd = 0
        for (index in 0 until n) {
            if (index > preEnd) return index - 1
            preEnd = Math.max(preEnd, end[index])
        }
        return -1
    }
}

复杂度分析:

  • 时间复杂度: 质因数分解时间 ,枚举数组时间
  • 空间复杂度: 数组空间。

2585. 获得分数的方法数(Hard)

题目地址

https://leetcode.cn/problems/number-of-ways-to-earn-points/

题目描述

考试中有 n 种类型的题目。给你一个整数 target 和一个下标从 0 开始的二维整数数组 types ,其中 types[i] = [counti, marksi] 表示第 i 种类型的题目有 counti 道,每道题目对应 marksi 分。

返回你在考试中恰好得到 target 分的方法数。由于答案可能很大,结果需要对 109 +7 取余。

注意,同类型题目无法区分。

  • 比如说,如果有 3 道同类型题目,那么解答第 1 和第 2 道题目与解答第 1 和第 3 道题目或者第 2 和第 3 道题目是相同的。

题解(背包问题)

这是分组背包模板题,OIWiki-背包 DP

定义 表示以物品 为止且分数为 的方案数,则有:

class Solution {
    fun waysToReachTarget(target: Int, types: Array<IntArray>): Int {
        val MOD = 1000000007
        // 背包问题
        val n = types.size
        // dp[i][j] 表示以 [i] 为止且分数为 j 的方案数
        val dp = Array(n + 1) { IntArray(target + 1) }.apply {
            // 不选择且分数为 0 的方案数为 1
            this[0][0] = 1
        }
        // 枚举物品
        for (i in 1..n) {
            val count = types[i - 1][0]
            val mark = types[i - 1][1]
            for (j in target downTo 0) {
                dp[i][j] += dp[i - 1][j]
                for (k in 1..Math.min(count, j / mark)) {
                    dp[i][j] = (dp[i][j] + dp[i - 1][j - k * mark]) % MOD
                }
            }
        }
        return dp[n][target]
    }
}

完全背包可以取消物品维度优化空间:

class Solution {
    fun waysToReachTarget(target: Int, types: Array<IntArray>): Int {
        val MOD = 1000000007
        // 背包问题
        val n = types.size
        // dp[i][j] 表示以 [i] 为止且分数为 j 的方案数
        val dp = IntArray(target + 1).apply {
            // 不选择且分数为 0 的方案数为 1
            this[0] = 1
        }
        // 枚举物品
        for (i in 1..n) {
            val count = types[i - 1][0]
            val mark = types[i - 1][1]
            for (j in target downTo 0) {
                for (k in 1..Math.min(count, j / mark)) {
                    dp[j] = (dp[j] + dp[j - k * mark]) % MOD
                }
            }
        }
        return dp[target]
    }
}

复杂度分析:

  • 时间复杂度: 其中 是所有 之和。
  • 空间复杂度:

有关LeetCode 周赛 335,纯纯手速场!的更多相关文章

  1. Python 刷Leetcode题库,顺带学英语单词(31) - 2

    ValidPalindromeGivenastring,determineifitisapalindrome,consideringonlyalphanumericcharactersandignoringcases. [#125]Example:"Aman,aplan,acanal:Panama"isapalindrome."raceacar"isnotapalindrome.Haveyouconsiderthatthestringmightbeempty?Thisisagoodquestiontoaskduringaninterview.Forthepurposeofthisproblem

  2. IDEA使用LeetCode插件 - 2

    前言我们习惯用idea编写、调试代码,在LeetCode上刷题时,如果能够在IDEA编写代码,并且做好代码管理,是一件事半功倍的事情。对于后续复习题目,做笔记也会非常便利。本文目的在于介绍LeetCodeEditor的使用,以及配置工具类,最终目录结构如下:note:放置笔记src:放置代码leetcode.editor.cn:插件LeetCodeEditor自动生成utils:自定义的工具包,可用于自动化输入测试用例,定义题目需要的类(结构体)out:运行测试时自动生成LeetCodeEditorGitHub:https://github.com/shuzijun/leetcode-edit

  3. 2022年10月23日周赛ZZULIOJ - 2

    文章目录问题B:芝华士威士忌和他的小猫咪们代码&注释问题C:愿我的弹雨能熄灭你们的痛苦代码注释问题D:猜糖果游戏代码注释问题E:有趣的次方代码注释问题F:这是一个简单题代码&注释问题G:打印矩阵代码注释问题H:scz的简单考验代码注释问题I:完美区间代码&注释问题J:是狂热的小迷妹一枚吖~代码&注释2022年10月23日周赛ZZULIOJ问题B:芝华士威士忌和他的小猫咪们时间限制:1Sec内存限制:128MB题目描述芝华士威士忌很喜欢带着他的猫咪们一块跑着玩。但是小猫咪们很懒,只有在离他y米以内才愿意和他一块跑。这天他在坐标为x的位置,他想和他的猫咪们一块跑着玩。有n个小猫咪,第i个小猫咪在坐

  4. LeetCode——2347. 最好的扑克手牌 - 2

    一、题目给你一个整数数组ranks和一个字符数组suit。你有5张扑克牌,第i张牌大小为ranks[i],花色为suits[i]。下述是从好到坏你可能持有的手牌类型:“Flush”:同花,五张相同花色的扑克牌。“ThreeofaKind”:三条,有3张大小相同的扑克牌。“Pair”:对子,两张大小一样的扑克牌。“HighCard”:高牌,五张大小互不相同的扑克牌。请你返回一个字符串,表示给定的5张牌中,你能组成的最好手牌类型。注意:返回的字符串大小写需与题目描述相同。来源:力扣(LeetCode)链接:https://leetcode.cn/problems/best-poker-hand/d

  5. LeetCode:344. 反转字符串 - 2

    🍎道阻且长,行则将至。🍓🌻算法,不如说它是一种思考方式🍀算法专栏:👉🏻123一、🌱344.反转字符串题目描述:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组s的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用O(1)的额外空间解决这一问题。来源:力扣(LeetCode)难度:简单提示:1s[i]都是ASCII码表中的可打印字符示例1:输入:s=[“h”,“e”,“l”,“l”,“o”]输出:[“o”,“l”,“l”,“e”,“h”]示例2:输入:s=[“H”,“a”,“n”,“n”,“a”,“h”]输出:[“h”,“a”,“n”,“n”,“a”,“H”

  6. 【日常系列】LeetCode《28·动态规划3》 - 2

    数据规模->时间复杂度10^8内容二维数组中的路径问题买卖股票的最佳时机lc62【剑指098】【top100】:不同路径https://leetcode.cn/problems/unique-paths/提示:1题目数据保证答案小于等于2*10^9#方案一:dfs+记忆化classSolution:defuniquePaths(self,m:int,n:int)->int:memo=[[-1]*nfor_inrange(m)]defdfs(i,j):ifi==m-1andj==n-1:return1ifi>=morj>=n:return0ifmemo[i][j]!=-1:returnmemo[

  7. LeetCode:454. 四数相加 II —— 哈希表为什么叫哈希表~ - 2

    🍎道阻且长,行则将至。🍓🌻算法,不如说它是一种思考方式🍀算法专栏:👉🏻123hash是什么,哈希表为什么叫哈希表?一、🌱454.四数相加II题目描述:给你四个整数数组nums1、nums2、nums3和nums4,数组长度都是n,请你计算有多少个元组(i,j,k,l)能满足:0nums1[i]+nums2[j]+nums3[k]+nums4[l]==0来源:力扣(LeetCode)难度:中等提示:n==nums1.lengthn==nums2.lengthn==nums3.lengthn==nums4.length1-2^28示例1:输入:nums1=[1,2],nums2=[-2,-1],n

  8. 【树】你真的会二叉树了嘛? --二叉树LeetCode专题Ⅳ - 2

     Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法......感兴趣就关注我吧!你定不会失望。🌈个人主页:主页链接🌈算法专栏:专栏链接     我会一直往里填充内容哒!🌈LeetCode专栏:专栏链接     目前在刷初级算法的LeetBook。若每日一题当中有力所能及的题目,也会当天做完发出🌈代码仓库:Gitee链接🌈点击关注=收获更多优质内容🌈目录题目:111. 二叉树的最小深度题解:代码实现:题目:700. 二叉搜索树中的搜索题解:代码实现:题目:701. 二叉搜索树中的插入操作题解:代码实现:题目:450. 删除二叉搜索树中的节点题解:代码实现:完结撒花:人生苦短,

  9. 【LeetCode】轮转数组 - 2

    👻内容专栏:《LeetCode刷题专栏》🐨本文概括:189.轮转数组🐼本文作者:花碟🐸发布时间:2023.4.12目录思想1暴力求解代码实现:思想2三次倒置代码实现: 思想3memcpy零时拷贝代码实现:189.轮转数组 点击跳转到LeetCode平台OJ页面题目:​​​​​​​给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。示例1:输入:nums=[1,2,3,4,5,6,7],k=3输出:[5,6,7,1,2,3,4]解释:向右轮转1步:[7,1,2,3,4,5,6]向右轮转2步:[6,7,1,2,3,4,5]向右轮转3步:[5,6,7,1,2,3,4]

  10. 【LeetCode: 673. 最长递增子序列的个数 | 动态规划】 - 2

    🚀算法题🚀🌲算法刷题专栏|面试必备算法|面试高频算法🍀🌲越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨🌲作者简介:硕风和炜,CSDN-Java领域新星创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文|经验分享|好用的网站工具分享💎💎💎🌲恭喜你发现一枚宝藏博主,赶快收入囊中吧🌻🌲人生如棋,我愿为卒,行动虽慢,可谁曾见我后退一步?🎯🎯🚀算法题🚀🍔目录🚗知识回顾🚩题目链接⛲题目描述🌟求解思路&实现代码&运行结果⚡动态规划🥦求解思路🥦实现代码🥦运行结果💬共勉🚗知识回顾大家再看这道题目之前,可以先去看一下我之前写过的一篇关于最长递增子序列算法

随机推荐