5380. 数组中的字符串匹配
O(n^2) 暴力枚举,具体思路见注释。介绍一下 string 的 find 函数。
- std::string::find 函数。接收一个字符串,返回该字符串第一次出现的位置。
- 如果没有找到,返回 std::string::npos。
class Solution {
public:
vector<string> stringMatching(vector<string>& words) {
vector<string> res;
//枚举每个字符串作为子串。
for(int i = 0; i < words.size(); i++) {
//对于每个子串,枚举所有字符串作为其母串。
for(int j = 0; j < words.size(); j++) {
//母串和子串不能是同一个字符串,所以需要 i != j
//检查words[i] 是否是 words[j] 的子串。
if(i != j && words[j].find(words[i]) != std::string::npos) {
res.push_back(words[i]);
break;
}
}
}
return res;
}
};
5381. 查询带键的排列
O(m*q) 暴力枚举。使用链表模拟即可,具体思路见注释。简单介绍下 std::list 的 insert 和 erase 函数。
- std::list::insert 可以接受一个std::list::iterator iter和一个待插入元素 p。该函数会将 p 插入到 iter 之前。
- std::list::erase 可以接受一个std::list::iterator iter。该函数会删除 iter 指向的结点。
class Solution {
public:
vector<int> processQueries(vector<int>& queries, int m) {
list<int> dataList;
//初始化排列。
for(int i = 1; i <= m; i++) {
dataList.push_back(i);
}
vector<int> res;
//处理每个请求
for(auto v : queries) {
auto it = dataList.begin();
int ans = 0;
//O(m)的查找当前询问的位置。
for(auto it = dataList.begin(); it != dataList.end(); ++it, ++ans) {
// 找到了
if(*it == v) {
res.push_back(ans); //记录答案
//在链表的头部插入该元素。
dataList.insert(dataList.begin(), *it);
//删除旧的元素。
dataList.erase(it);
break;
}
}
}
return res;
}
};
5382. HTML 实体解析器
准备数据容器
使用 unordered_map<string, string> 记录HTML的特殊字符和它们对应的字符实体。 使用一个新的string记录答案。
遍历 text
从头遍历 text,如果以 text[i] 为起点的字符串在 unordered_map 中出现了,那么将对应的字符实体放入 string 中。否则直接将 text[i] 放入string中。具体细节见注释。
class Solution {
public:
string entityParser(string text) {
string res;
unordered_map<string, string> dict; //将特殊字符和字符实体放入 dict 中。
dict["""] = "\"";
dict["'"] = "'";
dict["&"] = "&";
dict[">"] = ">";
dict["<"] = "<";
dict["⁄"] = "/";
for(int i = 0, n = text.size(); i < n; ) {//遍历text
bool match = false;
//判断以text[i]为起点的字符串是否是特殊字符
for(auto p = dict.cbegin(); p != dict.cend(); p++) {
//匹配上了
if(text.substr(i, p->first.size()) == p->first) {
res += p->second;
i += p->first.size(); //i 后移 p->first.size()
match = true; //标记已匹配
break;
}
}
if(match) {
continue;
}
res += text[i++]; //未匹配上,将 text[i] 放入 res。i 后移一位。
}
return res;
}
};
5383. 给 N x 3 网格图涂色的方案数
解题套路:状态压缩 + 递推。
状态压缩
用一个数字表示集合的一种状态。 比如在本题中,三个并列的格子可以看作是一个集合。我们用数字表示这个集合的状态,也就是染色方案。 具体的表示方法如下: 因为只有三种颜色,所以可以用三进制数字来表示染色方案。用 0,1,2 分别代表红黄绿。因为每个集合只有三个格子,所以只需要用三位数字。 比如21 = 0 + 31 + 92,代表第一个格子为红色,第二个格子为黄色,第三个格子为绿色。 我们可以把这个数字称为状态码。
关于状态码的取值范围
当三个格子都染成红色时,状态码取得最小值即 0 + 03 + 09 = 0。 当三个格子都染成绿色时,状态码取得最大值即 2 + 23 + 29 = 26。 所以状态码的取值范围为[0,26]。
递归
设 dp[n][sc] 为将前 n 层都染上颜色,且第n层的染色为 sc。sc 即上述的状态码。 当 n 等于 1时,dp[n][sc] 很好计算,即判断sc代表的染色方案是否合法即可。 如果合法 dp[1][sc] = 1,否则 dp[1][sc] = 0。
当 n > 1 时,dp[n][sc] 由 dp[n-1][psc] 推导得出。sc 为 第n行的染色方案,psc为 n-1行的染色方案。递推式子如下: dp[n][sc] += dp[n-1][psc] (0 <= psc < 27)。
class Solution {
public:
const int mod = 1000000007;
int dp[5001][27] = {0};
//根据每个格子的颜色获取状态码
int sc(int i, int j, int k) {
return i + j*3 + k *9;
}
//根据状态码获取每个格子的颜色
void cs(int statusCode, int &a, int &b, int &c) {
a = statusCode % 3;
b = (statusCode - a)%9/3;
c = statusCode/9;
}
int numOfWays(int n) {
初始化 n = 1 的方案数。
for(int i = 0; i <= 2; i++) {
for(int j = 0; j <= 2; j++) {
for(int k = 0; k <= 2; k++) {
if(i != j && j != k) {
dp[1][sc(i,j,k)] = 1;
}
}
}
}
//枚举行数
for(int level = 2; level <= n; level++) {
//枚举前一行的染色方案
for(int pre = 0; pre < 27; ++pre) {
int a, b, c;
cs(pre, a, b, c);
if(a == b || b == c) {
continue;
}
//枚举当前行的染色方案
for(int i = 0; i <= 2; i++) {
if(i == a) { continue; } //检查是否冲突
for(int j = 0; j <= 2; j++) {
if(i == j || j == b) { continue; } //检查是否冲突
for(int k = 0; k <= 2; k++) {
if(j == k || k == c) { continue; } //检查是否冲突
(dp[level][sc(i,j,k)] += dp[level-1][pre]) %= mod; //当前染色方案可行,累加方案数。
}
}
}
}
}
int ans = 0;
for(int i = 0; i < 27; i++) {
(ans += dp[n][i]) %= mod; //计算总的方案数。
}
return ans;
}
};
如果感觉有点意思,可以关注👏HelloNebula👏
- 分享周赛题解
- 分享计算机专业课知识
- 分享C++相关岗位面试题
- 分享专业书籍PDF