第一题:分析好题目,示例3的解释是一个陷阱。
第二题:模拟。
第三题:最短路。
第四题:动态规划 DP。
详细题解如下。
1.删除回文子序列(Remove Palindromic Subsequences)
2. 餐厅过滤器(Filter Restaurants By Vegan Friendly Price And Distance)
3.阈值距离内邻居最少的城市(Find The City With The Smallest Number Of Neighbors At A Threshold Distance)
4.工作计划的最低难度(Minimum Difficulty Of A Job Schedule)
LeetCode第173场周赛地址:
https://leetcode-cn.com/contest/weekly-contest-173
1.删除回文子序列(Remove Palindromic Subsequences)
题目链接
https://leetcode-cn.com/problems/remove-palindromic-subsequences/
题意
给你一个字符串 s,它仅由字母 ‘a’ 和 ‘b’ 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。
返回删除给定字符串中所有字符(字符串为空)的最小删除次数。
「子序列」定义:如果一个字符串可以通过删除原字符串某些字符而不改变原字符顺序得到,那么这个字符串就是原字符串的一个子序列。
「回文」定义:如果一个字符串向后和向前读是一致的,那么这个字符串就是一个回文。
示例 1:
输入:s = "ababa" 输出:1 解释:字符串本身就是回文序列,只需要删除一次。
示例 2:
输入:s = "abb" 输出:2 解释:"abb" -> "bb" -> "". 先删除回文子序列 "a",然后再删除 "bb"。
示例 3:
输入:s = "baabb" 输出:2 解释:"baabb" -> "b" -> "". 先删除回文子序列 "baab",然后再删除 "b"。
示例 4:
输入:s = "" 输出:0
提示:
0 <= s.length <= 1000
s
仅包含字母 ‘a’ 和 ‘b’
解题思路
这道题其实不难,因为它毕竟是一个Easy的题目。重要的是分析好题目。
由字母 ‘a’ 和 ‘b’ 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。
所以我们分析出,其实最多删除操作为 2,因为我们可以一次删除全部 a(回文子序列),第二次再删除 b。
至于示例 3 的解释,是一个误导,一个陷阱。
这道题最重要的就是分析,我们已经分析出,最多的操作为2,
1)何时为 0,也就是空串为 0
2)何时为 1,也就是原本的序列就是一个回文串,可以一次性全部删除
3)剩下的情况,就是2,一次删除全a,一次删除全b。
AC代码(C++)
class Solution {
public:
int removePalindromeSub(string s) {
int n = s.size();
if(n == 0) return 0;
for(int i = 0;i < n/2; ++i)
{
if(s[i] != s[n - 1 - i])
return 2;
}
return 1;
}
};
2. 餐厅过滤器(Filter Restaurants By Vegan Friendly Price And Distance)
题目链接
https://leetcode-cn.com/problems/filter-restaurants-by-vegan-friendly-price-and-distance/
题意
给你一个餐馆信息数组 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 。
示例 1:
输入:restaurants = [[1,4,1,40,10],[2,8,0,50,5],[3,8,1,30,4],[4,10,0,10,3],[5,1,1,15,1]], veganFriendly = 1, maxPrice = 50, maxDistance = 10 输出:[3,1,5] 解释: 这些餐馆为: 餐馆 1 [id=1, rating=4, veganFriendly=1, price=40, distance=10] 餐馆 2 [id=2, rating=8, veganFriendly=0, price=50, distance=5] 餐馆 3 [id=3, rating=8, veganFriendly=1, price=30, distance=4] 餐馆 4 [id=4, rating=10, veganFriendly=0, price=10, distance=3] 餐馆 5 [id=5, rating=1, veganFriendly=1, price=15, distance=1] 在按照 veganFriendly = 1, maxPrice = 50 和 maxDistance = 10 进行过滤后,我们得到了餐馆 3, 餐馆 1 和 餐馆 5(按评分从高到低排序)。
示例 2:
输入:restaurants = [[1,4,1,40,10],[2,8,0,50,5],[3,8,1,30,4],[4,10,0,10,3],[5,1,1,15,1]], veganFriendly = 0, maxPrice = 50, maxDistance = 10 输出:[4,3,2,1,5] 解释:餐馆与示例 1 相同,但在 veganFriendly = 0 的过滤条件下,应该考虑所有餐馆。
示例 3:
输入:restaurants = [[1,4,1,40,10],[2,8,0,50,5],[3,8,1,30,4],[4,10,0,10,3],[5,1,1,15,1]], veganFriendly = 0, maxPrice = 30, maxDistance = 3 输出:[4,5]
提示:
1 <= restaurants.length <= 10^4
restaurants[i].length == 5
1 <= idi, ratingi, pricei, distancei <= 10^5
1 <= maxPrice, maxDistance <= 10^5
veganFriendlyi 和 veganFriendly 的值为 0 或 1 。
所有 idi 各不相同。
解题思路
这道题,只要我们按照题目意思进行模拟即可。
按顺序遍历所有餐厅,把符合三个条件的餐厅的 id 和 rating,保存在一个二维数组中。
然后对这个二维数组排序,排序规则 : 对 rating 降序排序,如果 rating 相同,则对 id 降序排序。
然后按这个顺序,返回 所有 id。
AC代码(C++)
bool cmp(vector<int> a, vector<int> b)
{
if(a[1] != b[1])
return a[1] > b[1];
else
return a[0] > b[0];
}
class Solution {
public:
vector<int> filterRestaurants(vector<vector<int>>& restaurants, int veganFriendly, int maxPrice, int maxDistance) {
vector<vector<int> > a;
for(auto r : restaurants)
{
if(veganFriendly * r[2] == veganFriendly && r[3] <= maxPrice && r[4] <= maxDistance)
{
a.push_back({r[0], r[1]});
}
}
sort(a.begin(), a.end(), cmp);
vector<int> ans;
for(int i = 0;i < a.size();++i)
ans.push_back(a[i][0]);
return ans;
}
};
3.阈值距离内邻居最少的城市(Find The City With The Smallest Number Of Neighbors At A Threshold Distance)
题目链接
题意
有 n 个城市,按从 0 到 n-1 编号。给你一个边数组 edges,其中 edges[i] = [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边,距离阈值是一个整数 distanceThreshold。
返回能通过某些路径到达其他城市数目最少、且路径距离 最大 为 distanceThreshold 的城市。如果有多个这样的城市,则返回编号最大的城市。
注意,连接城市 i 和 j 的路径的距离等于沿该路径的所有边的权重之和。
示例 1:
示例有图,具体看链接 输入:n = 4, edges = [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], distanceThreshold = 4 输出:3 解释:城市分布图如上。 每个城市阈值距离 distanceThreshold = 4 内的邻居城市分别是: 城市 0 -> [城市 1, 城市 2] 城市 1 -> [城市 0, 城市 2, 城市 3] 城市 2 -> [城市 0, 城市 1, 城市 3] 城市 3 -> [城市 1, 城市 2] 城市 0 和 3 在阈值距离 4 以内都有 2 个邻居城市,但是我们必须返回城市 3,因为它的编号最大。
示例 2:
示例有图,具体看链接 输入:n = 5, edges = [[0,1,2],[0,4,8],[1,2,3],[1,4,2],[2,3,1],[3,4,1]], distanceThreshold = 2 输出:0 解释:城市分布图如上。 每个城市阈值距离 distanceThreshold = 2 内的邻居城市分别是: 城市 0 -> [城市 1] 城市 1 -> [城市 0, 城市 4] 城市 2 -> [城市 3, 城市 4] 城市 3 -> [城市 2, 城市 4] 城市 4 -> [城市 1, 城市 2, 城市 3] 城市 0 在阈值距离 4 以内只有 1 个邻居城市。
提示:
- 2 <= n <= 100
- 1 <= edges.length <= n * (n – 1) / 2
- edges[i].length == 3
- 0 <= fromi < toi < n
- 1 <= weighti, distanceThreshold <= 10^4
- 所有 (fromi, toi) 都是不同的。
解题分析
这道题,典型的,多源,最短路问题。
最短路问题可以用 BFS,Floyd,Dijkstra,spfa。
根据题目,是一个多源,有权,正权,同时数据范围。可以选择Floyd,Dijkstra,spfa。
这里我用了简单易写的 Floyd 算法。
用 Floyd 算法 得到所有城市之间的最短的 dp[ i ][ j ]。枚举每一个城市 i ,在阈值范围内distanceThreshold,可以到达的所有城市 数量。然后我们要得到最少数量的城市 i 。如果数量相同,则输出 更大的 i。
因此我们可以用两个值,ans记录 当前的城市,minVal记录这个城市能达到的城市数量。然后对应每一个 i,计算新的 cnt。因为要输出数量少的,那么就 cnt <= minVal,这里我们还用了 = ,因此即使当数量一样的时候,我们要输出更大的城市编号,我们的 i 是从小到大遍历的。
AC代码(C++)
#define INF 0x3f3f3f3f
class Solution {
public:
int findTheCity(int n, vector<vector<int>>& edges, int distanceThreshold) {
// Floyd算法模板
vector<vector<int> > dp(n, vector<int>(n, INF));
for(auto e : edges)
{
dp[e[0]][e[1]] = e[2];
dp[e[1]][e[0]] = e[2];
}
for(int i = 0;i < n;++i) dp[i][i] = 0;
for(int k = 0;k < n; ++k)
for(int i = 0;i < n;++i)
for(int j = 0;j < n;++j)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
// 按题目要求处理
int ans = -1, minVal = 150;
for(int i = 0;i < n;i++)
{
int cnt = 0;
for(int j = 0;j < n;j++)
{
if(j==i) continue;
if(dp[i][j] <= distanceThreshold)
++cnt;
}
if(cnt <= minVal){
ans = i;
minVal = cnt;
}
}
return ans;
}
};
4.工作计划的最低难度(Minimum Difficulty Of A Job Schedule)
题目链接
https://leetcode-cn.com/problems/minimum-difficulty-of-a-job-schedule/
题意
你需要制定一份 d 天的工作计划表。工作之间存在依赖,要想执行第 i 项工作,你必须完成全部 j 项工作( 0 <= j < i)。
你每天 至少 需要完成一项任务。工作计划的总难度是这 d 天每一天的难度之和,而一天的工作难度是当天应该完成工作的最大难度。
给你一个整数数组 jobDifficulty 和一个整数 d,分别代表工作难度和需要计划的天数。第 i 项工作的难度是 jobDifficulty[i]。
返回整个工作计划的 最小难度 。如果无法制定工作计划,则返回 -1 。
示例 1:
示例有图,具体看链接 输入:jobDifficulty = [6,5,4,3,2,1], d = 2 输出:7 解释:第一天,您可以完成前 5 项工作,总难度 = 6. 第二天,您可以完成最后一项工作,总难度 = 1. 计划表的难度 = 6 + 1 = 7
示例 2:
输入:jobDifficulty = [9,9,9], d = 4 输出:-1 解释:就算你每天完成一项工作,仍然有一天是空闲的,你无法制定一份能够满足既定工作时间的计划表。
示例 3:
输入:jobDifficulty = [1,1,1], d = 3 输出:3 解释:工作计划为每天一项工作,总难度为 3 。
示例 4:
输入:jobDifficulty = [11,111,22,222,33,333,44,444], d = 6 输出:843
提示:
1 <= jobDifficulty.length <= 300
0 <= jobDifficulty[i] <= 1000
1 <= d <= 10
解题分析
根据题目意思,我们要求,最小难度,那么考虑能不能使用 动态规划 DP。
1)设变量,dp[ i ][ j ]表示 在 i 天,做了 j 个工作的最小难度。
2)初始化,因为是最小难度,初始化为最大值 INF。同时 dp[ 0 ][ 0 ] = 0,没开始做,所以难度为0。因此我们设的变量 dp的二维数组大小是 [ 天数 + 1 ][ 工作数量 + 1 ]
3)状态转移方程。我们当前 dp[ i ][ j ],是由第 i – 1 天转移过来的,那么枚举 i – 1 的做的工作,last 从 j – 1 到 i – 1。因此要至少一天做一个工作,所以 i – 1 天,要做了 i – 1个工作。
所以状态转移方程 :
所以在转移的时候,我们从 i – 1天做了 已经做了last,那么剩下的 last + 1 到 目前的 j,都是给这一天来做的,那么这一天的需要的难度就是这里面的最大值。
为了方便求这个最大值,我们 last 是从last 从 j – 1 到 i – 1 遍历的,从而可以方便更新最大值,而不需要预处理。
4)最后的答案 dp[ d ][ n ],d是天数,n是工作长度
那么什么时候输出 -1 呢,根据示例2,因为要求一天至少做一个工作,所以当工作长度小于天数时,输出 -1。
不然的话,一定可以求出难度,那么也就是可以求出最小难度。
AC代码(C++)
#define INF 0x3f3f3f3f
class Solution {
public:
// dp[i][j] : 表示,在 i 天,做了 j 个工作的最小难度
// dp[i][j] = min( dp[i - 1][last] + 从[last + 1 到 j 的最大难度])
int minDifficulty(vector<int>& jobDifficulty, int d) {
int n = jobDifficulty.size();
if(n < d) return -1;
// 初始化
vector<vector<int> > dp(d + 1, vector<int>(n + 1, INF));
dp[0][0] = 0;
for(int i = 1;i <= d; ++i)
{
for(int j = 1;j <= n;++j)
{
// 要得到 last + 1 到 j 的最大难度,所以last从后到 0,这样子每一次新出来的工作和之前的最大难度取最大值即可。
// 一开始,只做 j 工作,所以最大值就是 j 的难度
int mx = jobDifficulty[j - 1];
for(int last = j - 1; last >= i - 1;--last)
{
dp[i][j] = min(dp[i][j], dp[i - 1][last] + mx);
// last要往前移动,此时这个工作归到后一天,那么更新 mx,求最大难度
if(last > 0) mx = max(mx, jobDifficulty[last - 1]);
}
}
}
// 输出
return dp[d][n];
}
};