哇,大声哭了出来,最后一题也太难了把,KMP + DP,我KMP都木有学,扎心了。
第一题:排序 + 暴力模拟 或者 计数统计。
第二题:暴力模拟 或者 优化一下。
第三题:模拟 + map 的使用。
第四题:KMP + DP。
详细题解如下。
1.找出数组中的幸运数(Find Lucky Integer In An Array)
AC代码(方法一、排序 + 暴力枚举 C++)
AC代码(方法二、计数统计 C++)
2. 统计作战单位数(Count Number of Teams)
AC代码(方法一、暴力枚举 C++)
AC代码(方法二、暴力优化 C++)
3.设计地铁系统(Design Underground System)
AC代码(C++)
4.找到所有好字符串(Find All Good Strings)
AC代码(C++)
LeetCode第182场周赛地址:
https://leetcode-cn.com/contest/weekly-contest-182/
https://leetcode-cn.com/problems/find-lucky-integer-in-an-array/
在整数数组中,如果一个整数的出现频次和它的数值大小相等,我们就称这个整数为「幸运数」。
给你一个整数数组 arr,请你从中找出并返回一个幸运数。
如果数组中存在多个幸运数,只需返回 最大 的那个。
如果数组中不含幸运数,则返回 -1 。示例 1:
输入:arr = [2,2,3,4] 输出:2 解释:数组中唯一的幸运数是 2 ,因为数值 2 的出现频次也是 2 。
提示:
1 <= arr.length <= 500
1 <= arr[i] <= 500
方法一、排序 + 暴力枚举
根据题目,我们可以使用先进行排序,然后对每一个数(从大到小),进行计算其出现次数,如果出现次数 == 数值,那么就是答案。
时间复杂度是 O(N * logN) + O(N * N),空间复杂度是 O(1)。根据数据范围,不会超时。
方法二、计数统计
根据数据的范围,我们可以用一个 501 的数组存储某个数出现的次数,即 cnt[ i ] 表示 i 这个数,出现了 cnt[ i ]次。
那么 i 从大到小判断,判断 i == cnt[ i ] ?是的话,那么就是答案。
时间复杂度是 O(max(N, M)),其中 M 是 arr[i] 的数值范围。
空间复杂度是 O(M)。
class Solution {
public:
int findLucky(vector& arr) {
sort(arr.begin(), arr.end());
reverse(arr.begin(), arr.end());
int n = arr.size();
for(int i = 0;i < n; ++i)
{
int cnt = 1;
for(int j = 0;j < n; ++j)
{
if(j == i) continue;
if(arr[j] == arr[i]) ++cnt;
}
if(cnt == arr[i]) return arr[i];
}
return -1;
}
};
const int MAXM = 510;
class Solution {
public:
int cnt[MAXM];
int findLucky(vector& arr) {
memset(cnt, 0, sizeof(cnt));
for(auto a : arr) ++cnt[a];
for(int i = 500; i >= 1; --i)
{
if(i == cnt[i]) return i;
}
return -1;
}
};
https://leetcode-cn.com/problems/count-number-of-teams/
n 名士兵站成一排。每个士兵都有一个 独一无二 的评分 rating 。
每 3 个士兵可以组成一个作战单位,分组规则如下:
从队伍中选出下标分别为 i、j、k 的 3 名士兵,他们的评分分别为 rating[i]、rating[j]、rating[k]
作战单位需满足: rating[i] < rating[j] < rating[k] 或者 rating[i] > rating[j] > rating[k] ,其中 0 <= i < j < k < n
请你返回按上述条件可以组建的作战单位数量。每个士兵都可以是多个作战单位的一部分。示例 1:
输入:rating = [2,5,3,4,1] 输出:3 解释:我们可以组建三个作战单位 (2,3,4)、(5,4,1)、(5,3,1) 。
示例 2:
输入:rating = [2,1,3] 输出:0 解释:根据题目条件,我们无法组建作战单位。
提示:
n == rating.length
1 <= n <= 200
1 <= rating[i] <= 10^5
方法一、暴力枚举
一开始,看到题目,那么想着,枚举所有可能的 i j k,然后判断满不满足条件。那么时间复杂度是 O(N ^ 3),根据数据范围,还是木得问题的,不会超时,所以直接解决即可。
方法二、暴力的优化
假设以某个士兵 j 为中间的士兵,分别统计其 左/右 侧评分 小于/大于 rating[j]的士兵数,则以士兵 j 为中间的士兵可以组成的作战单位数量为 两者士兵数的乘积。
上面是 值 < 值 < 值得,还有 值 > 值 > 值,这个也是类似得
此时就是,我们 j 遍历,然后剩下得就是,遍历 j 得左边 和 右边,所以时间复杂度是 O(N ^ 2),进行了优化。
class Solution {
public:
int numTeams(vector& rating) {
int n = rating.size();
if(n <= 2) return 0;
int ans = 0;
for(int i = 0;i < n; ++i)
{
for(int j = i + 1;j < n; ++j)
{
for(int k = j + 1;k < n; ++k)
{
if(rating[i] < rating[j] && rating[j] < rating[k])
++ans;
if(rating[i] > rating[j] && rating[j] > rating[k])
++ans;
}
}
}
return ans;
}
};
class Solution {
public:
int numTeams(vector& rating) {
int n = rating.size();
if(n < 3) return 0;
int ans = 0;
// 先计算 rating[i] < rating[j] < rating[k]
for(int j = 1;j < n - 1; ++j)
{
int miCnt = 0, mxCnt = 0;
for(int i = j - 1;i >= 0; --i)
{
if(rating[i] < rating[j]) ++miCnt;
}
for(int k = j + 1;k < n; ++k)
{
if(rating[j] < rating[k]) ++mxCnt;
}
ans += miCnt * mxCnt;
}
// 同理,rating[i] > rating[j] > rating[k]
for(int j = 1;j < n - 1; ++j)
{
int miCnt = 0, mxCnt = 0;
for(int i = j - 1;i >= 0; --i)
{
if(rating[i] > rating[j]) ++mxCnt;
}
for(int k = j + 1;k < n; ++k)
{
if(rating[j] > rating[k]) ++miCnt;
}
ans += miCnt * mxCnt;
}
return ans;
}
};
https://leetcode-cn.com/problems/design-underground-system/
题目和示例过长,具体看题目链接
提示:
总共最多有 20000 次操作。
1 <= id, t <= 10^6
所有的字符串包含大写字母,小写字母和数字。
1 <= stationName.length <= 10
与标准答案误差在 10^-5 以内的结果都视为正确结果。
拿到题目,应该就是根据题目进行模拟即可。
那么考虑是怎么一个过程:首先是,ID 这个人,在某时间,进入 某个站。然后 同样这个人,会在某时间,离开某个地铁站。
那么给定一个两个地铁站,要求求出,平均时间(假如有多个 ID,或者一个ID走多次,都是这两个地铁站,那么就是,总得花时间 / 有几次)。
所以根据上面得东西,我们就是,一开始,要记录 ID 这个人,进站得,地点和时间,那么想快速查找,那么就用 unordered_map
然后到了某个人出站,我们能快速找到这个人,进站得时间和进站得地点。
但是,我们后面,要判断得,是两个地铁站得,那么,我们可以根据 某个人出站,我们能快速找到这个人,进站得时间和进站得地点,我们此时不关心具体是 ID,而具体关心的是: 两个地铁站、耗时、同一两个地铁站出现的次数。那么我们就将 两个地铁站名,连接起来,然后用 unordered_map<两地铁站, 耗时> 和 unordered_map<两地铁站, 次数>。
那么最后要输出的时候,就是根据输入的两个地铁站,去 map 中找,其 总耗时 / 此时。
(两个地铁站连接可以是:start + "#" + end)。
所以这道题,主要就是用 map + 模拟即可。
class UndergroundSystem {
public:
unordered_map enter;
unordered_map time;
unordered_map cost;
unordered_map cnt;
UndergroundSystem() {
enter.clear();
time.clear();
cost.clear();
cnt.clear();
}
void checkIn(int id, string stationName, int t) {
enter[id] = stationName;
time[id] = t;
}
void checkOut(int id, string stationName, int t) {
string all = enter[id] + "#" + stationName;
long long c = t - time[id];
cost[all] += c;
++cnt[all];
}
double getAverageTime(string startStation, string endStation) {
string all = startStation + "#" + endStation;
return 1.0 * cost[all] / cnt[all];
}
};
/**
* Your UndergroundSystem object will be instantiated and called as such:
* UndergroundSystem* obj = new UndergroundSystem();
* obj->checkIn(id,stationName,t);
* obj->checkOut(id,stationName,t);
* double param_3 = obj->getAverageTime(startStation,endStation);
*/
https://leetcode-cn.com/problems/find-all-good-strings/
给你两个长度为 n 的字符串 s1 和 s2 ,以及一个字符串 evil 。请你返回 好字符串 的数目。
好字符串 的定义为:它的长度为 n ,字典序大于等于 s1 ,字典序小于等于 s2 ,且不包含 evil 为子字符串。
由于答案可能很大,请你返回答案对 10^9 + 7 取余的结果。
示例 1:
输入:n = 2, s1 = "aa", s2 = "da", evil = "b" 输出:51 解释:总共有 25 个以 'a' 开头的好字符串:"aa","ac","ad",...,"az"。还有 25 个以 'c' 开头的好字符串:"ca","cc","cd",...,"cz"。最后,还有一个以 'd' 开头的好字符串:"da"。
示例 2:
输入:n = 8, s1 = "leetcode", s2 = "leetgoes", evil = "leet" 输出:0 解释:所有字典序大于等于 s1 且小于等于 s2 的字符串都以 evil 字符串 "leet" 开头。所以没有好字符串。
提示:
s1.length == n
s2.length == n
1 <= n <= 500
1 <= evil.length <= 50
所有字符串都只包含小写英文字母。
很难,这道题,才是真的“困难”。结合了 KMP + DP。
这道题的最大难点在于,我们要求计算有多少个字符串“不包含”某个子串。而这个子串本身是可以重复的,这就导致重复计数的问题。也就是说,假如我们的思路是计算所有 sa 到 sb 的字符串有多少个,再减去所有不符合条件的字符串,那么减的过程中就得考虑“包含好几个evil串”的这个情况。
比如说,不包含的子串为 "abaab",那么 "abaabaab" 这个字符串中包含两个 "abaab",被“减”了两次,根据容斥原理,就需要返还一次。那么怎么判断字符串中有没有/有几个 evil 串呢?这就是 KMP 算法的范畴了。
因此需要用到KMP。
但传统的字符串算法中,我们是对用一个匹配串去匹配一个原字符串,现在我们没有“一个”源字符串,而是有很多很多的字符串。假如一个一个枚举,这个复杂度肯定难以想象了。
因此要用到DP。
(这道题目前还没有弄懂,等后面弄懂了,再继续更新),具体的代码是中国榜单第 4 的 huangyuyang 大佬的代码。
class Solution {
public:
int nxt[1005];
void get(string s)
{
int i, j, len = s.size();
nxt[0]=-1;
for(i=0,j=-1;i s1[i]) {
newk = 1;
}
}
if (l == 0) {
if (c > s2[i]) {
continue;
} else if (c < s2[i]) {
newl = 1;
}
}
int pos = get(e, j, c);
if (pos == e.size()) {
continue;
}
//printf("%c %d %d %d %d\n", c, i + 1, pos, newk, newl);
(f[i + 1][pos][newk][newl] += f[i][j][k][l]) %= MO;
}
}
}
}
}
int ans = 0;
for (int j = 0; j < n && j <= e.size() - 1; j++) {
for (int k = 0; k < 2; k++) {
for (int l = 0; l < 2; l++) {
(ans += f[n][j][k][l]) %= MO;
}
}
}
return ans;
}
};