力扣周赛

254 阅读6分钟

力扣周赛 346

2696. 删除子串后的字符串最小长度

给你一个仅由 大写 英文字符组成的字符串 s 。

你可以对此字符串执行一些操作,在每一步操作中,你可以从 s 中删除 任一个 "AB" 或 "CD" 子字符串。

通过执行操作,删除所有 "AB" 和 "CD" 子串,返回可获得的最终字符串的 最小 可能长度。

注意,删除子串后,重新连接出的字符串可能会产生新的 "AB" 或 "CD" 子串。

示例 1:

输入:s = "ABFCACDB" 输出:2 解释:你可以执行下述操作:

  • 从 "ABFCACDB" 中删除子串 "AB",得到 s = "FCACDB" 。
  • 从 "FCACDB" 中删除子串 "CD",得到 s = "FCAB" 。
  • 从 "FCAB" 中删除子串 "AB",得到 s = "FC" 。 最终字符串的长度为 2 。 可以证明 2 是可获得的最小长度。

示例 2:

输入:s = "ACBBD" 输出:5 解释:无法执行操作,字符串长度不变。

题解

这个问题可以通过贪心算法来解决。具体来说,我们可以反复执行以下两个步骤:

  1. 找到一个 "AB" 或 "CD" 子串并将其删除。
  2. 删除所有新产生的 "AB" 或 "CD" 子串。

我们可以证明这种贪心策略是最优的。具体来说,任何一种不包含 "AB" 或 "CD" 的最终结果都可以通过上述操作得到。而且,如果我们在某个时刻删除了一个子串,那么我们就不需要再考虑它了,因为我们已经将其删除了。因此,我们可以保证上述贪心策略是最优的。

代码

class Solution:
    def minimumLength(self, s: str) -> int:
        while len(s) > 1 and s[0] == s[-1]:
            c = s[0]
            while s and s[0] == c:
                s = s[1:]
            while s and s[-1] == c:
                s = s[:-1]
        return len(s)

2697. 字典序最小回文串

给你一个由 小写英文字母 组成的字符串 s ,你可以对其执行一些操作。在一步操作中,你可以用其他小写英文字母 替换 s 中的一个字符。

请你执行 尽可能少的操作 ,使 s 变成一个 回文串 。如果执行 最少 操作次数的方案不止一种,则只需选取 字典序最小 的方案。

对于两个长度相同的字符串 a 和 b ,在 a 和 b 出现不同的第一个位置,如果该位置上 a 中对应字母比 b 中对应字母在字母表中出现顺序更早,则认为 a 的字典序比 b 的字典序要小。

返回最终的回文字符串。

示例 1:

输入:s = "egcfe" 输出:"efcfe" 解释:将 "egcfe" 变成回文字符串的最小操作次数为 1 ,修改 1 次得到的字典序最小回文字符串是 "efcfe",只需将 'g' 改为 'f' 。

示例 2:

输入:s = "abcd" 输出:"abba" 解释:将 "abcd" 变成回文字符串的最小操作次数为 2 ,修改 2 次得到的字典序最小回文字符串是 "abba" 。

示例 3:

输入:s = "seven" 输出:"neven" 解释:将 "seven" 变成回文字符串的最小操作次数为 1 ,修改 1 次得到的字典序最小回文字符串是 "neven" 。

题解

我们可以将字符串分为两部分,左边部分为回文串,右边部分为非回文串。我们可以将右边部分翻转后加到左边部分后面,这样就可以得到一个回文串。但是这样得到的回文串不一定是字典序最小的,因此我们需要对右边部分进行一些处理。

我们可以将右边部分的第一个字符与左边部分的最后一个字符进行比较,如果右边部分的第一个字符较小,那么我们就可以将右边部分的第一个字符加到左边部分的最后,这样就可以得到一个字典序更小的回文串。如果右边部分的第一个字符较大,那么我们就需要将右边部分的第一个字符加到左边部分的最前面,然后将右边部分的第二个字符加到左边部分的最后,这样就可以得到一个字典序更小的回文串。

代码

class Solution:
    def breakPalindrome(self, s: str) -> str:
        if len(s) == 1:
            return ""
        for i in range(len(s) // 2):
            if s[i] != "a":
                return s[:i] + "a" + s[i + 1:]
        return s[:-1] + "b"

2698. 求一个整数的惩罚数

给你一个正整数 n ,请你返回 n 的 惩罚数 。

n 的 惩罚数 定义为所有满足以下条件 i 的数的平方和:

1 <= i <= n i * i 的十进制表示的字符串可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于 i 。

示例 1:

输入:n = 10 输出:182 解释:总共有 3 个整数 i 满足要求:

  • 1 ,因为 1 * 1 = 1
  • 9 ,因为 9 * 9 = 81 ,且 81 可以分割成 8 + 1 。
  • 10 ,因为 10 * 10 = 100 ,且 100 可以分割成 10 + 0 。 因此,10 的惩罚数为 1 + 81 + 100 = 182

示例 2:

输入:n = 37 输出:1478 解释:总共有 4 个整数 i 满足要求:

  • 1 ,因为 1 * 1 = 1
  • 9 ,因为 9 * 9 = 81 ,且 81 可以分割成 8 + 1 。
  • 10 ,因为 10 * 10 = 100 ,且 100 可以分割成 10 + 0 。
  • 36 ,因为 36 * 36 = 1296 ,且 1296 可以分割成 1 + 29 + 6 。 因此,37 的惩罚数为 1 + 81 + 100 + 1296 = 1478

题解

我们可以使用动态规划的方法来解决这个问题。具体来说,我们可以使用一个数组 dp 来记录惩罚数,其中 dp[i] 表示 i 的惩罚数。我们可以从小到大依次计算 dp[i] 的值。

对于 dp[i] 的计算,我们可以枚举 i 的所有因数 j,如果 j 的惩罚数为 j * j,且 i / j 的惩罚数为 dp[i / j],那么 i 的惩罚数为 dp[i / j] + j * j。我们可以使用一个哈希表来快速计算 i 的所有因数。

代码

class Solution:
    def getLucky(self, s: str, k: int) -> int:
        s = "".join(str(ord(c) - ord("a") + 1) for c in s)
        for _ in range(k):
            s = str(sum(int(c) for c in s))
        return int(s)

2699. 修改图中的边权

给你一个 n 个节点的 无向带权连通 图,节点编号为 0 到 n - 1 ,再给你一个整数数组 edges ,其中 edges[i] = [ai, bi, wi] 表示节点 ai 和 bi 之间有一条边权为 wi 的边。

部分边的边权为 -1(wi = -1),其他边的边权都为 正 数(wi > 0)。

你需要将所有边权为 -1 的边都修改为范围 [1, 2 * 109] 中的 正整数 ,使得从节点 source 到节点 destination 的 最短距离 为整数 target 。如果有 多种 修改方案可以使 source 和 destination 之间的最短距离等于 target ,你可以返回任意一种方案。

如果存在使 source 到 destination 最短距离为 target 的方案,请你按任意顺序返回包含所有边的数组(包括未修改边权的边)。如果不存在这样的方案,请你返回一个 空数组 。

注意:你不能修改一开始边权为正数的边。

示例 1:

输入:n = 5, edges = [[4,1,-1],[2,0,-1],[0,3,-1],[4,3,-1]], source = 0, destination = 1, target = 5 输出:[[4,1,1],[2,0,1],[0,3,3],[4,3,1]] 解释:上图展示了一个满足题意的修改方案,从 0 到 1 的最短距离为 5 。

示例 2:

输入:n = 3, edges = [[0,1,-1],[0,2,5]], source = 0, destination = 2, target = 6 输出:[] 解释:上图是一开始的图。没有办法通过修改边权为 -1 的边,使得 0 到 2 的最短距离等于 6 ,所以返回一个空数组。 示例 3:

输入:n = 4, edges = [[1,0,4],[1,2,3],[2,3,5],[0,3,-1]], source = 0, destination = 2, target = 6 输出:[[1,0,4],[1,2,3],[2,3,5],[0,3,1]] 解释:上图展示了一个满足题意的修改方案,从 0 到 2 的最短距离为 6 。

题解

我们可以使用 Dijkstra 算法来解决这个问题。具体来说,我们可以使用一个优先队列来维护当前到达每个节点的最短距离,每次从优先队列中取出距离最小的节点,然后更新与其相邻的节点的距离。当我们取出节点 u 时,如果 u 与 v 之间的边权为 -1,那么我们可以将 u 与 v 之间的边权修改为 1,这样就可以保证 u 与 v 之间的距离为 1。我们可以使用一个数组来记录每个节点的前驱节点,这样就可以还原出一条从 source 到 destination 的最短路径。

代码

class Solution:
    def findShortestPath(self, n: int, edges: List[List[int]], source: int, destination: int, distanceThreshold: int) -> List[int]:
        graph = collections.defaultdict(list)
        for u, v, w in edges:
            graph[u].append((v, w))
            graph[v].append((u, w))
        dist = [float("inf")] * n
        dist[source] = 0
        prev = [-1] * n
        pq = [(0, source)]
        while pq:
            d, u = heapq.heappop(pq)
            if d > dist[u]:
                continue
            for v, w in graph[u]:
                if w == -1:
                    w = 1
                if dist[u] + w < dist[v]:
                    dist[v] = dist[u] + w
                    prev[v] = u
                    heapq.heappush(pq, (dist[v], v))
        if dist[destination] > distanceThreshold:
            return []
        ans = []
        while destination != -1:
            ans.append(destination)
            destination = prev[destination]
        return ans[::-1]