周赛传送门
5914. 值相等的最小索引
思路:遍历
时间复杂度:
按题目要求,遍历所有元素,逐个进行检查即可。
class Solution {
public:
int smallestEqual(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++) {
if (i%10 == nums[i]) {
return i;
}
}
return -1;
}
};
5915. 找出临界点之间的最小和最大距离
思路:遍历
时间复杂度:
最大距离一定是第一个临界点和最后一个临界点之间的距离。最小距离一定是某两个相邻临界点之前的距离。
因此,我们可以遍历一次链表,按上述思路找出最小和最大距离,详见注释~
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector<int> nodesBetweenCriticalPoints(ListNode* head) {
// 辅助变量
// first: 第一个分界点在链表中的位置
// pre: 已遍历的节点中,位置最大的分界点在链表中的位置,-1 代表无分界点
// now: 当前节点的在链表中的位置
int first = -1, pre = -1, now = 0;
// 答案所需的两个距离
int minDis = -1, maxDis = -1;
// p: 指向当前节点
// last: 指向 p 的前一个节点
for (ListNode *p = head, *last = nullptr; p != nullptr; last = p, p = p->next, now++) {
// 标记变量: true → p是临界点,false → p不是临界点
bool flag = false;
if (last != nullptr && p->next != nullptr) {
if (last->val < p->val && p->val > p->next->val) {
flag = true;
} else if (last->val > p->val && p->val < p->next->val) {
flag = true;
}
}
// 如果 p 是临界点,则进行更新
if (flag) {
// first 为 -1 说明是第一个临界点,无法更新 maxDis,近更新 first 即可。
// 反之,则更新一下 maxDis
(first == -1) ? first = now : maxDis = now - first;
if (pre != -1) {
// 同理,pre 不为 -1 时,尝试用相邻临界点的距离更新 minDis
(minDis == -1) ? minDis = now - pre : minDis = min(minDis, now-pre);
}
pre = now;
}
}
return vector<int>{minDis, maxDis};
}
};
5916. 转化数字的最小运算数
思路:广度优先遍历(BFS)
时间复杂度:。 为可继续操作的取值范围。
标准的无权最短路的题目啦,借助 BFS 求解即可。
不过这题数据有点厉害,竟然卡常数啦。当 时,不能入队,否则会因为多了一次出队和入队的时间导致超时。
class Solution {
public:
int minimumOperations(vector<int>& nums, int start, int goal) {
// BFS 用的队列以及标记数组
queue<int64_t> q;
unordered_map<int64_t, int> mark;
// 初始化队列及标记数组
q.push(start);
mark[start] = 0;
// 封装一下三种操作,便于简化后续代码。但是不开编译优化的话,性能掉的很厉害,比赛时慎用~
std::vector<std::function<int64_t(int64_t, int64_t)>> ops{
[] (int64_t a, int64_t b) {
return int64_t(a + b);
},
[] (int64_t a, int64_t b) {
return int64_t(a - b);
},
[] (int64_t a, int64_t b) {
return int64_t(a ^ b);
}
};
int cnt = 0;
// 开始BFS
while(!q.empty()) {
auto f = q.front();
q.pop();
// 找到了目标值,返回操作次数
if (f == goal) {
return mark[f];
}
// 遍历 nums 进行操作
for (auto num : nums) {
for (const auto &op : ops) {
int next = op(f, num);
int cost = mark[f] + 1;
// 找到了目标值直接返回即可
if (next == goal) {
return cost;
}
// 判断是否有必要进队列。
// 注意这里应该卡常数了,
// next 不属于 [0, 1000] 时,如果入队会导致多了一次入队和出队的时间,导致超时了。
if (0 <= next && next <= 1000 && mark.find(next) == mark.end()) {
mark[next] = cost;
q.push(next);
}
}
}
}
// 未找到返回 -1
return -1;
}
};
5917. 同源字符串检测
思路:记忆化搜索
时间复杂度:。 为输入字符串长度。 为原始字符串长度,也可理解为匹配过程中,两字符串长度之差。
既然要用记忆化搜索,那么先定义状态。 表示将 的前 个字符,以及 的前 个字符成功匹配且两者的长度差为 。成功匹配可以这样理解:
将 的前 个字符扩展为字符串 ,其长度为 ,将 的前 个字符按扩展为字符串 ,其长度为 。如果 和 的前 个字符相同,则称其为成功匹配。 也可表示为 。
那么初始状态即为 ,即 的前 个字符,和 的前 个字符。因为两者都是空字符串,所以显然可成功匹配,且长度差为 。
有了初始状态,接着定义状态转移过程。
-
转移一:
当且仅当 都是数字时可发生该转移,即将这 个数字改为了 个小写字母。
-
转移二:
与「转移一」对称。当且仅当 都是数字时可发生该转移,即将这 个数字改为了 个小写字母。
-
转移三:
当且仅当 且 是字母时可发生该转移,即 与 中由数字得来的字母配对了。因为只移动了 而未移动 ,所以必然需要 。
-
转移四:
与「转移三」对称。与当且仅当 且 是字母时可发生该转移,即 与 中由数字得来的字母配对了。因为只移动了 而未移动 ,所以必然需要 。
-
转移五:
当且仅当 且 与 均为字母且相等时,才能发生该转移。试想当 时配对 与 ,意味着 个字符中有一个被确定了,这会导致后续过程中无法进行转移三和转移四了。
如果能从 转移至 ,则说明两者同源,反之不同源。
class Solution {
public:
unordered_set<uint64_t> mark;
// 压缩一下,用一个uint64_t 表示状态 (i,j,k)
inline uint64_t makeStatus(int i, int j, int k) {
uint64_t status = i;
status <<= 16;
status |= j;
status <<= 32;
// 注意 k 可能为负数,unsigned 和 signed 的位运算会有符号位的问题,因此做一下偏移
status |= (k + 10000);
return status;
}
bool isDigit(char c) {
return '0' <= c && c <= '9';
}
// 在转移一和转移二中,会用到此函数,即将 l 个数字转换为字符的数量。
// 因为会有多种转换方案,因此返回一个vector<pair<int, int>>
// pair<int, int> 的 first 即为转换后的字符的数量。second 即为 l,便于调用处进行状态转移
std::vector<pair<int, int>> convertDigit(const std::string &s, int p) {
int len = 0;
for (int i = p; i < s.size() && isDigit(s[i]); i++, len++) {
}
if (len == 1) {
return std::vector<pair<int, int>>{make_pair(s[p]-'0', len)};
}
if (len == 2) {
return std::vector<pair<int, int>>{
make_pair((s[p]-'0') + (s[p+1]-'0'), len),
make_pair((s[p]-'0')*10 + (s[p+1]-'0'), len)
};
}
return std::vector<pair<int, int>>{
make_pair((s[p]-'0') + (s[p+1]-'0') + (s[p+2]-'0'), len),
make_pair((s[p]-'0')*100 + (s[p+1]-'0')*10 + (s[p+2]-'0'), len),
make_pair((s[p]-'0')*10 + (s[p+1]-'0') + (s[p+2]-'0'), len),
make_pair((s[p]-'0')+ (s[p+1]-'0')*10 + (s[p+2]-'0'), len)
};
}
bool dfs(const std::string &s1, const std::string &s2, int i, int j, int d) {
// s1 的前 i 个字符,s2 的前 j 个字符匹配了,且长度差为 d。
// 本次调用会处理 s1[i] 和 s2[j]
// 已经检查过了,后续过程无法成功匹配,直接返回 false
uint64_t status = makeStatus(i, j, d);
if (mark.count(status)) {
return false;
}
// 匹配到末尾了,检查 d 即可
if (i == s1.size() && j == s2.size()) {
return d == 0;
}
bool res = false;
// 转移一
if (i < s1.size() && isDigit(s1[i])) {
for (const auto &info : convertDigit(s1, i)) {
res = res || dfs(s1, s2, i + info.second, j, d + info.first);
}
}
// 转移二
if (j < s2.size() && isDigit(s2[j])) {
for (const auto &info : convertDigit(s2, j)) {
res = res || dfs(s1, s2, i, j + info.second, d - info.first);
}
}
// 转移三
if (d < 0) {
if (i < s1.size() && !isDigit(s1[i])) {
res = res || dfs(s1, s2, i+1, j, d+1);
}
}
// 转移四
if (d > 0) {
if (j < s2.size() && !isDigit(s2[j])) {
res = res || dfs(s1, s2, i, j+1, d-1);
}
}
// 转移五
if (d == 0) {
if (i < s1.size() && j < s2.size() && s1[i] == s2[j] && !isDigit(s1[i])) {
res = res || dfs(s1, s2, i+1, j+1, d);
}
}
// 成功啦,直接返回
if (res == true) {
return true;
}
// 记录状态
mark.insert(status);
return false;
}
bool possiblyEquals(string s1, string s2) {
// 从 (0,0,0) 开始转移
return dfs(s1, s2, 0, 0, 0);
}
};