LeetCode --- 132双周赛

题目列表

3174. 清除数字

3175. 找到连续赢 K 场比赛的第一位玩家

3176. 求出最长好子序列 I

3177. 求出最长好子序列 II

一、清理数字

这题直接根据题目,进行模拟即可,大体的思路是遍历字符串,遇到字母就加入答案,遇到数字就去掉答案中的最后一个字母,最后返回答案(类似进栈出栈),代码如下

cpp 复制代码
class Solution {
public:
    string clearDigits(string s) {
        string ans;
        for(auto e:s){
            if(isdigit(e))  ans.pop_back();
            else ans += e;
        }
        return ans;
    }
};

二、找到连续赢k场的比赛的第一个玩家

这题的关键在于赢了的玩家会留下来和其他的玩家进行比赛,这就意味了在他之前参加比赛的人的skill都要小于他

  • 如果n个人比完了,其中没有人赢下k场比赛,那么第一个赢下k场比赛的玩家必然是skill最大的那个
  • 我们还要考虑在skill最大的玩家还没出现之前,就已经有玩家赢得k场比赛的情况

具体代码如下

cpp 复制代码
class Solution {
public:
    int findWinningPlayer(vector<int>& skills, int k) {
        int n = skills.size();
        int pos = 0, cnt = 0;
        for(int i = 1; i < n; i++){
            if(skills[pos] < skills[i])
                pos = i, cnt = 0;
            cnt++;
            if(cnt == k) return pos;
        }
        return pos;
    }
};

三、求出最长好子序列 I & II

题目要求好子序列的最长长度,是一个子序列相关的动态规划问题。

状态定义:

子序列dp问题一般有两种类型,相邻相关 和 相邻无关 (看子序列的相邻元素之间是否存在某种关系/限制),分别对应两种状态的定义套路:相邻相关:以i为结尾的子序列的______,相邻无关:前i个元素中______。

本题显然是相邻相关的子序列问题,状态定义为 dp[i][j] 表示以i为结尾的子序列中最多有j个满足相邻元素不相等的最长子序列长度

状态转移方程:

  • 当nums[i] == nums[k]时,dp[i][j] = max(dp[i][j], dp[k][j] + 1)
  • 当nums[i] != nums[k]时,dp[i][j] = max(dp[i][j], dp[k][j-1] + 1)
  • 其中 k < i

初始化:考虑 j = 0 的情况,即最多有0个相邻元素相等的情况(等价于子序列中的元素全部相同),边遍历数组便统计数组出现次数即可。

代码如下

cpp 复制代码
class Solution {
public:
    int maximumLength(vector<int>& nums, int k) {
        int n = nums.size();
        unordered_map<int,int> mp; // 记录相同元素的个数
        int ans = 0;
        vector<vector<int>> dp(n, vector<int>(k + 1));
        // 初始化
        for(int i = 0; i < n;i ++){
            dp[i][0] = ++mp[nums[i]];
            ans = max(ans, dp[i][0]);
        }
        for(int j = 1; j <= k; j++){
            dp[0][j] = dp[0][0];
        }
        for(int i = 1; i < n; i++){ // 枚举以哪个数字为结尾
            for(int j = 1; j <= k; j++){ // 枚举最多有j个相邻不相同的情况
                for(int p = 0; p < i; p ++){ // 从之前的状态进行转移
                    if(nums[i] == nums[p]) dp[i][j] = max(dp[i][j], dp[p][j] + 1);
                    else dp[i][j] = max(dp[i][j], dp[p][j-1] + 1);
                }
            }
            ans = max(ans, dp[i][k]); // 注意答案是所有以i为结尾的子序列最大长度的最大值
        }
        return ans;
    }
};

时间复杂度为O(kn^2),显然是过不了的第四问的,如何优化时间复杂度???我们需要将第三层for循环求max的时间缩短为O(1),如何做?

这里有一个技巧,我们可以将下标换成值,在去思考如何优化,即将状态定义改为 dp[x][j] 表示以x=nums[i]为结尾的最多有j个相邻不相同元素的子序列最大长度

转移方程:

  • 当 x == nums[k] 时,dp[x][j] = max(dp[x][j], dp[x][j] + 1) = dp[x][j]+1
  • 当 x != y 时,dp[x][j] = max(dp[x][j], dp[y][j-1] + 1)
  • 其中 k < i

故 dp[x][j] = max(dp[x][j],dp[y][j-1]) + 1,其中y是不等于x的出现过的数,所以我们只要维护好dp[y][j-1]的最大值就能在O(1)的时间复杂度内求出答案,即我们只要维护好前一列的最大值即可,即维护一个数组mx[j] = max(dp[y][j-1]),这里我们不需要额外关心 y == x的情况,因为dp[x][j] >= dp[x][j-1],所以不会对答案产生影响

代码如下

cpp 复制代码
class Solution {
public:
    int maximumLength(vector<int>& nums, int k) {
        unordered_map<int,vector<int>> dp;
        vector<int> mx(k+2);
        for(int x:nums){
            if(!dp.contains(x)) dp[x].resize(k+1);
            auto& f = dp[x];
            for(int j = k; j >= 0; j--){ // 这里得是从后往前遍历,正着遍历会覆盖掉之前的mx[j]
                f[j] = max(f[j], mx[j]) + 1;
                mx[j+1] = max(mx[j+1], f[j]);
            }
        }
        return mx[k+1];
    }
};

总结:上面两种状态定义的大致思路是一样的,只是从下标改为了数值,转移方程也很相似,但是在维护max时,因为状态的转移和数值有关,我们需要在下标和数值之间建立联系,但问题是这种联系不是一一对应的,导致我们很难通过数值关系找到合适的下标来进行操作,但是我们只要将状态的定义和数值直接挂钩,我们就能很轻松的发现维护max的方法。

这里大家可以记住这样的一个技巧:当我们需要对dp进行优化时,且状态的转移和数值有关,我们可以优先考虑是否能将状态参数改为数值

相关推荐
c1assy10 分钟前
DP动态规划+贪心题目汇总
数据结构·算法·leetcode·贪心算法·动态规划
jjjxxxhhh12313 分钟前
C++ 模板是为了解决啥问题
开发语言·c++·算法
c++初学者ABC18 分钟前
GESP2级2403 小杨的日字矩阵
c++·算法
大写-凌祁37 分钟前
2024国赛A题第一问
线性代数·算法·机器学习·数学建模
代码小将43 分钟前
PTA数据结构编程题7-1最大子列和问题
数据结构·c++·笔记·学习·算法
pk_xz1234561 小时前
R 和 Origin 完成细菌 OTU 表、土壤理化性质数据的微生物 Beta 多样性分析
算法·机器学习·r语言
Ning_.1 小时前
力扣第116题:填充每个节点的下一个右侧节点指针 - C语言解法
c语言·算法·leetcode
小小unicorn1 小时前
第二章:算法练习题2
算法
抓住鼹鼠不撒手1 小时前
力扣 429 场周赛-前两题
数据结构·算法·leetcode
神经网络的应用2 小时前
C++程序设计例题——第三章程序控制结构
c++·学习·算法