LeetCode 周赛上分之旅 #38 结合排序不等式的动态规划

news2025/1/16 20:18:11

⭐️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。

学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。

本文是 LeetCode 上分之旅系列的第 38 篇文章,往期回顾请移步到文章末尾~

双周赛 110

T1. 取整购买后的账户余额(Easy)

  • 标签:模拟

T2. 在链表中插入最大公约数(Medium)

  • 标签:链表、数学

T3. 使循环数组所有元素相等的最少秒数(Medium)

  • 标签:贪心、散列表

T4. 使数组和小于等于 x 的最少时间(Hard)

  • 标签:排序不等式、动态规划、贪心


T1. 取整购买后的账户余额(Easy)

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

题解(模拟)

阅读理解题。

其实就是将 purchaseAmount 向最近的 10 的倍数四舍五入,再用 100 减去它。

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

复杂度分析:

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

T2. 在链表中插入最大公约数(Medium)

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

题解(数学)

久违的链表题。

题目相对简单,其实就是依次处理每两个节点,并插入一个新的最大公约数节点。以下提供两个写法:

  • 构造新链表:
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
    }
}

复杂度分析:

  • 时间复杂度: O ( n l g U ) O(nlgU) O(nlgU) 其中单次最大公约数的计算时间复杂度为 O ( l g U ) O(lgU) O(lgU),U 为数值上界;
  • 空间复杂度: O ( 1 ) O(1) O(1) 不考虑输出空间。

T3. 使循环数组所有元素相等的最少秒数(Medium)

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

题解(贪心 + 散列表)

根据题目要求,我们可以通过将数字复制到相邻位置上,以实现数组中所有元素都相等。因此,如果我们选择数字 x 为最终元素,那么决定替换秒数的关键在与数组中不等于 x 的最长子数组长度。

所以,我们的算法是计算以每种数字 x 为目标的方案中,最短的不等于 x 的最长子数组长度,并除以 2 向上取整的到结果。

class Solution {
    fun minimumSeconds(nums: List<Int>): Int {
        // 最大间隔的最小值
        val n = nums.size
        // lens:记录每种数字的最长间隔
        val lens = HashMap<Int, Int>()
        // preIndexs:记录每种数字的上次出现位置
        val preIndexs = HashMap<Int, Int>()
        // 记录最后出现位置(环形数组逻辑)
        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
    }
}

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) 线性遍历;
  • 空间复杂度: O ( n ) O(n) O(n) 散列表空间。

T4. 使数组和小于等于 x 的最少时间(Hard)

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

题解(DP + 排序不等式)

  • 时间的上界: 假设题目的最少时间超过数组长度 n,那么根据抽屉原理必然有某个位置重复置零两次,那么第一次操作的贡献就丢失了,因此,题目的时间上界不应该超过 n,即每个位置最多置零一次;
  • 二分答案(X): 数组元素和小于等于 x 与操作时间 t 不具备单调性,因此不能使用二分答案的思路;
  • 逆向思维: 令 s1 = sum(nums1), s2 = sum(nums2),假设经过 t 时间且不进行任何操作,那么元素总和将变成 s1 + s2 *t。现在需要从 [0, n-1] 中非重复地选择 t 个位置,假设在第 x 秒选择位置 [i],那么对最终元素总和减少的贡献度为 nums1[i] + x·nums2[i]。
  • 排序不等式: 现在的问题是「选择哪些数」以及「如何分配选择时间」使得减少的贡献度尽可能大:假设选择位置 [i]、[j] 和 [k],那么贡献度为:
    • nums1[i] + nums2[i] * x
    • nums1[j] + nums2[j] * y
    • nums1[k] + nums2[k] * z
    • 无论如何分配,加法左边的贡献度是恒定的,问题关键在与如何使得加法右边的贡献度尽可能大;
    • 直观地观察,容易想到应该将元素值更大的元素分配到更靠后的位置上,使其置零时贡献更多;
    • 验证证明可以根据 排序不等式 ,假设有两组有序序列 a 和 b,每一项正序相乘并累加的和是最大的。
  • 动态规划(选哪个): 定义 dp[i][j] 表示到第 [i] 个元素为止操作 j 次时的最大贡献度
    • 目标:满足 dp[n][j] 小于等于 x 的最小 j 值
    • 状态转移方程(选和不选): d p [ i ] [ j ] = m a x d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − 1 ] + n u m s 1 [ i ] + n u m s 2 [ i ] ∗ j dp[i][j] = max{dp[i - 1][j], dp[i - 1][j - 1] + nums1[i] + nums2[i] * j} dp[i][j]=maxdp[i1][j],dp[i1][j1]+nums1[i]+nums2[i]j
  • 排序: 将元素按照 nums2 正序排序,对于选择 [i] 位置且选择 j 次的方案,分配在第 j 次选择上的贡献度是最大的。
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]
        } 
        // 动态规划
        val dp = Array(n + 1) { IntArray(n + 1) { INF }}
        dp[0][0] = 0 // 初始状态
        for (i in 1 .. n) { // 枚举物品
            for (j in 0 .. i) { // 枚举次数
                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
    }
}

滚动数组优化:

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]
        } 
        // 动态规划
        val dp = IntArray(n + 1) { INF }
        dp[0] = 0 // 初始状态
        for (i in 1 .. n) { // 枚举物品
            for (j in i downTo 1) { // 枚举次数(逆序)
                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
    }
}

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) 其中排序时间为 O ( n l g n ) O(nlgn) O(nlgn),动态规划时间为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n ) O(n) O(n) DP 数组空间。

推荐阅读

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

  • LeetCode 单周赛第 357 场 · 多源 BFS 与连通性问题
  • LeetCode 单周赛第 356 场 · KMP 字符串匹配殊途同归
  • LeetCode 双周赛第 109 场 · 按部就班地解决动态规划问题
  • LeetCode 双周赛第 107 场 · 很有意思的 T2 题

⭐️ 永远相信美好的事情即将发生,欢迎加入小彭的 Android 交流社群~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/843687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

思维能力的学习

前言 在工作中&#xff0c;随着工作时间的增长&#xff0c;我们与他人的差异不是知识本身的差异&#xff0c;主要是思维方面的差异&#xff0c;所以我们需要培养自己的思维能力。 思维能力的学习 思维是一个具备内在框架和逻辑的系统工程&#xff0c;思维覆盖了学习、认知、问…

43..利用fsolve函数解对应lambda下的方程组(matlab程序)

1.简述 fsolve的基本用法 : x fsolve(fun,x0) 其中fun应为函数句柄&#xff0c;x0为搜索的种子&#xff0c;即预估的fun0的解的大致位置。 函数句柄的定义方式主要有两种&#xff1a; 1.定义函数文件&#xff0c;使用操作符 定义function文件root2d.m, 如下&#xff1a; …

nvm下载node导致npm报错无法使用

有个依赖库需要更新下node&#xff0c;用nvm下载后项目跑不起来了&#xff0c;npm -v 还报错 其实一开始是npm下载不来&#xff0c;然后换了淘宝镜像后还是报错 然后就只能手动下载下了 进入node.js官网 https://nodejs.org/en/download 下载后注意要安装在你nvm目录中&#x…

httpd+Tomcat(jk)的Web动静分离搭建

动静分离是指将动态请求和静态请求分别交给不同的服务器来处理&#xff0c;可以提高服务器的效率和性能。在Java Web开发中&#xff0c;常见的动态请求处理方式是通过Tomcat来处理&#xff0c;而静态请求则可以通过Apache服务器来处理。本文将详细讲解如何结合Apache和Tomcat来…

MySQL的数据插入总结(不存在就插入,存在就更新)

MySQL的数据插入总结(不存在就插入&#xff0c;存在就更新) 1. on duplicate key update 当在insert语句后面带上ON DUPLICATE KEY UPDATE 子句&#xff0c;而要插入的行与表中现有记录的惟一索引或主键中产生重复值&#xff0c;那么就会发生旧行的更新&#xff1b;如果插入的…

高性能计算集群使用

一、PuTTY的下载与安装 PuTTY是一款开源的连接软件&#xff0c;是 SSH、Telnet、Rlogin 和 SUPDUP 网络协议的客户端程序。 下载网址&#xff1a;Download PuTTY - a free SSH and telnet client for Windows 安装好后连接自己的服务器 输入用户名和密码&#xff0c;回车登录…

前端安全XSS和CSRF讲解

文章目录 XSSXSS攻击原理常见的攻击方式预防措施 CSRFCSRF攻击原理常见攻击情景预防措施&#xff1a; CSRF和XSS的区别 XSS 全称Cross Site Scripting&#xff0c;名为跨站脚本攻击。为啥不是单词第一个字母组合CSS&#xff0c;大概率与样式名称css进行区分。 XSS攻击原理 不…

【数据结构】单链表OJ题(二)

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、链表分割 &#x1f4a1;方法一&#xff1a; 二、链表的回文 &#x…

5个可以创意灵感的AI绘画工具

当设计灵感耗尽&#xff0c;陷入创作瓶颈时&#xff0c;人工智能艺术生成器可能会为您提供新的启示。这些基于深度学习和发展“神经网络”的工具可以将输入的文本描述或图像转换成各种风格的艺术作品&#xff0c;并提供丰富的风格参数和材料库&#xff0c;让您可以自由调整和创…

Web3 solidity编写交易所合约 编写ETH和自定义代币存入逻辑 并带着大家手动测试

上文 Web3 叙述交易所授权置换概念 编写transferFrom与approve函数我们写完一个简单授权交易所的逻辑 但是并没有测试 其实也不是我不想 主要是 交易所也没实例化 现在也测试不了 我们先运行 ganache 启动一个虚拟的区块链环境 先发布 在终端执行 truffle migrate如果你跟着我…

C++初阶缺省参数

目录 缺省参数全缺省半缺省有意义的使用场景注意点 缺省参数 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用指定的实参。 举例&#xff1a;Func(2),那么a就是2&#xff0c;Fu…

LeetCode[210]课程表II

难度&#xff1a;Medium 题目&#xff1a; 现在你总共有 numCourses 门课需要选&#xff0c;记为 0 到 numCourses - 1。给你一个数组 prerequisites &#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示在选修课程 ai 前 必须 先选修 bi 。 例如&#xff0c;想要学…

8.7面经

1.字符串是怎么实现不可变的&#xff0c;不可变类&#xff0c;字符串不可变的好处 &#xff08;1&#xff09;保存字符串的数组被 final 修饰且为私有的&#xff0c;并且String 类没有提供/暴露修改这个字符串的方法。 (2)String 类被 final 修饰导致其不能被继承&#xff0c…

2023上半年京东烘干机行业品牌销售排行榜(京东商品数据)

随着人们消费水平的提高&#xff0c;追求健康品质消费的用户越来越多&#xff0c;这样的消费升级为市场的发展带来很大的动力。同时&#xff0c;随着洗衣机市场趋向饱和&#xff0c;增长趋于平缓&#xff0c;更新换代和结构升级成为行业的主旋律。 在这一市场背景下&#xff0…

【VUE】项目本地开启https访问模式(vite4)

在实际开发中&#xff0c;有时候需要项目以https形式进行页面访问/调试&#xff0c;下面介绍下非vue-cli创建的vue项目如何开启https 环境 vue: ^3.2.47vite: ^4.1.4 根据官方文档&#xff1a;开发服务器选项 | Vite 官方中文文档 ps&#xff1a;首次操作&#xff0c;不要被类…

Crond计划任务和用户权限提升(三十七)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、一次性任务 二、周期性任务 ​编辑 三、用户权限提升 3.1 su 3.2 sudo提权 总结 前言 计划任务是在指定的时间间隔内自动执行的任务。在Linux系统中&#xff…

开源项目如何贡献代码

以腾讯犀牛鸟开源项目ncnn为例 目录 fork项目仓库 添加远程仓库 同步更新仓库 贡献代码提交新PR PR未merge更新PR fork项目仓库 只需要做一次 到仓库页面点击fork&#xff0c;然后create forkTencent/ncnn: ncnn is a high-performance neural network inference frame…

Grafana 曲线图报错“parse_exception: Encountered...”

问题现象 配置的Grafana图报错如下&#xff1a; 原因分析 点开报错&#xff0c;可以看到报错详细信息&#xff0c;是查询语句的语法出现了异常。 变量pool的取值为None 解决方案 需要修改变量pool的查询SQL&#xff0c;修改效果如下&#xff1a; 修改后&#x…

Gym 0.26.2版本简明教程【1】

Gym 0.26.2版本简明教程【1】 文章目录 Gym 0.26.2版本简明教程【1】1. 构建gym环境2. gym.Env常用method3. 关于 gym 0.26.2新版本的更改4. Reference 1. 构建gym环境 使用gym.make(id,render_mode)我们可以注册一个环境&#xff0c;然后我们可以查看一下这个环境的一些信息&…

自动化处理,web自动化测试处理多窗口+切换iframe框架页总结(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 web 自动化之处理…