题目列表
一、子字符串匹配模式
本题需要在 s 中模糊匹配找到 p 这样的子字符串,具体做法为现在 s 中找到 p 中 * 之前的字符串,在 s 匹配好的位置后面接着找匹配 p 中 * 之后的字符串,如果都能找到则能匹配,否则不能匹配,代码如下
cpp
class Solution {
public:
bool hasMatch(string s, string p) {
int pos = p.find('*');
int i = s.find(p.substr(0, pos));
return i != string::npos && s.substr(i + pos).find(p.substr(pos + 1)) != string::npos;
}
};
(上面代码使用的库函数find,是暴力找子字符串,可以通过使用KMP算法,进行优化)
二、设置任务管理器
简单来说就是实现任务管理器增删查改的功能。在查找的时候,需要返回优先级最大的那个任务,如果优先级相同,则返回 taskId 最大的那个任务。
我们可以用 priority_queue 来实现,但是在查找之前我们可能会对任务的相关数据进行修改,而 priority_queue 不允许我们对内部的数据进行修改,这里有一个技巧 --- 懒更新,即我们可以将修改的数据当作新的数据插入到 priority_queue 中,同时额外维护一张哈希表,用来记录最新的任务信息。当我们弹出数据时,我们去看弹出的数据是否和哈希表中的数据一致,如果不一致,说明数据被修改过,继续弹出数据,直到与哈希表中的数据一致,代码如下
cpp
class TaskManager {
priority_queue<tuple<int,int,int>> pq; // [priority, taskId, userId]
unordered_map<int, pair<int,int>> mp; // taskId -> [priority, userId]
public:
TaskManager(vector<vector<int>>& tasks){
for(auto t : tasks){
add(t[0], t[1], t[2]);
}
}
void add(int userId, int taskId, int priority) {
pq.emplace(priority, taskId, userId);
mp[taskId] = {priority, userId};
}
void edit(int taskId, int newPriority) {
add(mp[taskId].second, taskId, newPriority);
}
void rmv(int taskId) {
mp[taskId].first = -1;
}
int execTop() {
while(pq.size()){
auto [priority, taskId, userId] = pq.top();
pq.pop();
if(mp[taskId] == pair(priority, userId)){
rmv(taskId);
return userId;
}
}
return -1;
}
};
当然我们也可以直接用 set 和 unordered_map 直接模拟增删改查的功能,代码如下
cpp
class TaskManager {
set<tuple<int,int,int>> st;
unordered_map<int, set<tuple<int,int,int>>::iterator> mp;
public:
TaskManager(vector<vector<int>>& tasks){
for(auto t : tasks){
add(t[0], t[1], t[2]);
}
}
void add(int userId, int taskId, int priority) {
auto it = st.emplace(priority, taskId, userId);
mp[taskId] = it.first;
}
void edit(int taskId, int newPriority) {
auto [_, _, userId] = *mp[taskId];
rmv(taskId);
add(userId, taskId, newPriority);
}
void rmv(int taskId) {
st.erase(mp[taskId]);
mp.erase(taskId);
}
int execTop() {
if(st.empty()) return -1;
auto it = st.rbegin();
auto [priority, taskId, userId] = *it;
rmv(taskId);
return userId;
}
};
三、最长相邻绝对差递减子数组
求最长子序列,我们一般有两种状态定义的方式:
- 1、以 i 为结尾的最长子序列的长度
- 2、前 i 个元素中选出的最长子序列的长度。
这题又和相邻元素的差值有关,所以我们需要知道选择的元素下标,所以选择第一种状态定义方式,在结合差值,我们给出如下定义:
状态定义: 表示 以 为结尾的倒数最后两个数的绝对差 的最长递减子序列的长度
状态转移:设 , 表示 所在下标
当只有 一个元素时,
当倒数最后两个数的绝对差 时,
当倒数最后两个数的绝对差 时,
代码如下
cpp
class Solution {
public:
int longestSubsequence(vector<int>& nums) {
int n = nums.size();
int mx = ranges::max(nums);
vector<vector<int>> f(n + 1, vector<int>(mx + 1));
vector<int> idx(mx + 1, -1);
int ans = 0;
for(int i = 0; i < n; i++){
int x = nums[i];
for(int j = mx - 1; j >= 0; j--){
f[i][j] = max(1, f[i][j+1]);
if(x - j > 0 && idx[x - j] >= 0){
f[i][j] = max(f[i][j], f[idx[x - j]][j] + 1);
}
if(x + j <= mx && idx[x + j] >= 0){
f[i][j] = max(f[i][j], f[idx[x + j]][j] + 1);
}
ans = max(ans, f[i][j]);
}
idx[nums[i]] = i;
}
return ans;
}
};
// 值域优化空间
// 定义 f[x][j] 表示 以 x 为最后一个元素,且倒数两个数的绝对差 >= j 的最长子序列长度
// f[x][j] = max(1, f[x][j+1], f[x-j][j] + 1, f[x+j][j] + 1)
class Solution {
public:
int longestSubsequence(vector<int>& nums) {
int n = nums.size();
int mx = ranges::max(nums);
vector<vector<int>> f(mx + 1, vector<int>(mx + 1));
int ans = 0;
for(auto x: nums){
for(int j = mx - 1; j >= 0; j--){
int fx = max(1, f[x][j+1]); // 当 j = 0 时,f[x][j] 会被重复+1,所以用 fx 代替
if(x - j > 0){
fx = max(fx, f[x - j][j] + 1);
}
if(x + j <= mx){
fx = max(fx, f[x + j][j] + 1);
}
f[x][j] = fx;
ans = max(ans, f[x][j]);
}
}
return ans;
}
};
四、删除所有值为某个元素后最大子数组和
本题是最大子数组和的变形,可以将所有值为 x 的数删除(等价于将 x 变为 0),也就是要我们在计算最大子数组和的同时,也可以修改元素值。可以用线段树来维护,具体思路可以看看53. 最大子数组和 的官方题解的第二种做法,代码如下
cpp
struct Node{
long long ans; // 表示区间[l, r]中的最大子数组
long long sum; // 表示区间[l, r]中的元素和
long long pre; // 表示区间[l, r]中的最大前缀和
long long suf; // 表示区间[l, r]中的最大后缀和
};
struct SegmentTree{
vector<Node> tree;
SegmentTree(vector<int>& nums){
int n = nums.size();
tree.resize(2 << (bit_width((unsigned)n) + 1));
build(nums, 1, 0, n - 1);
}
void maintain(int o){
const auto & a = tree[o << 1];
const auto & b = tree[o << 1 | 1];
tree[o] = {
max({a.suf + b.pre, a.ans, b.ans}), // 最大子数组大小 = max{左区间的最大子数组大小,右区间的最大子数组大小,左右区间结合的最大值}
a.sum + b.sum,
max(a.pre, a.sum + b.pre), // [l, r]的最大前缀和 = max{左区间的最大前缀和,左区间 + 右区间的最大前缀和}
max(b.suf, b.sum + a.suf) // [l, r]的最大后缀和 = max{右区间的最大后缀和,右区间 + 左区间的最大后缀和}
};
}
void build(vector<int>& nums, int o, int l, int r){
if(l == r){
int val = nums[l];
tree[o] = {val, val, val, val};
return;
}
int m = (l + r) / 2;
build(nums, o << 1, l, m);
build(nums, o << 1 | 1, m + 1, r);
maintain(o);
}
void update(int o, int l, int r, int i, int val){
if(l == r){
tree[o] = {val, val, val, val};
return;
}
int m = (l + r) / 2;
if(m >= i){
update(o << 1, l, m, i, val);
}else{
update(o << 1 | 1, m + 1, r, i, val);
}
maintain(o);
}
};
class Solution {
public:
long long maxSubarraySum(vector<int>& nums) {
int n = nums.size();
SegmentTree t(nums);
long long ans = t.tree[1].ans;
if(ans <= 0){
return ans;
}
unordered_map<int, vector<int>> pos; // 记录相同数字下标
for(int i = 0; i < n; i++){
if(nums[i] < 0){
pos[nums[i]].push_back(i);
}
}
for(auto& [_, idx] : pos){
for(int i : idx){ // 将值相同的数字置为 0
t.update(1, 0, n - 1, i, 0);
}
ans = max(ans, t.tree[1].ans);
for(int i : idx){ // 恢复现场
t.update(1, 0, n - 1, i, nums[i]);
}
}
return ans;
}
};