欢迎大家加QQ群:623375442,可以方便群里面交流。最后一题只有6分是完全没想到的。这题真的好难。
100509. Substring Matching Pattern
给你一个字符串 s 和一个模式字符串 p ,其中 p 恰好 包含 一个 '*' 符号。
p 中的 '*' 符号可以被替换为零个或多个字符组成的任意字符序列。
如果 p 可以变成 s 的子字符串,那么返回 true ,否则返回 false 。
子字符串 指的是字符串中一段连续 非空 的字符序列。
测试样例:
输入:s = "leetcode", p = "ee*e"
输出:true
解释:将 '*' 替换为 "tcod" ,子字符串 "eetcode" 匹配模式串。
解答:这题类似正则匹配那道题目。注意p的后缀都是'*'的情况。
class Solution {
public boolean hasMatch(String s, String p) {
boolean[][] isVisit = new boolean[s.length() + 1][p.length() + 1];
for (int i = 0; i < s.length(); ++i) {
if (helper(s, p, i, 0, isVisit)) {
return true;
}
}
return false;
}
private boolean helper(String s, String p, int sp, int pp, boolean[][] isVisit) {
if (pp == p.length()) {
return true;
} else if (sp == s.length()) {
if (isVisit[sp][pp]) return false;
isVisit[sp][pp] = true;
if (p.charAt(pp) == '*') {
return helper(s, p, sp, pp + 1, isVisit);
}
return false;
} else if (!isVisit[sp][pp]) {
isVisit[sp][pp] = true;
if (s.charAt(sp) == p.charAt(pp)) {
return helper(s, p, sp + 1, pp + 1, isVisit);
} else if (p.charAt(pp) == '*') {
return helper(s, p, sp + 1, pp + 1, isVisit) || helper(s, p, sp + 1, pp, isVisit) || helper(s, p, sp, pp + 1, isVisit);
}
}
return false;
}
}
100503. Design Task Manager
一个任务管理器系统可以让用户管理他们的任务,每个任务有一个优先级。这个系统需要高效地处理添加、修改、执行和删除任务的操作。
请你设计一个 TaskManager 类:
- TaskManager(vector<vector
>& tasks) 初始化任务管理器,初始化的数组格式为 [userId, taskId, priority] ,表示给 userId 添加一个优先级为 priority 的任务 taskId 。 - void add(int userId, int taskId, int priority) 表示给用户 userId 添加一个优先级为 priority 的任务 taskId ,输入 保证 taskId 不在系统中。
- void edit(int taskId, int newPriority) 更新已经存在的任务 taskId 的优先级为 newPriority 。输入 保证 taskId 存在于系统中。
- void rmv(int taskId) 从系统中删除任务 taskId 。输入 保证 taskId 存在于系统中。
- int execTop() 执行所有用户的任务中优先级 最高 的任务,如果有多个任务优先级相同且都为 最高 ,执行 taskId 最大的一个任务。执行完任务后,taskId 从系统中 删除 。同时请你返回这个任务所属的用户 userId 。如果不存在任何任务,返回 -1 。
注意 ,一个用户可能被安排多个任务。
测试样例:
输入:["TaskManager", "add", "edit", "execTop", "rmv", "add", "execTop"]
[[[[1, 101, 10], [2, 102, 20], [3, 103, 15]]], [4, 104, 5], [102, 8], [], [101], [5, 105, 15], []]输出:[null, null, null, 3, null, null, 5]
解释:
- TaskManager taskManager = new TaskManager([[1, 101, 10], [2, 102, 20], [3, 103, 15]]); // 分别给用户 1 ,2 和 3 初始化一个任务。
- taskManager.add(4, 104, 5); // 给用户 4 添加优先级为 5 的任务 104 。
- taskManager.edit(102, 8); // 更新任务 102 的优先级为 8 。
- taskManager.execTop(); // 返回 3 。执行用户 3 的任务 103 。
- taskManager.rmv(101); // 将系统中的任务 101 删除。
- taskManager.add(5, 105, 15); // 给用户 5 添加优先级为 15 的任务 105 。
- taskManager.execTop(); // 返回 5 。执行用户 5 的任务 105 。
解答:这题难度不大,主要多复用函数可以减少犯错的概率。
class TaskManager {
private HashMap<Integer, int[]> taskInfo;
private TreeSet<int[]> sort;
public TaskManager(List<List<Integer>> tasks) {
taskInfo = new HashMap<>();
sort = new TreeSet<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
for (int i = 0; i < 2; ++i) {
if (o1[i] != o2[i]) return Integer.compare(o2[i], o1[i]);
}
return 0;
}
});
for (List<Integer> task : tasks) {
add(task.get(0), task.get(1), task.get(2));
}
}
public void add(int userId, int taskId, int priority) {
taskInfo.put(taskId, new int[]{userId, taskId, priority});
sort.add(new int[]{priority, taskId});
}
public void edit(int taskId, int newPriority) {
int[] info = taskInfo.get(taskId);
rmv(taskId);
add(info[0], taskId, newPriority);
}
public void rmv(int taskId) {
int[] info = taskInfo.get(taskId);
taskInfo.remove(taskId);
sort.remove(new int[]{info[2], info[1]});
}
public int execTop() {
if (sort.isEmpty()) return -1;
int[] first = sort.pollFirst();
int[] info = taskInfo.get(first[1]);
System.out.println(Arrays.toString(info));
rmv(first[1]);
return info[0];
}
}
100536. Longest Subsequence With Decreasing Adjacent Difference
给你一个整数数组 nums 。
你的任务是找到 nums 中的 最长子序列 seq ,这个子序列中相邻元素的 绝对差 构成一个 非递增 整数序列。换句话说,nums 中的序列 seq0, seq1, seq2, ..., seqm 满足 |seq1 - seq0| >= |seq2 - seq1| >= ... >= |seqm - seqm - 1| 。
请你返回这个子序列的长度。
一个 子序列 指的是从一个数组中删除零个或多个元素后,剩下元素不改变顺序得到的 非空 数组。
测试样例:
输入:nums = [16,6,3]
输出:3
解释:最长子序列是 [16, 6, 3] ,相邻绝对差值为 [10, 3] 。
解答:典型动态规划题目。1 <= nums[i] <= 300,这个范围可以知道最大diff小于299。可以遍历每个位置,所有diff情况。
class Solution {
public int longestSubsequence(int[] nums) {
int[] lastAppear = new int[301];
Arrays.fill(lastAppear, -1);
int[][] mem = new int[nums.length][300];
int res = 0;
for (int i = 0; i < nums.length; ++i) {
for (int j = 1; j <= 300; ++j) {
// 遍历diff情况
if (lastAppear[j] != -1) {
int diff = Math.abs(nums[i] - j);
int max = mem[lastAppear[j]][diff];
// 后缀最大会被使用到,注意
mem[i][diff] = Math.max(mem[i][diff], max == 0 ? 2 : (max + 1));
res = Math.max(res, mem[i][diff]);
}
}
// 记录后缀最大
for (int j = 298; j >= 0; --j) {
mem[i][j] = Math.max(mem[i][j], mem[i][j + 1]);
}
lastAppear[nums[i]] = i;
}
return res;
}
}
100513. Maximize Subarray Sum After Removing All Occurrences of One Element
给你一个整数数组 nums 。
你可以对数组执行以下操作 至多 一次:
- 选择 nums 中存在的 任意 整数 X ,确保删除所有值为 X 的元素后剩下数组 非空 。
- 将数组中 所有 值为 X 的元素都删除。
请你返回 所有 可能得到的数组中 最大 子数组和为多少。
子数组 指的是一个数组中一段连续 非空 的元素序列。
测试样例:
输入:nums = [-3,2,-2,-1,3,-2,3]
输出:7
解释:我们执行至多一次操作后可以得到以下数组:
- 原数组是 nums = [-3, 2, -2, -1, 3, -2, 3] 。最大子数组和为 3 + (-2) + 3 = 4 。
- 删除所有 X = -3 后得到 nums = [2, -2, -1, 3, -2, 3] 。最大子数组和为 3 + (-2) + 3 = 4 。
- 删除所有 X = -2 后得到 nums = [-3, 2, -1, 3, 3] 。最大子数组和为 2 + (-1) + 3 + 3 = 7 。
- 删除所有 X = -1 后得到 nums = [-3, 2, -2, 3, -2, 3] 。最大子数组和为 3 + (-2) + 3 = 4 。
- 删除所有 X = 3 后得到 nums = [-3, 2, -2, -1, -2] 。最大子数组和为 2 。
输出为 max(4, 4, 7, 4, 2) = 7 。
解答:这题没想到啥好办法。直接分类讨论。如果一个负数出现次数超过330次,那么就假设删除这个数字,遍历一次数组,然后计算最大子序列。如果小于330次,那么计算一下,利用这个负数出现的位置,计算最大子序列。
class Solution {
public long maxSubarraySum(int[] nums) {
long[] prefixSum = new long[nums.length + 1];
HashMap<Integer, List<Integer>> map = new HashMap<>();
long res = nums[0];
for (int i = 0; i < nums.length; ++i) {
prefixSum[i + 1] = prefixSum[i] + nums[i];
if (nums[i] < 0) {
if (!map.containsKey(nums[i])) {
map.put(nums[i], new ArrayList<>());
}
map.get(nums[i]).add(i);
}
res = Math.max(res, nums[i]);
}
res = Math.max(res, prefixSum[nums.length]);
if (res <= 0) return res;
SegmentTree tree = new SegmentTree(prefixSum);
for (Map.Entry<Integer, List<Integer>> kv : map.entrySet()) {
if (kv.getValue().size() == nums.length) {
break;
// 次数超过330次,直接假设删除这个数字,得出最大子序列。
} else if (kv.getValue().size() >= 330) {
res = Math.max(res, scanWithSkip(nums, kv.getKey()));
} else {
long[] max = new long[kv.getValue().size()];
res = Math.max(res, tree.maxRange(0, kv.getValue().get(0)));
int size = kv.getValue().size();
for (int i = 1; i < size; ++i) {
max[i - 1] = tree.maxRange(kv.getValue().get(i - 1) + 2, kv.getValue().get(i));
}
max[kv.getValue().size() - 1] = tree.maxRange(kv.getValue().get(size - 1) + 1, nums.length);
int last = 0;
for (int i = 0; i < size; ++i) {
long min = tree.minRange(last, kv.getValue().get(i));
last = kv.getValue().get(i);
// 双循环,枚举两端情况下,最佳结果。
// 利用线段树快速计算min和max
for (int j = i; j < size; ++j) {
res = Math.max(res, max[j] - min - kv.getKey() * (j - i + 1L));
}
}
}
}
return res;
}
private long scanWithSkip(int[] nums, int skip) {
long sum = 0, res = Long.MIN_VALUE;
for (int n : nums) {
if (n == skip) continue;
sum += n;
res = Math.max(res, sum);
if (sum < 0) sum = 0;
}
return res;
}
}
class SegmentTree {
private long[] minTree, maxTree;
private int n;
public SegmentTree(long[] nums) {
if (nums.length > 0) {
n = nums.length;
minTree = new long[n * 2];
maxTree = new long[n * 2];
buildTree(nums);
}
}
private void buildTree(long[] nums) {
for (int i = n, j = 0; i < 2 * n; i++, j++) {
minTree[i] = nums[j];
maxTree[i] = nums[j];
}
for (int i = n - 1; i > 0; --i) {
minTree[i] = Math.min(minTree[i * 2], minTree[i * 2 + 1]);
maxTree[i] = Math.max(maxTree[i * 2], maxTree[i * 2 + 1]);
}
}
public long minRange(int l, int r) {
l += n;
r += n;
long sum = Long.MAX_VALUE / 2;
while (l <= r) {
if ((l % 2) == 1) {
sum = Math.min(sum, minTree[l]);
l++;
}
if ((r % 2) == 0) {
sum = Math.min(sum, minTree[r]);
r--;
}
l /= 2;
r /= 2;
}
return sum;
}
public long maxRange(int l, int r) {
l += n;
r += n;
long sum = Long.MIN_VALUE / 2;
while (l <= r) {
if ((l % 2) == 1) {
sum = Math.max(sum, maxTree[l]);
l++;
}
if ((r % 2) == 0) {
sum = Math.max(sum, maxTree[r]);
r--;
}
l /= 2;
r /= 2;
}
return sum;
}
}