学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。
本文是 LeetCode 上分之旅系列的第 38 篇文章,往期回顾请移步到文章末尾~
T1. 取整购买后的账户余额(Easy)
T2. 在链表中插入最大公约数(Medium)
T3. 使循环数组所有元素相等的最少秒数(Medium)
T4. 使数组和小于等于 x 的最少时间(Hard)
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 } }
复杂度分析:
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 } }
复杂度分析:
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 } }
复杂度分析:
https://leetcode.cn/problems/minimum-time-to-make-array-sum-at-most-x/
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 } }
复杂度分析: