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个元素中______。

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

状态转移方程:

  • 当numsi == numsk时,dpij = max(dpij, dpkj + 1)
  • 当numsi != numsk时,dpij = max(dpij, dpkj-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),如何做?

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

转移方程:

  • 当 x == numsk 时,dpxj = max(dpxj, dpxj + 1) = dpxj+1
  • 当 x != y 时,dpxj = max(dpxj, dpyj-1 + 1)
  • 其中 k < i

故 dpxj = max(dpxj,dpyj-1) + 1,其中y是不等于x的出现过的数,所以我们只要维护好dpyj-1的最大值就能在O(1)的时间复杂度内求出答案,即我们只要维护好前一列的最大值即可,即维护一个数组mxj = max(dpyj-1),这里我们不需要额外关心 y == x的情况,因为dpxj >= dpxj-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进行优化时,且状态的转移和数值有关,我们可以优先考虑是否能将状态参数改为数值

相关推荐
机器学习之心几秒前
均值、中值、圆周期:三种 InSAR 干涉相位滤波的实测对比与参数取舍
算法·均值算法
xy202610 分钟前
多轮 Tool Agent 训不动?先把环境 token 从 loss 里拿掉
算法
wzdark23 分钟前
多核架构下算法并行化的瓶颈与突破点4
算法·架构
阿明634 分钟前
Leetcode: 283. 移动零
算法·leetcode·职场和发展
葫三生1 小时前
三生原理与《涌现:从简单规则到复杂世界》在“简单规则生成复杂系统”核心思路上存在理论呼应?
人工智能·科技·算法·机器学习·开源
6Hzlia1 小时前
【Classic 150 刷题计划】 LeetCode 134. 加油站 | C++ 贪心算法与局部否决逻辑
c++·leetcode·贪心算法
6Hzlia1 小时前
【Classic 150 刷题计划】 LeetCode 274. H 指数 | C++ 二分答案法 (单调性降维打击)
c++·算法·leetcode
行者全栈架构师1 小时前
WorkBuddy 实战:50 份简历 30 分钟筛完,还能自动出评估报告
人工智能·算法·微信
ysu_03142 小时前
19-插入排序与希尔排序
数据结构·算法·排序算法
CoderYanger2 小时前
Java EE 进阶:2.3 JavaScript
java·开发语言·前端·javascript·css·职场和发展·java-ee