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;
}
}
}
};