LeetCode 周赛上分之旅 #46 经典二分答案与质因数分解
️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。
学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。
本文是 LeetCode 上分之旅系列的第 46 篇文章,往期回顾请移步到文章末尾~
LeetCode 周赛 363
T1. 计算 K 置位下标对应元素的和(Easy)
- 标签:位运算
T2. 让所有学生保持开心的分组方法数(Medium)
- 标签:贪心、排序、计数排序
T3. 最大合金数(Medium)
- 标签:二分查找
T4. 完全子集的最大元素和(Hard)
- 标签:数学、质因素分解、散列表
T1. 计算 K 置位下标对应元素的和(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}
}
}
复杂度分析:
- 时间复杂度:$O(n)$ Java
Integer#bitCount
的时间复杂度是 $O(1)$ - 空间复杂度:$O(1)$ 仅使用常数级别空间。
T2. 让所有学生保持开心的分组方法数(Medium)
https://leetcode.cn/problems/happy-students/description/
问题分析
思考选哪个:
- 条件 1: 如果选中的学生 $nums[i]$ 越小,那么越容易满足选中人数 > $nums[i]$;
- 条件 2: 如果未选中的学生 $nums[i]$ 越大,那么越容易满足选中人数 < $nums[i]$;
因此,在合法的选择方案中,应该优先选择越小的学生。
题解(排序 + 贪心)
先对数组排序,再枚举分割点验证条件 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
对于分割点 i 的要求是:
- 条件 1:$i + 1 > nums[i]$,利用有序性质只需要判断已选列表的最大值 $nums[i]$;
- 条件 2:$i + 1 < nums[i + 1]$,利用有序性质只需要判断未选列表的最小值 $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
}
}
复杂度分析:
- 时间复杂度:$O(nlgn)$ 瓶颈在排序;
- 空间复杂度:$O(lgn)$ 排序递归栈空间。
T3. 最大合金数(Medium)
https://leetcode.cn/problems/maximum-number-of-alloys/description/
问题分析
初步分析:
- 问题目标: 求在预算限制下最大可以制造的合金数量;
- 关键信息: 所有合金都需要由同一台机器制造,这样难度就降低很多了。
容易发现原问题的单调性:
- 如果合金数 x 可以制造,那么合金数 $x - 1$ 一定可以制造;
- 如果合金数 x 不可制造,那么合金数 $x + 1$ 一定不可制造。
因此,可以用二分答案来解决问题:
- 合金数的下界:$0$
- 合金数的上界:$2 * 10^8$,即金钱和初始金属的最大值;
现在需要思考的问题是: 「如何验证合金数 $x$ 可以构造」
由于所有合金都需要由同一台机器制造,判断很简单,只需要先计算目标数量需要的每种金属的初始金属数是否足够,不足则花金钱购买。如果花费超过限制则不可制造。
题解(二分答案)
基于以上问下,我们枚举机器,使用二分查找寻找可以制造的合金数的上界:
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 {
// 计算需要的金属原料
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
}
}
复杂度分析:
- 时间复杂度:$O(k·n·lgU)$ 其中 $k$ 为机器数,$n$ 为金属种类,$U$ 为二分上界;
- 空间复杂度:$O(1)$ 除结果数组外仅使用常量级别空间。
T4. 完全子集的最大元素和(Hard)
https://leetcode.cn/problems/maximum-element-sum-of-a-complete-subset-of-indices/description/
问题分析
初步分析:
- 问题目标: 求解满足条件的目标子集的元素最大和;
- 目标子集: 子集元素的下标两两相乘的乘积是完全平方数,允许仅包含一个元素的子集;
观察测试用例 2:
- 对于下标 $1$ 和下标 $4$:两个完全平方数的乘积自然是完全平方数;
- 对于下标 $2$ 和下标 $8$:$2$ 和 $8$ 都包含质因子 $2$,$2$ 的平方自然是完全平方数;
由此得出结论:
- 核心思路: 我们消除每个下标中的完全平方数因子,再对剩余的特征分组,能够构造目标子集的方案有且只能出现在相同的特征分组中(否则,子集中一定存在两两相乘不是完全平方数的情况)。
{2 | 6} x 需要相同的因子
{6 | 6} ok
思考实现:
- 预处理: 预处理覆盖所有测试用例下标的特征值
- 质因素分解: 有 2 种基础算法:
朴素算法:枚举 $[2, \sqrt{n}]$ 将出现次数为奇数的质因子记录到特征值中,时间复杂度是 $O(\sqrt{n})$:
private val U = 1e4.toInt()
private val core = IntArray(U + 1)
init {
for (num in 1 .. U) {
// 质因素分解
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
}
}
筛法:枚举质因子,将记录质因子的整数倍的特征值。
private val U = 1e4.toInt()
private val core = IntArray(U + 1) { 1 }
private val isMark = BooleanArray(U + 1)
init {
// 质因素分解
for (i in 2 .. U) {
// 检查是否为质数,这里不需要调用 isPrime() 函数判断是否质数,因为它没被小于它的数标记过,那么一定不是合数
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 // 记录特征值
}
}
}
题解一(质因素分解 + 分桶)
组合以上技巧,枚举下标做质因数分解,将数值累加到分桶中,最后返回最大分桶元素和。
class Solution {
companion object {
private val U = 1e4.toInt()
private val core = IntArray(U + 1)
init {
for (num in 1 .. U) {
// 质因素分解
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
}
}
复杂度分析:
- 时间复杂度:预处理时间为 $O(U\sqrt{U})$,单次测试用例时间为 $O(n)$;
- 空间复杂度:$O(U)$ 预处理空间,单次测试用例空间比较松的上界为 $O(n)$。
题解二(找规律)
题解一的时间复杂度瓶颈在之因素分解。
继续挖掘数据特征,我们观察同一个分桶内的数据规律:
假设分桶中的最小值为 x,那么将分桶的所有元素排序后必然是以下序列的子序列:${x, 4 * x, 9 * x, 16 * x…}$,由此发现规律:我们可以枚举分桶的最小值,再依次乘以完全平方数序列来计算,既可以快速定位分桶中的元素,而不需要预处理质因数分解。
那怎么度量此算法的时间复杂度呢?
显然,该算法一个比较松上界是 $O(n·C)$,其中 $C$ 为数据范围内的完全平方数个数,$C = 100$。严格证明参考羊神题解,该算法线性时间复杂度 $O(n)$。
class Solution {
companion object {
// 预处理完全平方数序列
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
}
}
复杂度分析:
- 时间复杂度:$O(n)$ 线性算法;
- 空间复杂度:$O(C)$ 预处理完全平方数序列空间,可以优化。
推荐阅读
LeetCode 上分之旅系列往期回顾:
️ 永远相信美好的事情即将发生,欢迎加入小彭的 Android 交流社群~
LeetCode 周赛上分之旅 #46 经典二分答案与质因数分解的更多相关文章
- BZOJ2406矩阵——有上下界的可行流+二分答案
题目描述 输入 第一行两个数n.m,表示矩阵的大小. 接下来n行,每行m列,描述矩阵A. 最后一行两个数L,R. 输出 第一行,输出最小的答案: 样例输入 2 2 0 1 2 1 0 1 样例输出 1 ...
- 洛谷 P2678 跳石头【经典二分答案/贪心】
题目描述 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有 NN 块岩石(不含起点和终点的岩石).在比赛过程中,选手们将从 ...
- BZOJ4552 HEOI/TJOI2016 排序 线段树、二分答案
题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=4552 题意:给出一个$1$到$N$的全排列,对其进行$M$次排序,每次排序将区间$[l ...
- 刷爆 LeetCode 周赛 337,位掩码/回溯/同余/分桶/动态规划·打家劫舍/贪心
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末是 LeetCode 第 337 场周赛,你参加了吗?这场周赛第三题有点放水,如果 ...
- LeetCode hard 668. Kth Smallest Number in Multiplication Table(二分答案,一次过了,好开心,哈哈哈哈)
题目:https://leetcode.com/problems/kth-smallest-number-in-multiplication-table/description/ 668. Kth S ...
- 【Leetcode周赛】从contest-91开始。(一般是10个contest写一篇文章)
Contest 91 (2018年10月24日,周三) 链接:https://leetcode.com/contest/weekly-contest-91/ 模拟比赛情况记录:第一题柠檬摊的那题6分钟 ...
- LeetCode 周赛 333,你管这叫 Medium 难度?
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周是 LeetCode 第 333 场周赛,你参加了吗?这场周赛质量很高,但难度标得不 ...
- LeetCode 周赛 342(2023/04/23)容斥原理、计数排序、滑动窗口、子数组 GCB
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 前天刚举办 2023 年力扣杯个人 SOLO 赛,昨天周赛就出了一场 Easy - Ea ...
- Kindle:自动追更之云上之旅
2017年5月27: 原来的程序是批处理+Python脚本+Calibre2的方式,通过设定定时任务的方式,每天自动发动到自己的邮箱中.缺点是要一直开着电脑,又不敢放到服务器上~~ 鉴于最近公司查不关 ...
- Leetcode之回溯法专题-46. 全排列(Permutations)
Leetcode之回溯法专题-46. 全排列(Permutations) 给定一个没有重复数字的序列,返回其所有可能的全排列. 示例: 输入: [1,2,3] 输出: [ [1,2,3], [1,3, ...
随机推荐
- GitHub 私有仓库完全免费且不限制协作人数
GitHub is now free for teams GitHub CEO Nat Friedman 在 2020.04.14 宣布已面向全体 GitHub 用户和团队提供不限制协作人数的私有仓库 ...
- bugku_MagicImageViewer
CTF 安卓逆向 MagicImageViewer--png结构+算法 很少做安卓逆向的题目,在此记录一下 先用模拟器看一下 嗯,没啥提示. jeb打开 关键部分 if(s.length() == 1 ...
- Python编程和机器学习中的自然语言处理:如何从文本中提取有意义的信息和数据
目录 引言 自然语言处理(Natural Language Processing,NLP)是一种人工智能技术,旨在使计算机理解和处理自然语言文本,从中提取有意义的信息和数据.NLP是机器学习领域中的重 ...
- 使用GoEasy快速实现Android原生app中的websocket消息推送
摘要: GoEasy带来了一项令开发者振奋的消息:全面支持Android原生平台!现在,您可以在Android应用中使用最酷炫的实时通信功能,借助GoEasy轻松实现消息的发送和接收.本文将带您领略G ...
- 【Kubernetes】yaml文件编写 -- 持续更新
K8S通过yaml格式的声明式API与资源对象交互 API版本由apiVersion字段指定,API对象类型由kind字段指定 除此之外,每个API对象有三大类属性: metadata:元数据 spe ...
- IoTOS-v1.5.3 新增 智能诊断&会话记录导出
IoTOS v1.5.3 一.新增智能诊断 智能诊断功能: 智能诊断会根据不同上游接口能力开放提供接近官方甚至比官方更加完善的智能诊断功能. 目前还原OneLink官方智能诊断功能包括动效.诊断建议等 ...
- Java 中怎样将 bytes 转换为 long 类型?
将bytes 转换为long类型: 第一种方式: String 接收 bytes 的构造器转成 String,再 Long.parseLong: 但此种情况需要注意:字节数组中的每个字节都必须是有效的 ...
- IIC总线学习笔记
IIC(Inter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板.嵌入式系统或手 ...
- Abstract Factory 抽象工厂模式简介与 C# 示例【创建型1】【设计模式来了_1】
〇.简介 1.什么是抽象工厂模式? 一句话解释: 提供一个接口,以创建一系列相关或相互依赖的抽象对象,而无需指定它们具体的类.(将一系列抽象类装进接口,一次接口实现,就必须实例化这一系列抽象类) ...
- [python]使用diagrams绘制架构图
简介 diagrams是python的一个第三方库,用于实现使用代码绘制架构图. 安装 依赖于 Graphviz,安装diagrams之前需要先安装 Graphviz(下载压缩包后,将bin目录添加到 ...