LeetCode 第32场夜喵双周赛 题解

文章目录

  • 第 k 个缺失的正整数
    • a.题目
    • a.分析
    • a.参考代码
  • K 次操作转变字符串
    • b.题目
    • b.分析
    • b.参考代码
  • 平衡括号字符串的最少插入次数
    • c.题目
    • c.分析
    • c.参考代码
  • 找出最长的超赞子字符串
    • d.题目
    • d.分析
    • d.参考代码

第 k 个缺失的正整数

a.题目

给你一个 严格升序排列 的正整数数组 arr 和一个整数 k
请你找到这个数组里第 k 个缺失的正整数。

示例 1

输入:arr = [2,3,4,7,11], k = 5
输出:9
解释:缺失的正整数包括 [1,5,6,8,9,10,12,13,…] 。第 5 个缺失的正整数为 9 。

示例 2

输入:arr = [1,2,3,4], k = 2
输出:6
解释:缺失的正整数包括 [5,6,7,…] 。第 2 个缺失的正整数为 6 。

提示

  • 1 <= arr.length <= 1000
  • 1 <= arr[i] <= 1000
  • 1 <= k <= 1000
  • 对于所有 1 <= i < j <= arr.length 的 i 和 j 满足 arr[i] < arr[j]

a.分析

第一题不浪费太多时间都是先暴力入手
从题目的规模中可以知道 全部数字不超过2000
因为数组长度最大1000 k最大1000 所以答案的数字肯定在2000内(假如前1000个数字均存在
那么只需要按照2000的规模数数 数下去就好了

具体就是:
从1开始数 没有的就记录在k那
至于怎么知道有没有 可以提前开个2000大小的flag表示 下标即那个数字
在给定数组中把其标记为没有

总的时间复杂度是O(n+k)

a.参考代码

class Solution {
public:
    int findKthPositive(vector<int>& arr, int k) {
        vector<bool> v(2333,true);
        for(auto i:arr)v[i]=false;
        for(int i=1,cnt=0;i<v.size();i++)
            if(v[i]){
                cnt++;
                if(cnt==k)return i;
            }
        return 0;
    }
};

K 次操作转变字符串

b.题目

给你两个字符串 s 和 t ,你的目标是在 k 次操作以内把字符串 s 转变成 t 。
在第 i 次操作时(1 <= i <= k),你可以选择进行如下操作:

  • 选择字符串 s 中满足 1 <= j <= s.length 且之前未被选过的任意下标 j (下标从 1 开始),并将此位置的字符切换 i 次。
  • 不进行任何操作。

切换 1 次字符的意思是用字母表中该字母的下一个字母替换它(字母表环状接起来,所以 ‘z’ 切换后会变成 ‘a’)。
请记住任意一个下标 j 最多只能被操作 1 次。
如果在不超过 k 次操作内可以把字符串 s 转变成 t ,那么请你返回 true ,否则请你返回 false 。

示例 1

输入:s = “input”, t = “ouput”, k = 9
输出:true
解释:第 6 次操作时,我们将 ‘i’ 切换 6 次得到 ‘o’ 。第 7 次操作时,我们将 ‘n’ 切换 7 次得到 ‘u’ 。

示例 2

输入:s = “abc”, t = “bcd”, k = 10
输出:false
解释:我们需要将每个字符切换 1 次才能得到 t 。我们可以在第 1 次操作时将 ‘a’ 切换成 ‘b’ ,但另外 2 个字母在剩余操作中无法再转变为 t 中对应字母。

示例 3

输入:s = “aab”, t = “bbb”, k = 27
输出:true
解释:第 1 次操作时,我们将第一个 ‘a’ 切换 1 次得到 ‘b’ 。在第 27 次操作时,我们将第二个字母 ‘a’ 切换 27 次得到 ‘b’ 。

提示

  • 1 <= s.length, t.length <= 10^5
  • 0 <= k <= 10^9
  • s 和 t 只包含小写英文字母。

b.分析

本来看到k的范围有点被吓到了 感觉是不是要用公式来推
考虑后发现 一个任意字符串变成另外一个字符串 最复杂的情况就是
aaa…aaa 变成zzz…zzz 也就是25*n次 n为字符串长度
所以嘛… 1e5的长度 k大于3e6就肯定是ok的
所以如果模拟变换的话 最多只会3e6的复杂度 是可以模拟的

  • 首先两个字符串长度不相等肯定是不ok的
  • k大过2.5e6肯定ok的

接下来是模拟变换:
对于给定的s[i]和t[i] 我们都能知道要换多少次
假设为x次 那么如果之前已经有换过x次的 那么需要多一个轮回 所以就会变成26+x
同理 如果之前已经换过两次x次的 那么要多两个轮回就是 26+26+x
总结就是要换的次数就是y*26+x
y是之前已经换了多少次 y初始值为0 每遇到一次就那个次数+1
那么我们在模拟换的时候看一下 这个y*26+x是否比k大 大过就肯定不行

总的时间复杂度是 O(Cn)的 C为一个轮回是多少

b.参考代码

class Solution {
public:
    bool canConvertString(string s, string t, int k) {
        if(s.size()!=t.size())return false;
        if(k>3000000)return true;
        vector<int> vis(27,0);	//记录出现了多少次 即上文的y
        for(int i=0;i<s.size();i++){
            int a=s[i]-'a',b=t[i]-'a';
            if(a==b)continue;
            int x=(b+26-a)%26;	//上文中的x 表示变换要多少次
            if(vis[x]*26+x>k)return false;
            vis[x]++;	//记得++
        }
        return true;
    }
};

平衡括号字符串的最少插入次数

c.题目

给你一个括号字符串 s ,它只包含字符 ‘(’ 和 ‘)’ 。一个括号字符串被称为平衡的当它满足:

  • 任何左括号 ‘(’ 必须对应两个连续的右括号 ‘))’ 。
  • 左括号 ‘(’ 必须在对应的连续两个右括号 ‘))’ 之前。

比方说 “())”, “())(())))” 和 “(())())))” 都是平衡的, “)()”, “()))” 和 “(()))” 都是不平衡的。
你可以在任意位置插入字符 ‘(’ 和 ‘)’ 使字符串平衡。
请你返回让 s 平衡的最少插入次数。

示例 1

输入:s = “(()))”
输出:1
解释:第二个左括号有与之匹配的两个右括号,但是第一个左括号只有一个右括号。我们需要在字符串结尾额外增加一个 ‘)’ 使字符串变成平衡字符串 “(())))” 。

示例 2

输入:s = “())”
输出:0
解释:字符串已经平衡了。

示例 3

输入:s = “))())(”
输出:3
解释:添加 ‘(’ 去匹配最开头的 ‘))’ ,然后添加 ‘))’ 去匹配最后一个 ‘(’ 。

示例 4

输入:s = “((((((”
输出:12
解释:添加 12 个 ‘)’ 得到平衡字符串。

示例 5

输入:s = “)))))))”
输出:5
解释:在字符串开头添加 4 个 ‘(’ 并在结尾添加 1 个 ‘)’ ,字符串变成平衡字符串 “(((())))))))” 。

提示

  • 1 <= s.length <= 10^5
  • s 只包含 ‘(’ 和 ‘)’ 。

c.分析

就是典型的括号匹配 按照经验 把的权值记录为+2 的权值记录为-1
那么一个平衡括号的权值和应该要为0
不过比()这种以前的情况多了几种非法状态需要特判的

  • ()) ))) 这种情况 也就是说在)单独出现的时候 这时候就要补(了 那么这种情况下 权值会立刻变成-1 所以在权值出现负的时候补一个(令权值变成+1即可
  • ()()) 这种情况 在(出现的时候 但是前面的只出现了一个) 题目的))必须是成对出现的 所以这里要补一个) 这种情况下权值是正的奇数
  • ((() 这种情况就很容易理解了 缺多少补多少 这种就是在最后的情况下补的 这种情况下的权值是多少 就要补多少 例如((()这个权值是2+2+2-1=5 所以要补5个括号

总的时间复杂度是O(n)的

c.参考代码

class Solution {
public:
    int minInsertions(string s) {
        int ans=0;	//要修改的次数
        int x=0;	//当前权值
        for(int i=0;i<s.size();i++){
            if(s[i]=='('){
                x+=2;
                if(x&1){	//第二种非法的立刻要补情况
                    ans++;
                    x--;
                }
            }
            else{
                x--;
                if(x<0){	//第一种非法的立刻要补情况
                    ans++;
                    x+=2;
                }
            }
        }
        return ans+x;	//最后补上的第三种非法情况
    }
};

找出最长的超赞子字符串

d.题目

给你一个字符串 s 。请返回 s 中最长的 超赞子字符串 的长度。
「超赞子字符串」需满足满足下述两个条件:
该字符串是 s 的一个非空子字符串
进行任意次数的字符交换重新排序后,该字符串可以变成一个回文字符串

示例 1

输入:s = “3242415”
输出:5
解释:“24241” 是最长的超赞子字符串,交换其中的字符后,可以得到回文 “24142”

示例 2

输入:s = “12345678”
输出:1

示例 3

输入:s = “213123”
输出:6
解释:“213123” 是最长的超赞子字符串,交换其中的字符后,可以得到回文 “231132”

示例 4

输入:s = “00”
输出:2

提示

  • 1 <= s.length <= 10^5
  • s 仅由数字组成

d.分析

这题比赛时候没做出来 本来以为是二分的 做完才发现 长的是回文 但是短的不一定是 不符合二分性质 (因为是连续的子串交换顺序
这题用的是前缀和思想+状态集合位压缩+hashmap

但是有个点可以立刻想出来就是 如何判定一个子串是否可以变换成回文串
因为是任意排序 所以只需要满足以下性质即可:

  • 出现的字符计数不超过一个字符计数为奇数

也就是说:

  • 当长度为偶数时候 需要全部字符计数为偶数 (当然也不可能出现只有一个字符计数为奇数
  • 当长度为奇数时候 需要仅有一个字符计数为奇数 其他都为偶数

然后接下来是最重点的思想:

  • 第一点是 某个区间的状态可以用位运算压缩
    • 因为全部只有0~9这10个数字 所以某个区间的0~9的奇偶可以用位运算0和1来表示 假设0表示偶数次 1表示奇数次
    • 比如213123 就是1和2和3都是出现两次 所以都是偶数次 所以就用00000000000来表示
    • 比如12345678 1~8出现都是奇数次1次 所以就用0111111110来表示
  • 第二点 当前仅当某个区间的状态为以下情况 它是可以变换成为回文串的
    • 为0000000000 即全部都为偶数次出现
    • 为1000000000 或 0100000000 或 0010000000 等等 共十种情况 某位上的状态为奇数 即仅有一个字符计数为计数
    • 仅有以上11种状态符合 出现的字符计数不超过一个字符计数为奇数
  • 第三点是 不可能记录全部的区间 记录全部的区间 空间复杂度为O(n^2) 需要用前缀和思想来优化
    • 假设0~i的区间的状态值为x 0~j的区间的状态值为y i<=j 那么i~j区间的状态值为x^y
    • 为什么呢 因为x和y相同的部分即0~i 异或后表示出现的次数加倍了 偶数加倍还是偶数 奇数加倍是偶数 而这个结果的偶数可以看作出现了0次(把前面0~i的重叠部分置0) 假如x和y异或后的状态种某个数字为奇数次 那么这个奇数次肯定在i~j区间产生的

那么基于以上三点 我们就可以用O(Cn)的时间复杂度来解题了

那么同学们的问题就会来了 虽然用前缀和优化了储存 但是不还是要枚举出来全部的区间才能知道哪个区间是回文串吗?

并不是 不是回文串的垃圾区间并不需要枚举 因为从回文串的状态 可以推出 当且仅当x^y之后的状态值为0或者某位为1才是回文串
所以我们可以枚举y 然后通过hashmap 记录第一次出现的某个状态值的位置(为什么是第一次出现呢 因为要尽可能的长) 把x给弄出来

举例 某个状态值y为0010110101 那么可以和它构成回文串的x为
0010110101 即它本身这个状态值 还有例如1010110101 这个只有第一位不一样的 异或之后为1000000000 等等共11个对应的x
所以我们只需要去对这11个状态的x看下对应的x是否存在这样的位置i
i 我们是枚举j的

具体看代码

d.参考代码

class Solution {
public:
    int longestAwesome(string s) {
        int n=s.size();
        vector<int> Set(n,0);	//表示前缀状态
        int nowSet=0;	//当前的状态
        for(int i=0;i<n;i++){
            int num=s[i]-'0';
            nowSet^=(1<<num);
            Set[i]=nowSet;
        }
        vector<int> ipos(1<<12,-1);	//某个状态值x对应的位置i 如果没有这样的状态 位置为-1
        int ans=0;
        for(int j=0;j<n;j++){	//枚举j
            int nowSet=Set[j];
            if(!nowSet)ans=max(ans,j+1);	//本身0~j就可以
            for(int k=0;k<10;k++)if(nowSet==(1<<k))ans=max(ans,j+1);
			//以下是枚举i的位置 是否存在
            int i=ipos[nowSet];
            if(i!=-1)ans=max(ans,j-i);
            for(int k=0;k<10;k++){
                i=ipos[nowSet^(1<<k)];
                if(i!=-1)ans=max(ans,j-i);
            }
            if(ipos[nowSet]==-1)ipos[nowSet]=j;	//记录某个状态第一次出现的位置
        }
        return ans;
    }
};

你可能感兴趣的:(leetcode,周赛,字符串,算法,数据结构,leetcode)