这周的周赛,出乎意料的容易,特别是前三题,几乎都是easy题目了(特别第三题,如果会 python,超简单,但是我不会,所以还是手动写了匹配)。手速场,手速场,然鹅我脑子转得慢,而且敲代码还慢,可怜。
第一题:字符串查找。
第二题:模拟。
第三题:字符串替换。
第四题:DP + 状压。
详细题解如下。
1.数组中的字符串匹配(String Matching in An Array)
AC代码(C++)
2. 查询带键的排列(Queries on A Permutation with Key)
AC代码(C++)
3.HTML 实体解析器(Html Entity Parser)
AC代码(C++)
4.给 N x 3 网格图涂色的方案数(Number of Ways to Paint N X 3 Grid)
AC代码(C++)
LeetCode第184场周赛地址:
https://leetcode-cn.com/contest/weekly-contest-184
https://leetcode-cn.com/problems/string-matching-in-an-array/
给你一个字符串数组 words ,数组中的每个字符串都可以看作是一个单词。请你按 任意 顺序返回 words 中是其他单词的子字符串的所有单词。
如果你可以删除 words[j] 最左侧和/或最右侧的若干字符得到 word[i] ,那么字符串 words[i] 就是 words[j] 的一个子字符串。
示例 1:
输入:words = ["mass","as","hero","superhero"] 输出:["as","hero"] 解释:"as" 是 "mass" 的子字符串,"hero" 是 "superhero" 的子字符串。 ["hero","as"] 也是有效的答案。
提示:
1 <= words.length <= 100
1 <= words[i].length <= 30
words[i] 仅包含小写英文字母。
题目数据 保证 每个 words[i] 都是独一无二的。
根据题意,我们就是要判断每一个字符串,是不是其他字符串的子串。
那么一开始的想法就是,对每一个字符串,都枚举其他字符串,那么就是 O(N ^ 2),同时,得到两个字符串后,要看,其中一个字符串是不是另一个的字符串,字符串匹配,那么简单的就是 O(len * len),所以,暴力枚举的时间复杂度是 O(N ^ 2 * len * len),那么根据数据范围,不会超时。
其中,字符串匹配的,可以用 string 的函数 find,str.find(s),即在 str 中,是否有 s。
class Solution {
public:
vector stringMatching(vector& words) {
vector ans;
int n = words.size();
for(int i = 0;i < n; ++i)
{
bool flag = false;
for(int j = 0;j < n; ++j)
{
// 判断 words[i] 是不是其中一个的子串
if(i == j) continue;
if(words[i].size() > words[j].size()) continue;
if(words[j].find(words[i]) != -1)
{
flag = true;
break;
}
}
if(flag) ans.push_back(words[i]);
}
return ans;
}
};
https://leetcode-cn.com/problems/queries-on-a-permutation-with-key/
给你一个待查数组 queries ,数组中的元素为 1 到 m 之间的正整数。 请你根据以下规则处理所有待查项 queries[i](从 i=0 到 i=queries.length-1):
- 一开始,排列 P=[1,2,3,...,m]。
- 对于当前的 i ,请你找出待查项 queries[i] 在排列 P 中的位置(下标从 0 开始),然后将其从原位置移动到排列 P 的起始位置(即下标为 0 处)。注意, queries[i] 在 P 中的位置就是 queries[i] 的查询结果。
请你以数组形式返回待查数组 queries 的查询结果。
示例 1:
输入:queries = [3,1,2,1], m = 5 输出:[2,1,2,1] 解释:待查数组 queries 处理如下: 对于 i=0: queries[i]=3, P=[1,2,3,4,5], 3 在 P 中的位置是 2,接着我们把 3 移动到 P 的起始位置,得到 P=[3,1,2,4,5] 。 对于 i=1: queries[i]=1, P=[3,1,2,4,5], 1 在 P 中的位置是 1,接着我们把 1 移动到 P 的起始位置,得到 P=[1,3,2,4,5] 。 对于 i=2: queries[i]=2, P=[1,3,2,4,5], 2 在 P 中的位置是 2,接着我们把 2 移动到 P 的起始位置,得到 P=[2,1,3,4,5] 。 对于 i=3: queries[i]=1, P=[2,1,3,4,5], 1 在 P 中的位置是 1,接着我们把 1 移动到 P 的起始位置,得到 P=[1,2,3,4,5] 。 因此,返回的结果数组为 [2,1,2,1] 。
示例 2:
输入:queries = [4,1,2,2], m = 4 输出:[3,1,2,0]
提示:
1 <= m <= 10^3
1 <= queries.length <= m
1 <= queries[i] <= m
根据题意,其实就是一个模拟,也就是我们不断的找到 p 中某个数的位置后,然后对 p 再进行处理。
那么如果暴力枚举,也就是枚举 q 中的每一个数,然后去找到这个数在 p 中的位置后。再将 p 处理(也就是在对应位置以及前面位置,相当于数后移动一位,然后第一位数变成 q 中的这个数)。
也就是,第一层循环 q,然后循环 p 找到 位置后,再循环 p 进行处理。
那么时间复杂度就是 O(q * p) = O(N ^ 2),根据数据范围,不会超时。
class Solution {
public:
vector processQueries(vector& q, int m) {
int n = q.size();
vector ans(n, 0);
vector p(m, 0);
for(int i = 0;i < m; ++i) p[i] = i + 1; // 弄到 p
for(int i = 0;i < n; ++i)
{
for(int j = 0;j < m; ++j) // 先找到 位置,那么这个就是 ans 的答案。
if(q[i] == p[j])
{
ans[i] = j;
break;
}
// 然后对 p 处理,此时位置是 ans[i],数都往后移动一位
for(int j = ans[i];j >= 1; --j)
{
p[j] = p[j - 1];
}
p[0] = q[i]; // 最后第一个数,是此时要找的 q[i] 这个数。
}
return ans;
}
};
https://leetcode-cn.com/problems/html-entity-parser/
「HTML 实体解析器」 是一种特殊的解析器,它将 HTML 代码作为输入,并用字符本身替换掉所有这些特殊的字符实体。
HTML 里这些特殊字符和它们对应的字符实体包括:
- 双引号:字符实体为 " ,对应的字符是 " 。
- 单引号:字符实体为 ' ,对应的字符是 ' 。
- 与符号:字符实体为 & ,对应对的字符是 & 。
- 大于号:字符实体为 > ,对应的字符是 > 。
- 小于号:字符实体为 < ,对应的字符是 < 。
- 斜线号:字符实体为 ⁄ ,对应的字符是 / 。
给你输入字符串 text ,请你实现一个 HTML 实体解析器,返回解析器解析后的结果。
示例 1:
输入:text = "& is an HTML entity but &ambassador; is not." 输出:"& is an HTML entity but &ambassador; is not." 解释:解析器把字符实体 & 用 & 替换
示例 2:
输入:text = "and I quote: "..."" 输出:"and I quote: \"...\""
提示:
1 <= text.length <= 10^5
- 字符串可能包含 256 个ASCII 字符中的任意字符。
其实是一道,字符串替换问题。即在源字符串中,如果存在对应的子串,那么就替换成其他的。
所以这道题,我们可以遍历字符串,如果发现是 & 开头的,那就一直找到 ; 结束的这部分子串,如果这个字符串存在替换列表中,就替换掉。
关于找到子串后,判断在不在,我们可以用哈希表 map 来存储,快速判断这个子串是否存在,并且为了快速得到 替换结果,我们可以 map 的value 是下标,这个下标对应的字符数组,就是这个子串要替换的字符。
而在字符串查找的时候,要特别小心,如果有一个字符串是,刚好开头 &,结尾才 ;那么此时时间复杂度就变为了 O(N ^ 2),就会超时。
因此我们要优化一下,我们发现,要替换的子串,最大长度为 7,所以我们找到 &,之后,最后查找长度为 7,如果此时还没到 ;那就说明,这个子串不是可以替换的。这样子,时间复杂度是 O(N * 7),就不会超时。
class Solution {
public:
unordered_map mp;
char c[6] = {'\"', '\'', '&', '>', '<', '/'}; // 替换列表
string entityParser(string text) {
string res = "";
// 初始化
mp.clear();
mp["""] = 1;
mp["'"] = 2;
mp["&"] = 3;
mp[">"] = 4;
mp["<"] = 5;
mp["⁄"] = 6;
int n = text.size();
for(int i = 0;i < n; ++i)
{
if(text[i] == '&') //发现是 &,那么就去找到 ;
{
int j = i + 1;
while(j < n && j - i + 1 <= 7 && text[j] != ';') ++j; // 因为最大长度是 7
string tep = text.substr(i, j - i + 1); // 把这部分可能替换的子串取下来,用于判断是不是我们要替换的
if(mp[tep])
{
res += c[mp[tep] - 1];
i = j;
}
else
res += text[i]; // 不是要替换的,说明从 & 的这部分,是要原来不变的。
}
else res += text[i];
}
return res;
}
};
https://leetcode-cn.com/problems/number-of-ways-to-paint-n-x-3-grid/
你有一个 n x 3 的网格图 grid ,你需要用 红,黄,绿 三种颜色之一给每一个格子上色,且确保相邻格子颜色不同(也就是有相同水平边或者垂直边的格子颜色不同)。
给你网格图的行数 n 。
请你返回给 grid 涂色的方案数。由于答案可能会非常大,请你返回答案对 10^9 + 7 取余的结果。
示例 1:
示例有图,具体看链接 输入:n = 1 输出:12 解释:总共有 12 种可行的方法:
示例 2:
输入:n = 5000 输出:30228214
提示:
n == grid.length
grid[i].length == 3
1 <= n <= 5000
这道题看起来就像是 DP,也就是,当前行进行涂色,会受到上一行涂色的影响。
1)设状态
我们假设 dp[ i ][ j ] 表示,涂 第 i 行,j 状态的 方案数。
那么关于 j,我们发现,一行有三个状态,我们把涂色变为数字 0 1 2 表示三种颜色,那么一行的三个状态就是 0 1 2的组合,也就是可以看成一个 3 位 的 三进制数,那么变为 十进制表示,就是 j。(因此,j 最大数字位 3^3 - 1 = 26)
因此,我们对每一行枚举 j,此时判断在上一行 dp[ i - 1][ k ] 状态下,是不是可以成功,如果可以成功,说明此时 dp[ i ][ j ] 的数量就在 dp[ i - 1][ k ] 方案数上累加(因为可以是其他 k 状态转移来的,所以是累加)。
所以总共三层循环,i,j,k,时间复杂度是 O(N * 30 * 30) 不会超时。
2)状态转移
也就是对于每一行 i,判断其所有可能状态 j,看从上一行的 所有 k 状态是否可以进行涂色。(因为某一行的涂色,只和本行和上一行有关而已)
那么我们要想能转移,就要判断 j 状态是不是满足条件(和上一行不冲突,同时自己这一行的状态不冲突)
3)初始化
一开始,所有的 dp = 0,(方案数为 0 )
然后,计算所有第一行 dp[ 1 ][ all j ] 的值,也就是,如果 j 状态成立,那么对应的方案数 = 1,不成立方案数 = 0。
4)结果
最后输出的结果,应该是 dp[ n ][ all ] 的累加,因为涂到最后一行,任何状态都是可以的,所以是累加。
const int MAXN = 5005;
const int MOD = 1e9 + 7;
class Solution {
public:
int dp[MAXN][27];
int vis[27];
bool check(int num) // 同一行状态冲突不冲突
{
int last = num % 3;
num /= 3;
for(int i = 0;i < 2; ++i)
{
if(num % 3 == last) return false;
last = num % 3;
num /= 3;
}
return true;
}
bool check(int a, int b) // 两行的状态不能冲突
{
for(int i = 0;i < 3; ++i)
{
if(a % 3 == b % 3) return false;
a /= 3;
b /= 3;
}
return true;
}
int numOfWays(int n) {
memset(vis, 0, sizeof(vis));
memset(dp, 0, sizeof(dp));
for(int i = 0;i < 27; ++i) // 先记录下,所有同一行不冲突的状态
{
if(check(i)) vis[i] = 1;
}
for(int i = 0;i < 27; ++i) // 初始化
{
if(vis[i]) dp[1][i] = 1;
}
for(int i = 2;i <= n; ++i)
{
for(int j = 0;j < 27; ++j) // 当前状态 j
{
if(vis[j] == 0) continue; // 如果本身自己状态就冲突,那就下一个状态
for(int k = 0;k < 27; ++k) // 上一行的状态 k
{
if(vis[k] == 0) continue; // 如果自己行的状态就不成立,就下一个状态
if(!check(j, k)) continue; // 两行的状态冲突,那就考虑下一个状态
dp[i][j] = (dp[i][j] + dp[i - 1][k]) % MOD;
}
}
}
int ans = 0; // 结果
for(int i = 0;i < 27; ++i) // 最后一行的,所有状态情况的涂色方案累加
{
if(vis[i]) ans = (ans + dp[n][i]) % MOD;
}
return ans;
}
};