[Leetcode Weekly Contest]173
链接:LeetCode
[Leetcode]5319.删除回文子序列
给你一个字符串 s,它仅由字母 'a' 和 'b' 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。返回删除给定字符串中所有字符(字符串为空)的最小删除次数。
注意这里,「子序列」定义:如果一个字符串可以通过删除原字符串某些字符而不改变原字符顺序得到,那么这个字符串就是原字符串的一个子序列。
「回文」定义:如果一个字符串向后和向前读是一致的,那么这个字符串就是一个回文。
这道题关键在于“审题”,当发现字符串仅有字母 'a' 和 'b' 组成,那么我们联想到,删除给定字符串中所有字符(字符串为空)的最小删除次数只有三种情况:
- 0种,即字符串为空
- 1种,即字符串本身为回文
- 2种,即其他情况。因为当字符串仅有字母 'a' 和 'b' 组成,则我们可以将所有包含a的子序列提取出来删除,再将所有b的子序列删除,此时只需要两次就实现了删除所有字符。
class Solution:
def removePalindromeSub(self, s: str) -> int:
if not s:
return 0
if s == s[::-1]:
return 1
return 2
[Leetcode]5320.餐厅过滤器
给一个餐馆信息数组 restaurants,其中 \(restaurants[i] = [idi, ratingi, veganFriendlyi, pricei, distancei]\)。你必须使用以下三个过滤器来过滤这些餐馆信息。
其中素食者友好过滤器 veganFriendly 的值可以为 true 或者 false,如果为 true 就意味着你应该只包括 veganFriendlyi 为 true 的餐馆,为 false 则意味着可以包括任何餐馆。此外,我们还有最大价格 maxPrice 和最大距离 maxDistance 两个过滤器,它们分别考虑餐厅的价格因素和距离因素的最大值。
过滤后返回餐馆的 id,按照 rating 从高到低排序。如果 rating 相同,那么按 id 从高到低排序。简单起见, veganFriendlyi 和 veganFriendly 为 true 时取值为 1,为 false 时,取值为 0 。
当理解题意之后,代码也就很简单了,这不过是一道普通的业务理解题:
class Solution:
def filterRestaurants(self, restaurants: List[List[int]], veganFriendly: int, maxPrice: int, maxDistance: int) -> List[int]:
if veganFriendly:
restaurants = [x for x in restaurants if x[2]==1]
restaurants = [x for x in restaurants if x[3]<=maxPrice and x[4]<=maxDistance]
restaurants.sort(key=lambda x:[-x[1],-x[0]])
return [x[0] for x in restaurants]
[Leetcode]5321.阈值距离内邻居最少的城市
有\(n\)个城市,按从\(0\)到\(n-1\)编号。给你一个边数组\(edges\),其中 \(edges[i] = [fromi, toi, weighti]\) 代表 \(from_i\) 和 \(to_i\) 两个城市之间的双向加权边,距离阈值是一个整数\(distanceThreshold\)。
返回能通过某些路径到达其他城市数目最少、且路径距离最大为\(distanceThreshold\)的城市。如果有多个这样的城市,则返回编号最大的城市。
考察点在于寻找每个城市间的最短路径,常用的有弗洛伊德算法:
class Solution:
def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int:
matrix = [[float('inf') for j in range(n)] for i in range(n)]
for from_,to_,weight_ in edges:
matrix[from_][to_] = weight_
matrix[to_][from_] = weight_
for i in range(n):
matrix[i][i] = 0
res = self.floyd(matrix,n)
ids = [0 for _ in range(n)]
for i in range(n):
ids[i] = len([x for x in res[i] if x<=distanceThreshold])
mn = min(ids)
res = [i for i,n in enumerate(ids) if n==mn]
return max(res)
# F算法
def floyd(self,matrix,n):
for i in range(n):
for j in range(n):
for k in range(n):
matrix[j][k] = min(matrix[j][k], matrix[j][i] + matrix[i][k])
return matrix
[Leetcode]5322.工作计划的最低难度
你需要制定一份\(d\)天的工作计划表。工作之间存在依赖,要想执行第\(i\)项工作,你必须完成全部\(j\)项工作\((0 <= j < i)\)。
你每至少需要完成一项任务。工作计划的总难度是这\(d\)天每一天的难度之和,而一天的工作难度是当天应该完成工作的最大难度。
给你一个整数数组\(jobDifficulty\)和一个整数\(d\),分别代表工作难度和需要计划的天数。第\(i\)项工作的难度是\(jobDifficulty[i]\)。返回整个工作计划的最小难度。如果无法制定工作计划,则返回 -1 。
这是一道动态规划题,难度在于两个:构造动态转移方程以及状态的初始化。
先构造动态转移方程,我们令\(dp[i][j-1]\)表示计划\(i\)天完成\(j-1\)项工作的最小难度,考察\(dp[i][j]\)的值,有以下可能:
- 当\(jobDifficulty[j-1]>jobDifficulty[j-2]\),即当前项比前面一项大,则我们考察每一个前面的\(k,k<=j\),令\(dp[i][j] = min(dp[i][j],dp[i-1][k-1]+preMax)\),其中preMax代表\(jobDifficulty[k-1:j]\)的最大值,也就是说,我们把\(jobDifficulty[k-1:j]\)的工作在一天完成,那么这一天的日工作量也就是其中的最大难度,再加上前面的\(i-1\)天的最小难度,也就是在当前\(k\)值下的最小难度。遍历k即可得到全局最小难度。
- 当\(jobDifficulty[j-1]<=jobDifficulty[j-2]\),即当前项比前面一项小,那么我们可以将当期项单独在一天完成(也就是\(i-1\)天完成\(j-1\)项的最小难度加上当前项难度,即\(dp[i-1][j-1]+jobDifficulty[j-1]\)),也可以把当前项与前面项共同完成(由于比前面项小,所以此时不影响\(dp[i][j-1]\)的值),因此转移式如下: \(dp[i][j] = min(dp[i][j-1],dp[i-1][j-1]+jobDifficulty[j-1])\)
总的来说,考察\(i\)天完成\(j\)项工作的最小难度,当前项工作只有可能单独完成或者是跟前面项一起完成,考虑这两种情况即可。
另外,要特别考虑状态的初始化,当\(i==j==0\)时,\(dp[i][j]=0\);否则,当\(i==0\) or \(j==0\) or \(j<i\) 时,$dp[i][j]=\infin $
整体代码如下:
class Solution:
def minDifficulty(self, jobDifficulty: List[int], d: int) -> int:
n = len(jobDifficulty)
if n < d:
return -1
if n == d:
return sum(jobDifficulty)
dp = [[float('inf') for j in range(n+1)] for i in range(d+1)]
dp[0][0] = 0
for i in range(1,d+1):
for j in range(i,n+1):
if i == j:
dp[i][j] = sum(jobDifficulty[:j])
elif jobDifficulty[j-1] <= jobDifficulty[j-2]:
dp[i][j] = min(dp[i][j-1],dp[i-1][j-1]+jobDifficulty[j-1])
else:
preMax = jobDifficulty[j-1]
for k in reversed(range(1,j+1)):
preMax = max(preMax,jobDifficulty[k-1])
dp[i][j] = min(dp[i][j],dp[i-1][k-1]+preMax)
return dp[-1][-1]