1332. 删除回文子序列
给你一个字符串 s,它仅由字母 'a' 和 'b' 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。
返回删除给定字符串中所有字符(字符串为空)的最小删除次数。
「子序列」定义:如果一个字符串可以通过删除原字符串某些字符而不改变原字符顺序得到,那么这个字符串就是原字符串的一个子序列。
「回文」定义:如果一个字符串向后和向前读是一致的,那么这个字符串就是一个回文。
示例 1:
输入:s = "ababa"
输出:1
解释:字符串本身就是回文序列,只需要删除一次。
示例 2:
输入:s = "abb"
输出:2
解释:"abb" -> "bb" -> "".
先删除回文子序列 "a",然后再删除 "bb"。
这题乍一看非常唬人,很容易让人联想到最长回文子串这类题,然后动态规划一顿操作……实际上非常简单,最关键的是题目中给你说了仅由字母 'a' 和 'b' 组成,而且每一次删除操作都可以从 s 中删除一个回文子序列(注意:子序列是指一个字符串中非连续!!!的字符串)。而且因此删除操作最多只有两次,删 'a' + 删 ' b' 。
public int removePalindromeSub(String s) {
if (s.isEmpty()) {
return 0;
}
StringBuilder builder = new StringBuilder(s);
if (builder.reverse().toString().equals(s)) {
return 1;
}
return 2;
}
1333. 餐厅过滤器
给你一个餐馆信息数组 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(按评分从高到低排序)。
这道题题意很简单,思路也很简单。就是根据题目中的条件,然后进行筛选处理就可以了。难点在于题目中的条件很多,如果直接按照条件进行筛选,编写代码非常麻烦,不仅需要层层的判断条件,还需要排序。最好的做法是利用Java8中的流操作。
public List<Integer> filterRestaurants(int[][] restaurants, int veganFriendly, int maxPrice, int maxDistance) {
//传统方法
// List<Integer> result = new ArrayList<>();
// for (int i = 0; i < restaurants.length; i++) {
// int id = restaurants[i][0];
// int rating = restaurants[i][1];
// int friendly = restaurants[i][2];
// int price = restaurants[i][3];
// int distance = restaurants[i][4];
// ...
// //处理删选条件太多
// }
//流处理
return Arrays.stream(restaurants)
.filter(array -> veganFriendly == 0 || array[2] == veganFriendly) //过滤
.filter(array -> array[3] <= maxPrice)
.filter(array -> array[4] <= maxDistance)
.sorted((x, y) -> {
if (x[1] - y[1] != 0) { //排序
return y[1] - x[1];
} else {
return y[0] - x[0];
}
})
.map(x -> x[0]) //映射:将元素映射成另外一个元素
.collect(Collectors.toList()); //将结果转换为List
}
1334. 阈值距离内邻居最少的城市
有 n 个城市,按从 0 到 n-1 编号。给你一个边数组 edges,其中 edges[i] = [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边,距离阈值是一个整数 distanceThreshold。
返回能通过某些路径到达其他城市数目最少、且路径距离 最大 为 distanceThreshold 的城市。如果有多个这样的城市,则返回编号最大的城市。
注意,连接城市 i 和 j 的路径的距离等于沿该路径的所有边的权重之和。
输入:n = 4, edges = [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], distanceThreshold = 4
输出:3
解释:城市分布图如上。
每个城市阈值距离 distanceThreshold = 4 内的邻居城市分别是:
城市 0 -> [城市 1, 城市 2] // 0 -> 3 距离大于4,不符合
城市 1 -> [城市 0, 城市 2, 城市 3]
城市 2 -> [城市 0, 城市 1, 城市 3]
城市 3 -> [城市 1, 城市 2] // 3 -> 0 距离大于4,不符合
城市 0 和 3 在阈值距离 4 以内都有 2 个邻居城市,但是我们必须返回城市 3,因为它的编号最大。
分析:
本题是典型的最短路径问题,这类问题通常有两种解法:Floyd算法 和 Dijkstra算法,本题可采用Floyd算法。
Floyd算法:
Floyd算法又称插点法,其中算法的核心思想是动态规划。
算法步骤:
- 通过已知条件初始化距离矩阵D[n][n],其中D[i][j]表示,顶点i到顶点j的距离。
- n个顶点依次作为插入点,例如,k为其中一个顶点,D[i][k] + D[k][j] < D[i][j],那说明顶点i经过顶点k再到达j,比直接到达j要近。所以更新D[i][j]:D[i][j] = D[i][k] + D[k][j]。
- 可以归纳得到状态转移方程:D[i][j] = min(D[i,k]+D[k,j],D[i,j]);
核心代码:
// Floyd算法
for (int k = 0; k < n; k++) {
// n个顶点依次作为插入点
// 注意插点k是放在第一层循环
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// 遍历各个顶点之间的距离,并用插入点进行更新
D[i][j] = min(D[i][k]+D[k][j], D[i][j]);
}
}
}
本题完整代码如下:
public int findTheCity(int n, int[][] edges, int distanceThreshold) {
int[][] map = new int[n][n];
for (int i = 0; i < edges.length; i++) {
map[edges[i][0]][edges[i][1]] = edges[i][2];
map[edges[i][1]][edges[i][0]] = edges[i][2];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j) {
if (map[i][j] == 0) {
map[i][j] = 100000; //注意:此处不能设置为Integer.MAX_VALUE, 计算会导致越界造成不可知的结果
}
}
}
}
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
map[i][j] = Math.min(map[i][j], map[i][k] + map[k][j]);
}
}
}
int mind = n; //当前到达其他城市的最少数目
int min = Integer.MAX_VALUE; //符合要求的城市编号
for (int i = 0; i < n; i++) {
int count = 0; //到达其他城市的数目
for (int j = 0; j < n; j++) {
int a = map[i][j];
if (a <= distanceThreshold) {
count++;
}
}
if (count <= mind) {
mind = count;
min = i;
}
}
return min;
}
1335. 工作计划的最低难度
你需要制定一份 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
分析:
本题是典型的动态规划问题。
dp[i][j]:表示前 i 天完成前 j 项任务所需的最小难度
有多种选择,可以是前 i-1 天完成 k-1 项任务(因为每天至少完成一项所以,k-1 >= i-1),然后第 i 天完成第 k, k+1, ..., i 项任务, 所需的难度:dp[i-1][k-1] + max{ jobDifficulty[k], jobDifficulty[k+1], ..., jobDifficulty[i] }
为了完成的难度最低,我们从中选最小的即可,即:
dp[i][j] = min{ dp[i-1][k-1] + max{ jobDifficulty[k], jobDifficulty[k+1], ..., jobDifficulty[i] } }, i <= k <= jobDiffculty.size()。
public int minDifficulty(int[] jobDifficulty, int d) {
if (d > jobDifficulty.length) {
return -1;
}
int LENGTH = jobDifficulty.length;
int[][] dp = new int[d][LENGTH];
for (int i = 0; i < d; i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE / 2);
}
int preDif = 0; //工作难度
for(int i = 0; i < LENGTH; i++){
preDif = Math.max(preDif, jobDifficulty[i]);
dp[0][i] = preDif;
}
for(int i = 1; i < d; i++){ //前i天
for(int j = i; j < LENGTH; j++){ //前j项任务
preDif = jobDifficulty[j];
for(int k = j; k >= i; k--){ //前k项任务
preDif = Math.max(preDif, jobDifficulty[k]);
dp[i][j]= Math.min(dp[i][j], dp[i-1][k-1] + preDif);
}
}
}
return dp[d-1][LENGTH-1];
}