LeetCode---147周赛

题目列表

3407. 子字符串匹配模式

3408. 设计任务管理器

3409. 最长相邻绝对差递减子序列

3410. 删除所有值为某个元素后的最大子数组和

一、子字符串匹配模式

本题需要在 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;
    }
};
相关推荐
Chicheng_MA1 小时前
思维转换:突破思维桎梏,创造更高效的工作与生活
职场和发展
594h21 小时前
蓝桥杯 第十五届 研究生组 B题 召唤数学精灵
c++·算法·蓝桥杯
高一学习c++会秃头吗1 小时前
leetcode_2816. 翻倍以链表形式表示的数字
算法·leetcode·链表
冠位观测者2 小时前
【Leetcode 热题 100】739. 每日温度
数据结构·算法·leetcode
相思半3 小时前
Web前端开发入门学习笔记之CSS 57-58--新手超级友好版- 盒子模型以及边框线应用篇
前端·css·笔记·学习·职场和发展
yanglee03 小时前
L4-Prompt-Delta
人工智能·算法·语言模型·prompt
廖显东-ShirDon 讲编程4 小时前
《零基础Go语言算法实战》【题目 2-1】使用一个函数比较两个整数
算法·程序员·go语言·web编程·go web
刘争Stanley4 小时前
训练一只AI:深度学习在自然语言处理中的应用
人工智能·深度学习·算法·链表·自然语言处理·贪心算法·排序算法