Leetcode 第 389 场周赛题解

本文详细解析了LeetCode中四个关于字符串操作的问题,涉及子字符串反转查找、以特定字符开头结尾子串计数、特殊字符串转换最少删除字符数以及拾取特定1的最少行动次数,运用了暴力法、组合数学、贪心策略和前缀和等算法技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Leetcode389 国服130名 1 - 4题 题解

T1 字符串及其反转中是否存在同一子字符串

题目大意:
判断字符串 s 是否存在一个长度为 2 的子字符串,在其反转后的字符串中也出现。
【暴力】
由于1 <= s.length <= 100,直接暴力即可。

class Solution {
public:
    bool isSubstringPresent(string s) {
        string t = "";
        int n = s.size();
        for(int i=n-1; i>=0; i--) // 直接暴力构造反转后的字符串
            t += s[i];
        for(int i=0; i+1<n; i++) {
            string f = s.substr(i, 2);
            for(int j=0; j<n; j++)
            if(t.substr(j, 2) == f) {
                return true;
            }
        }
        return false;
    }
};

T2 统计以给定字符开头和结尾的子字符串总数

题目大意
给你一个字符串 s 和一个字符 c 。返回在字符串 s 中并且以 c 字符开头和结尾的非空子字符串的总数。
【组合数学】
假设字符c一共在n个位置中出现。那么,选一个开头,选一个结尾(可重合),相当于是在n个位置中 C n 2 + C n 1 C_{n}^{2} + C_{n}^{1} Cn2+Cn1

class Solution {
public:
    long long countSubstrings(string s, char c) {
        long long cnt = 0;
        int n = s.size();
        for(int i=0; i<n; i++)
        if(s[i] == c) cnt ++;
        return (cnt-1)*cnt/2 + cnt;
    }
};

T3 成为 K 特殊字符串需要删除的最少字符数

题目大意
新定义:如果 ∣ f r e q ( w o r d [ i ] ) − f r e q ( w o r d [ j ] ) ∣ < = k |freq(word[i]) - freq(word[j])| <= k freq(word[i])freq(word[j])<=k 对于字符串中所有下标 i 和 j 都成立,则认为 word 是 k 特殊字符串
求让一个字符串变为k 特殊字符串,需要删除的最少字符数。
【贪心】【前缀和】
维护一个数组,记录出现 i i i 次的字母有多少个,暴力枚举该数组长度为k的区间。利用前缀和来计算需要删除的字符数。

class Solution {
public:
    int minimumDeletions(string word, int k) {
        map<char, int> mp;
        int n = word.size();
        for(int i=0; i<n; i++) 
            mp[word[i]] ++; // 记录每个字母出现的次数
        vector<int> a(n+1);
        vector<long long> b(n+1);
        for(auto [x, y]: mp) {
            a[y] ++; // 记录出现y次的字母有多少个
            b[y] += y; // 记录出现y次的字母的权重和
        }
        // 因为要快速获取区间和,所以这里做一个前缀和操作
        for(int i=1; i<=n; i++) {
            a[i] += a[i-1];
            b[i] += b[i-1];
        }
            
        if(k >= n) return 0;
        int minn = 1e9;
        for(int i=1; i+k<=n; i++) {
            int t = b[i-1] + (b[n] - b[i+k]) - (a[n] - a[i+k]) * (i+k);
            minn = min(minn, t);
        }
        return minn;
    }
};

T4 拾起 K 个 1 需要的最少行动次数

题目大意
(以灵神为主角的题目)
一个游戏,目标是从一个二进制数组中拾取特定数量的1,最少行动次数。
游戏开始时,玩家可以选择任意位置站立。
每次行动可以将数组中的一个0变为1,或者交换相邻01的位置。
当玩家站立位置为1时,玩家可以捡起一个1,该位置重新置为零。
玩家有限制条件,即最多执行maxChanges次改变操作。最终目标是在某个固定位置上拾取k个1,返回实现此目标的最少行动次数。
【贪心】【前缀和】
(一开始写的二分,后来发现不需要)
这道题算是一个T3的类题,放在T3后面,有一定的提示作用。
首先,可以明确一点,除了玩家脚下和左右两侧的1以外,优先使用行动1一定是最优的(只需要两次操作),所以我们需要尽可能多地用行动1。
同时,假如maxChanges够我们使用,玩家的初始位置要尽可能地在多个连续的1处,分别有三种情况:1个连续的1, 2个连续的1,3个及以上连续的1。我以这三种情况作为分类讨论的依据。
然后,假如maxChanges不够用,那么我们去选择一个起始点,使得两边的1向起始点收敛所需的代价最小。这个最小值通过前缀和优化暴力枚举即可得到结果。

思路不难想,但需要处理的边界条件比较多。

class Solution {
public:
    long long minimumMoves(vector<int>& nums, int k, int maxChanges) {

        int n = nums.size();
        int cnt = 0, maxn = 0;
        // maxn为序列中最长连续1的长度
        for(int i=0; i<n; i++) {
            if(nums[i] == 1) cnt ++;
            else {
                maxn = max(maxn, cnt);
                cnt = 0;
            }
        }
        maxn = max(maxn, cnt);
        cnt = 0;
        //前缀和pre记录
        vector<long long> pre(n+5);
        for(int i=0; i<n; i++)
        if(nums[i] == 1) {
            cnt ++;
            pre[cnt] = pre[cnt-1] + i;
        }
        auto fun1 = [&](int t) { // t为奇数的情况
            long long res = 1e18;
            for(int i=t/2+1; i+t/2<=cnt; i++) {
                res = min(res, pre[i+t/2]-pre[i] - (pre[i-1]-pre[i-t/2-1]));
            }
            return res;
        };
        auto fun2 = [&](int t) { // t为偶数的情况
            long long res = 1e18;
            for(int i=t/2; i+t/2<=cnt; i++) {
                res = min(res, pre[i+t/2]-pre[i] - (pre[i]-pre[i-t/2]));
            }
            return res;
        };
        if(maxn == 0) {
            return k*2;
        } else if(maxn == 1) {
            if(k == 1) return 0ll;
            if(maxChanges+1 >= k) {
                return (k-1)*2;
            } else {
                int t = k - maxChanges;
                if(t % 2) return fun1(t) + maxChanges*2;
                else return fun2(t) + maxChanges*2;
            }
        } else if(maxn == 2) {
            if(k == 1) return 0ll;
            if(maxChanges+2 >= k) {
                return max((k-2)*2+1, 0);
            } else {
                int t = k - maxChanges;
                if(t % 2) return fun1(t) + maxChanges*2;
                else return fun2(t) + maxChanges*2;
            }
        } else {
            if(maxChanges+3 >= k) {
                if(k == 1) return 0ll;
                if(k == 2) return 1ll;
                return max((k-3)*2+2, 0);
            } else {
                int t = k - maxChanges;
                if(t % 2) return fun1(t) + maxChanges*2;
                else return fun2(t) + maxChanges*2;
            }
        }
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值