力扣周赛 339

34 阅读4分钟

力扣周赛 339

5909. 检查句子中的数字是否递增

1.1 题目描述

给你一个下标从 0 开始的字符串 s ,它的 偶数 下标处为小写英文字母,奇数 下标处为数字。

定义一个函数 getNumericValue ,对 s 中的任意字符 c ,有:

  • 如果 c 是一个数字,那么 getNumericValue(c) 为它的数值

  • 如果 c 是一个小写英文字母,那么 getNumericValue(c) 为它在字母表中的位置(从 1 开始) 例如,getNumericValue('a') = 1 ,getNumericValue('c') = 3 ,getNumericValue('2') = 2 ,getNumericValue('9') = 9 。 请你返回一个布尔值 result ,result 为 True 当且仅当对 s 中的 所有 下标 i (下标从 0 开始),都满足下述条件:

  • 如果 i 是偶数,那么 getNumericValue(s[i]) < getNumericValue(s[i+1])

  • 如果 i 是奇数,那么 getNumericValue(s[i]) > getNumericValue(s[i+1])

示例 1:

输入:s = "a1b2c3" 输出:true 解释:对于 s 中的每个下标 i ,都满足下述条件:

  • i = 0 (偶数):getNumericValue(s[i]) = 1 ,getNumericValue(s[i+1]) = 1 。由于 1 < 1 ,条件满足。
  • i = 1 (奇数):getNumericValue(s[i]) = 1 ,getNumericValue(s[i+1]) = 2 。由于 1 > 2 ,条件满足。
  • i = 2 (偶数):getNumericValue(s[i]) = 2 ,getNumericValue(s[i+1]) = 3 。由于 2 < 3 ,条件满足。
  • i = 3 (奇数):getNumericValue(s[i]) = 3 ,getNumericValue(s[i+1]) = 3 。由于 3 > 3 ,条件满足。
  • i = 4 (偶数):getNumericValue(s[i]) = 3 ,getNumericValue(s[i+1]) = -1 (超出字符串边界)。由于 3 < -1 ,条件满足。 由于所有的条件都满足,返回 true 。

示例 2:

输入:s = "a123" 输出:false 解释:对于 s 中的每个下标 i ,都满足下述条件:

  • i = 0 (偶数):getNumericValue(s[i]) = 1 ,getNumericValue(s[i+1]) = -1 。由于 -1 < -1 ,条件不满足。
  • i = 1 (奇数):getNumericValue(s[i]) = -1 ,getNumericValue(s[i+1]) = -2 。由于 -1 > -2 ,条件不满足。
  • i = 2 (偶数):getNumericValue(s[i]) = -2 ,getNumericValue(s[i+1]) = -3 。由于 -2 < -3 ,条件不满足。 由于至少有一个条件不满足,返回 false 。

示例 3:

输入:s = "c7b2j6" 输出:true 解释:对于 s 中的每个下标 i ,都满足下述条件:

  • i = 0 (偶数):getNumericValue(s[i]) = 3 ,getNumericValue(s[i+1]) = 7 。由于 3 < 7 ,条件满足。
  • i = 1 (奇数):getNumericValue(s[i]) = 7 ,getNumericValue(s[i+1]) = 2 。由于 7 > 2 ,条件满足。
  • i = 2 (偶数):getNumericValue(s[i]) = 2 ,getNumericValue(s[i+1]) = 10 。由于 2 < 10 ,条件满足。
  • i = 3 (奇数):getNumericValue(s[i]) = 10 ,getNumericValue(s[i+1]) = 6 。由于 10 > 6 ,条件满足。
  • i = 4 (偶数):getNumericValue(s[i]) = 6 ,getNumericValue(s[i+1]) = -1 (超出字符串边界)。由于 6 < -1 ,条件满足。 由于所有的条件都满足,返回 true 。

提示:

  • 1 <= s.length <= 1000
  • s 只包含小写英文字母和数字。
  • s 中的数字取值为 [0, 9] ,所以如果 s[i] 是数字的话,getNumericValue(s[i]) 就等于 s[i] 本身(数字字符的 ASCII 码与它的数值相等)。

1.2 题目分析

这道题目的难度不大,主要是要理解题目中的 getNumericValue 函数的定义,以及如何判断字符串中的数字是否递增。我们可以用一个辅助函数来实现 getNumericValue 的功能,即如果字符是数字,就直接返回它的数值,如果是字母,就返回它在字母表中的位置加一。然后我们可以遍历字符串,每次比较相邻两个字符的数值,根据奇偶下标判断是否满足递增或递减的条件。如果遇到任何不满足的情况,就返回 false 。如果遍历完整个字符串都没有发现不满足的情况,就返回 true 。注意,当下标超出字符串边界时,我们可以认为它对应的数值是 -1 ,这样可以保证最后一个字符也能被正确判断。

1.3 代码实现

class Solution:
    def areNumbersAscending(self, s: str) -> bool:
        # 定义一个辅助函数,返回字符对应的数值
        def getNumericValue(c: str) -> int:
            if c.isdigit(): # 如果是数字,直接返回它的数值
                return int(c)
            else: # 如果是字母,返回它在字母表中的位置加一
                return ord(c) - ord('a') + 1

        # 遍历字符串
        for i in range(len(s) - 1):
            # 获取当前字符和下一个字符对应的数值
            cur = getNumericValue(s[i])
            nxt = getNumericValue(s[i + 1]) if i + 1 < len(s) else -1 # 如果下标超出边界,认为数值是 -1
            # 根据奇偶下标判断是否满足递增或递减的条件
            if i % 2 == 0: # 偶数下标
                if cur >= nxt: # 应该递增
                    return False
            else: # 奇数下标
                if cur <= nxt: # 应该递减
                    return False

        # 遍历完没有发现不满足的情况,返回 true
        return True

5910. 课程安排的合法性

2.1 题目描述

给你一个整数 n ,表示一所高中里课程的数量。编号为 1 到 n 的 n 门课程分别有一个预备课程列表 prerequisites[i] ,其中 prerequisites[i] 是编号为 i 的课程的所有预备课程编号组成的列表。

同时给你一个整数 k ,表示你可以完成的最大课程数量。

请你返回在不违反任何规则的情况下,你 最多 能完成多少门课程。规则如下:

  • 如果要完成编号为 i 的课程,那么你必须先完成它的所有预备课程。
  • 一次最多只能完成一门课程。

示例 1:

输入:n = 5, dependencies = [[2,1],[3,1],[4,1],[1,5]], k = 2 输出:4 解释:上图展示了给定的图形。在不违反任何规则的情况下,最多可以完成 4 门课程,它们分别是 [2,3,4,5] 。不能完成编号为 1 的课程,因为它是所有其他课程的预备课程。

示例 2:

输入:n = 11, dependencies = [], k = 2 输出:2 解释:没有任何依赖关系,所以可以完成任何两门课程。

提示:

  • 1 <= n <= 15
  • 0 <= dependencies.length <= n * (n-1) / 2
  • dependencies[i].length == 2
  • 1 <= xi, yi <= n
  • xi != yi
  • 所有先修关系都是不同的,也就是说 dependencies[i] != dependencies[j] 。
  • 题目输入保证每个节点 xi 在 prerequisites[i] 中至多出现一次。
  • 1 <= k <= n

2.2 题目分析

这道题目是一个拓扑排序的问题,我们需要找出一种合法的课程顺序,使得每个课程都在它的所有预备课程之后,并且在给定的 k 次机会内,尽可能多地完成课程。我们可以用一个有向图来表示课程之间的依赖关系,其中每个节点表示一个课程,每条边表示一个先修关系。例如,如果有一条边从节点 i 指向节点 j ,那么表示 i 是 j 的一个预备课程。

我们可以用一个数组 indegree 来记录每个节点的入度,即有多少条边指向该节点。如果一个节点的入度为零,那么表示它没有任何先修关系,可以直接完成。我们可以用一个队列来存储所有入度为零的节点,并依次出队处理它们。每次处理一个节点时,我们将它标记为已完成,并将它指向的所有节点的入度减一。如果某个节点的入度减为零,那么就将它加入队列中。这样我们就可以按照拓扑排序的顺序完成所有没有先修关系或者已经满足先修关系的课程。

但是题目要求我们在 k 次机会内尽可能多地完成课程,那么我们应该优先选择哪些课程呢?一种贪心的策略是优先选择那些出度最大的节点,也就是说指向其他节点最多的节点。这样可以使得更多的节点入度减少,从而增加可选的课程数量。我们可以用一个优先队列来实现这个策略,每次从队列中弹出一个出度最大的节点,并更新它指向的节点的入度。我们重复这个过程,直到队列为空或者 k 次机会用完。最后我们统计完成的课程数量,就是答案。

2.3 代码实现

import heapq
class Solution:
    def maxNumberOfCourses(self, n: int, dependencies: List[List[int]], k: int) -> int:
        # 构建有向图和入度数组
        graph = [[] for _ in range(n + 1)] # 邻接表表示有向图
        indegree = [0] * (n + 1) # 入度数组
        for x, y in dependencies: # 遍历每条边
            graph[x].append(y) # 添加邻接关系
            indegree[y] += 1 # 更新入度

        # 定义一个优先队列,按照出度从大到小排序
        pq = []
        for i in range(1, n + 1): # 遍历每个节点
            if indegree[i] == 0: # 如果入度为零,可以直接完成
                heapq.heappush(pq, (-len(graph[i]), i)) # 将节点的出度和编号加入优先队列

        # 定义一个变量,记录完成的课程数量
        ans = 0
        # 循环处理优先队列中的节点,直到队列为空或者 k 次机会用完
        while pq and k > 0:
            _, node = heapq.heappop(pq) # 弹出一个出度最大的节点
            ans += 1 # 完成该课程
            k -= 1 # 使用一次机会
            for nei in graph[node]: # 遍历该节点指向的所有邻居节点
                indegree[nei] -= 1 # 将邻居节点的入度减一
                if indegree[nei] == 0: # 如果邻居节点的入度变为零,可以加入优先队列
                    heapq.heappush(pq, (-len(graph[nei]), nei)) # 将邻居节点的出度和编号加入优先队列

        # 返回完成的课程数量
        return ans

5911. 零钱兑换 IV

3.1 题目描述

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 n ,表示总金额。

计算并返回可以凑成总金额 n 的硬币组合数。

如果任何硬币面额的数量都不限制,那么请返回 可能的硬币组合数 。

答案可能会很大,所以请将结果对 109 + 7 取余 后返回。

示例 1:

输入:n = 5, coins = [1, 2, 5] 输出:4 解释:有四种方式可以凑成总金额: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1

示例 2:

输入:n = 3, coins = [2] 输出:0 解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

输入:n = 10, coins = [10] 输出:1 解释:只用面额 10 的硬币可以凑成总金额 10 。

提示:

  • 1 <= n <= 104
  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同

3.2 题目分析

这道题目是一个经典的完全背包问题,我们需要求出用给定的硬币面额凑成目标金额的方案数。我们可以用动态规划的方法来解决这个问题,具体思路如下:

  • 定义一个数组 dp ,其中 dp[i] 表示凑成金额 i 的方案数。
  • 初始化 dp[0] = 1 ,表示凑成金额 0 的方案只有一种,即不使用任何硬币。
  • 遍历每种硬币面额 coin ,对于每个 coin ,从小到大遍历金额 i ,如果 i >= coin ,那么说明可以使用这个硬币来凑成金额 i ,此时有两种选择:
    • 不使用这个硬币,那么方案数为 dp[i] 不变;
    • 使用这个硬币,那么方案数为 dp[i - coin] ,表示使用了这个硬币后,剩余的金额为 i - coin 的方案数。
  • 因此,我们可以得到状态转移方程:dp[i] = dp[i] + dp[i - coin] ,表示凑成金额 i 的方案数等于不使用这个硬币的方案数加上使用这个硬币的方案数。
  • 最后,我们返回 dp[n] 即可,表示凑成目标金额 n 的方案数。

3.3 代码实现

class Solution:
    def waysToChange(self, n: int, coins: List[int]) -> int:
        # 定义一个数组,表示凑成各个金额的方案数
        dp = [0] * (n + 1)
        # 初始化,凑成金额0的方案只有一种,即不使用任何硬币
        dp[0] = 1
        # 遍历每种硬币面额
        for coin in coins:
            # 对于每个面额,从小到大遍历金额
            for i in range(coin, n + 1):
                # 如果当前金额大于等于面额,那么可以使用这个硬币来凑成
                # 此时有两种选择:不使用这个硬币,或者使用这个硬币
                # 方案数等于两种选择的和
                dp[i] = (dp[i] + dp[i - coin]) % (10**9 + 7)
        # 返回凑成目标金额的方案数
        return dp[n]