周末去参见婚礼了,吃了两天大席哈哈哈。
周赛传送门
2000. 反转单词前缀
思路:std::string::find,std::reverse
时间复杂度:
首先调用 word.find(ch),得到 ch 在 word 中位置 pos:
- 如果
pos == std::string::npos,说明未找到,直接返回word。 - 反之,则使用
std::reverse翻转 [0,pos] 区间内的字符。
class Solution {
public:
string reversePrefix(string word, char ch) {
auto pos = word.find(ch);
if (pos != std::string::npos) {
std::reverse(word.begin(), word.begin() + pos + 1);
}
return word;
}
};
2001. 可互换矩形的组数
思路:最大公约数,哈希
时间复杂度:, 为宽高的最大值。 为求解最大公约数的时间复杂度。
设有两组正整数 与 ,其最大公约数分别为 和 。
如果正整数满足 ,则必有:
因此,我们可将 组 都化简为 ,记为 。
借助哈希表统计每种 的个数 ,然后累加组合数 即可得到答案。
class Solution {
public:
long long interchangeableRectangles(vector<vector<int>>& rects) {
std::unordered_map<uint64_t, int> mark;
for (const auto &p : rects) {
int g = __gcd(p[0], p[1]);
uint64_t w = p[0]/g;
uint64_t h = p[1]/g;
mark[w<<32|h]++;
}
int64_t sum = 0;
for (const auto &p : mark) {
int64_t cnt = p.second;
sum += (cnt-1L)*cnt/2;
}
return sum;
}
};
2002. 两个回文子序列长度的最大乘积
思路:暴力枚举
时间复杂度:
每个字符只会有三种状态:
- 在第一个序列中
- 在第二个序列中
- 都不在
考虑本题的数据范围较小,,可以暴力枚举所有状态,共 种。对于每种状态检查下两个序列是否回文,记录最优解即可。
class Solution {
public:
bool isValid(const std::string &s) {
for (int i = 0, j = s.size()-1; i < j; i++, j--) {
if (s[i] != s[j]) return false;
}
return true;
}
int dfs(const std::string &s, int pos, std::string &a, std::string &b) {
if (pos == s.size()) {
if (isValid(a) && isValid(b)) {
return a.size() * b.size();
}
return 0;
}
int opt = 0;
a += s[pos];
opt = max(opt, dfs(s, pos+1, a, b));
a.pop_back();
b += s[pos];
opt = max(opt, dfs(s, pos+1, a, b));
b.pop_back();
return max(opt, dfs(s, pos+1, a, b));
}
int maxProduct(string s) {
std::string a, b;
return dfs(s, 0, a, b);
}
};
2003. 每棵子树内缺失的最小基因值
思路:自下而上合并子树
时间复杂度:
如果所有节点的值都不为 1,则答案显而易见——所有子树最小缺失值均为 1。换言之,如果一棵子树中不含 1,那么该子树的答案必为 1。
不妨设值为 1 的节点为,而且题目约束了这种节点最多只有一个。
假设节点 存在,我们只需求以「从 到 这条路径上的节点」为根的子树的最小缺失值。其他子树的最小缺失值均为 1。
我们从 到 依次遍历路径上的点 ,并对每个 做如下操作:
- 把以 为根的子树中所有节点的值加入到集合 中。
- 找出 中不存在的最小正整数即为 的最小缺失值。
具体思路可参考注释~
class Solution {
int miss = 1; // 最小缺失值
bool exist[100002] = {0}; // 记录已经被添加过的值
vector<int> edge[100000]; // 边表
bool mark[100000] = {0}; // 防止子树被重复添加
void addSubTree(int root, const vector<int> &nums) {
if (mark[root]) { return; } // 该子树已经添加过了,
mark[root] = true; // 更新标记
exist[nums[root]] = true; // 更新集合
while(exist[miss]) { // 更新最小缺失值
miss++;
}
for (auto e : edge[root]) { // 添加子树
addSubTree(e, nums);
}
}
public:
vector<int> smallestMissingValueSubtree(vector<int>& parents, vector<int>& nums) {
int n = parents.size();
// 构造边表
for (int i = 1; i < n; i++) {
edge[parents[i]].emplace_back(i);
}
// 全部初始化为 1
vector<int> anw(parents.size(), 1);
int x = -1;
// 寻找节点 x
for (int i = 0; i < nums.size() && x == -1; i++) {
if (nums[i] == 1) {
x = i;
break;
}
}
// 从 x 到 root 依次遍历路径上的节点
while(x != -1) {
// 将子树中的值添加到集合 exist 中
addSubTree(x, nums);
// 更新最小缺失值。addSubTree 中会更新 miss
anw[x] = miss;
x = parents[x];
}
return anw;
}
};