当前位置: 首页 > article >正文

LeetCode---428双周赛

题目列表

3386. 按下时间最长的按钮

3387. 两天自由外汇交易后的最大货币数

3388. 统计数组中的美丽分割

3389. 使字符频率相等的最少操作次数

一、按下时间最长的按钮

题意要求找到按钮按下时间(即与它前一次按下按钮的时间差)最大的下标,如果存在两个相同的最大按下时间,取下标小的那个,其中第一次按下按钮的时间记为它与0的时间差。直接模拟即可,代码如下

class Solution {
public:
    int buttonWithLongestTime(vector<vector<int>>& events) {
        int n = events.size();
        int diff = events[0][1], idx = events[0][0];
        for(int i = 1; i < n; i++){
            int x = events[i][1] - events[i-1][1];
            if(x > diff || x == diff && idx > events[i][0]){
                diff = x, idx = events[i][0];
            }
        }
        return idx;
    }
};

二、两天自由外汇交易后的最大货币数量

这题题目意思比较绕,简单来说就是给你两张汇率的表,分别表示第一天和第二天中不同货币间的汇率, 然后给你一张货币,通过货币间的不断兑换,使得我们所拥有的原始货币的数量最多,可以理解为一张货币能最多变成多少张货币(其中初始的货币和最后的货币一样)。

我们可以把问题抽象成一个图,每一种货币代表一个结点,结点之间的边是有向的,边权表示一种货币到另一种货币的汇率,求到终点的路径边权乘积最大为多少。由于两天的汇率不一样,所以我们需要建两张图分别对第一题和第二天的汇率进行处理,其中我们遍历第一张图,得到初始节点到达各个结点的汇率乘积,然后让这些结点遍历第二张图,找到到达终止结点的最大汇率乘积。代码如下

class Solution {
public:
    double maxAmount(string initialCurrency, vector<vector<string>>& pairs1, vector<double>& rates1, vector<vector<string>>& pairs2, vector<double>& rates2) {
        unordered_map<string, vector<pair<string, double>>> g;
        for(int i = 0; i < pairs1.size(); i++){
            auto & e = pairs1[i];
            g[e[0]].emplace_back(e[1], rates1[i]);
            g[e[1]].emplace_back(e[0], 1/rates1[i]); // 题目允许货币进行来回转换
        }
        unordered_map<string, double> mp;
        auto dfs = [&](this auto && dfs, string x, string fa, double rate)->void{
            mp[x] = rate; // 记录到达每一种货币的汇率
            for(auto [y, r]: g[x]){
                if(y != fa){ // 由于题目中保证不出现循环,所以我们只要不让结点走到父结点就能保证遍历完整个图
                    dfs(y, x, rate * r);
                }
            }
        };
        dfs(initialCurrency, "", 1);
        g.clear();
        for(int i = 0; i < pairs2.size(); i++){
            auto & e = pairs2[i];
            g[e[0]].emplace_back(e[1], rates2[i]);
            g[e[1]].emplace_back(e[0], 1/rates2[i]); // 题目允许货币进行来回转换
        }
        double ans = 1;
        auto dfs1 = [&](this auto && dfs, string x, string fa, double rate)->void{
            if(x == initialCurrency){
                ans = max(ans, rate);
                return;
            }
            for(auto [y, r]: g[x]){
                if(y != fa){ // 由于题目中保证不出现循环,所以我们只要不让结点走到父结点就能保证遍历完整个图
                    dfs(y, x, rate * r);
                }
            }
        };
        for(auto [x, r] : mp){
            // 从每一种货币出发,计算到达初始货币的最大汇率
            dfs1(x, "", r);
        }
        return ans;
    }
};

当然,我们也可以通过建立分层图,来实现一次dfs遍历,就计算出答案。假设有三种货币为A、B、C,其中A 为 初始货币,则我们可以对A1,B1、C1进行建图,表示第一天的汇率变化情况,用A2、B2、C2建图表示第二天的汇率变化,我们只要额外添加A1->A2,B1->B2,C1->C2这三条边权为1的边,就能通过求A1->A2的边权乘积最大的值,找到答案。这种建图的方式就跟一栋楼的上下层一样,故称为分层图,代码如下

class Solution {
public:
    double maxAmount(string initialCurrency, vector<vector<string>>& pairs1, vector<double>& rates1, vector<vector<string>>& pairs2, vector<double>& rates2) {
        unordered_map<string, vector<pair<string, double>>> g;
        // 建立第一天的汇率图
        for(int i = 0; i < pairs1.size(); i++){
            auto & e = pairs1[i];
            g[e[0]].emplace_back(e[1], rates1[i]);
            g[e[1]].emplace_back(e[0], 1/rates1[i]); // 题目允许货币进行来回转换
        }
        // 将两天的汇率图进行连接
        for(auto [x,_]:g){
            g[x].emplace_back(x + "2", 1);
        }
        // 建立第二天的汇率图
        for(int i = 0; i < pairs2.size(); i++){
            auto & e = pairs2[i];
            g[e[0] + "2"].emplace_back(e[1] + "2", rates2[i]);
            g[e[1] + "2"].emplace_back(e[0] + "2", 1/rates2[i]); // 题目允许货币进行来回转换
        }
        double ans = 1;
        string res = initialCurrency + "2";
        auto dfs = [&](this auto && dfs, string x, string fa, double rate)->void{
            if(x == res){
                ans = max(ans, rate);
                return;
            }
            for(auto [y, r]: g[x]){
                if(y != fa){ // 由于题目中保证不出现循环,所以我们只要不让结点走到父结点就能保证遍历完整个图
                    dfs(y, x, rate * r);
                }
            }
        };
        dfs(initialCurrency, "", 1);
        return ans;
    }
};

三、统计数组中的美丽分割

看到判断前缀,很容易想到扩展KMP算法(又叫Z函数算法)。对于给定的字符串,会得到一个z数组,z[i] 表示从 i 往后的字符串与原字符串的最长公共前缀长度。下面是z函数的详细计算过程

假设我们将数组分为 [0,i)[i,j)[j,n) 三部分

  • 如果 [0,i)是 [i,j)的前缀,则 i <= j - i (保证长度符合条件) 并且 z[i] >= i (保证满足前缀关系)
  • 如果 [i,j)是 [j,n) 的前缀,则 j - i <= n - j (保证长度符合条件) 并且 z[j] >= j - i (保证满足前缀关系)

代码如下

class Solution {
    vector<int> getz(const vector<int>& nums){
        int n = nums.size();
        vector<int> z(n); z[0] = n;
        int l = 0, r = 0;
        for(int i = 1; i < n; i++){
            if(i <= r){
                z[i] = min(r - i + 1, z[i - l]);
            }
            while(i + z[i] < n && nums[z[i]] == nums[i + z[i]])
                z[i]++;
            if(i + z[i] - 1 > r){
                l = i, r = i + z[i] - 1;
            }
        }
        return z;
    }
public:
    int beautifulSplits(vector<int>& nums) {
        int n = nums.size();
        auto z0 = getz(nums);
        int ans = 0;
        for(int i = 1; i < n - 1; i++){
            auto z = getz(vector<int>(nums.begin() + i, nums.end()));
            for(int j = i + 1; j < n; j++){
                if(z0[i] >= i && i <= j - i 
                || z[j - i] >= j - i && j - i <= n - j){
                    ans++;
                }
            }
        }
        return ans;
    }
};


// 也可以通过计算lcp来判断前缀关系
class Solution {
public:
    int beautifulSplits(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> lcp(n + 1, vector<int>(n + 1));
        // lcp[i][j] 表示[i,n)的子串和[j,n)的子串的最长公共前缀
        // lcp[i][j] = lcp[i+1][j+1] + nums[i] == nums[j] 
        for(int i = n - 2; i >= 0; i--){
            for(int j = n - 1; j > i; j--){
                if(nums[i] == nums[j]){
                    lcp[i][j] = lcp[i+1][j+1] + 1;
                }
            }
        }
        int ans = 0;
        for(int i = 1; i < n; i++){
            for(int j = i + 1; j < n; j++){
                if(i <= j - i && lcp[0][i] >= i 
                || j - i <= n - j && lcp[i][j] >= j - i){
                    ans++;
                }
            }
        }
        return ans;
    }
};

四、使字符频率相等的最少操作次数

该题是一个思维题,具体的解题思路如下

关键性质:对连续的字符进行 操作3 只会让首尾两个字符的出现频率有-1 和 +1 的效果,不如直接用 2 次增/删 操作操作3 只有当相邻元素需要一减一加时,才能让操作次数变少

枚举频率 target,找最小的操作次数,则对于一个字符,它的频率要么变为 0,要么变为 target(因为我们很难贪心地确定哪一个频率是操作次数最少的频率,且它不具有单调性,也不能进行二分,同时数据范围也在一定范围内,所以我们考虑枚举频率,算操作次数)

如果一个字符 ? 的出现频率为 x,有如下情况

1、只进行前两个操作,则需要 min(x, abs(target - x)) 次操作

2、需要进行 操作3 【根据性质,只考虑相邻的字符即可】

如果 ?+1 的出现频率为 y

1) target <= y 只能进行前两个操作,因为进行一次操作3,就必然需要一次操作1使得 ?+1 恢复原来的频率,得不偿失

2) target > y

    如果 x < target,让 x = 0,则操作次数为 max(x, target - y)

    如果 x >= target,让 x = target,则操作次数为 max(x - target, target - y)

此外我们还需要知道哪些数字需要进行操作3,而对于字符 ? 的操作,只和 ? + 1 字符的频率有关,且根据性质我们知道操作3 不能连续使用,类似打家劫舍的一种,可以用 dp 求解

设 f[i] 表示 后 i 个字符的频率均为 target 的最小操作次数

f[i] = f[i+1] + min(x, abs(target - x))  不进行操作3

if(y < target) 进行操作3

  •     if(x < target) f[i] = min(f[i], f[i+2] + max(x, target - y));
  •     if(x >= target) f[i] = min(f[i], f[i+2] + max(x - target, target - y));

其中 x = cnt[i], y = cnt[i+1]

代码如下

class Solution {
public:
    int makeStringGood(string s) {
        int n = s.size();
        vector<int> cnt(26);
        for(auto e : s) cnt[e-'a']++; 
        int mx = ranges::max(cnt), mn = ranges::min(cnt);
        int ans = INT_MAX;
        for(int target = mn; target <= mx; target++){
            vector<int> f(27);
            f[25] = min(cnt[25], abs(target - cnt[25]));
            for(int i = 24; i >= 0; i--){
                int x = cnt[i], y = cnt[i+1];
                f[i] = f[i+1] + min(x, abs(target - x));
                if(y < target){
                    if(x < target){
                        f[i] = min(f[i], f[i+2] + max(x, target - y));
                    }else{
                        f[i] = min(f[i], f[i+2] + max(x - target, target - y));
                    }
                }
            }
            ans = min(ans, f[0]);
        }
        return ans;
    }
};

http://www.kler.cn/a/448234.html

相关文章:

  • 将HTML转换为PDF:使用Spire.Doc的详细指南(一) 试用版
  • airflow docker 安装
  • 条款33 对auto形参使用decltype以std::forward它们
  • 编译原理复习---目标代码生成
  • react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
  • gitee给DeployKey添加push权限
  • 电子电器架构 ---证书认证需求及CANoe验证脚本
  • 青少年编程与数学 02-004 Go语言Web编程 15课题、表单处理
  • python安卓自动化pyaibote实践------学习通自动刷课
  • Golang Gin Redis+Mysql 同步查询更新删除操作(我的小GO笔记)
  • Mysql “this is incompatible with sql_mode=only_full_group_by” 问题解决
  • SpringBoot CRUD 简易模板后端
  • Kafka 磁道寻址过程详解
  • 智能座舱进阶-应用框架层-Handler分析
  • 阿里开源最强数字人工具 EchoMimicV2,本地部署(一)
  • windows的服务怎么删除
  • 【k8s集群应用】Kubernetes二进制部署实例(master02+负载均衡)+Dashboard
  • 开始探索XDP:开启Linux高性能网络编程的新篇章
  • HarmonyOS NEXT 技术实践-基于基础视觉服务的多目标识别
  • ubuntu20.04安装mysql5.7
  • java抽奖系统(八)
  • HarmonyOS:开启万物互联智能新时代
  • 【电商推荐】全文空间反事实学习:调整、分析属性和工业应用
  • 【PyCharm】
  • 【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
  • 日拱一卒(19)——leetcode学习记录:两个子序列的最大点积