周赛传送门
来公司打黑工了。忙里偷闲打场周赛,水了三道题,还挺满意哈哈哈。
2011. 执行操作后的变量值
时间复杂度:
算了个简单题了。根据每次操作的中间符号计算累加和即可。
class Solution {
public:
int finalValueAfterOperations(vector<string>& operations) {
int anw = 0;
for (const auto &s : operations) {
anw += (s[1] == '+' ? 1 : -1);
}
return anw;
}
};
2012. 数组美丽值求和
思路:预处理
时间复杂度:
设有一维数组 , 表示以 为起点的后缀中的最小值。
可以从大到小枚举 , 的预处理所有的 :
同样的,设有一维数组 , 表示以 为终点的前缀中的最大值。从小到大枚举 , 的预处理所有的 :
至此,每个位置的美丽值可以的得到了:
- 如果 ,则美丽值为 2。
- 如果不满足上述条件,且 ,则美丽值为 1。
- 其他情况为 0。
class Solution {
public:
int sumOfBeauties(vector<int>& nums) {
vector<int> suf(nums.size(), nums.back());
for (int i = nums.size()-2; i >= 0; i--) {
suf[i] = min(suf[i+1], nums[i]);
}
vector<int> pre(nums.size(), nums[0]);
for (int i = 1; i < nums.size(); i++) {
pre[i] = max(pre[i-1], nums[i]);
}
int sum = 0;
for (int i = 1; i <= nums.size()-2; i++) {
if (pre[i-1] < nums[i] && nums[i] < suf[i+1]) {
sum += 2;
} else if (nums[i-1] < nums[i] && nums[i] < nums[i+1]) {
sum += 1;
}
}
return sum;
}
};
2013. 检测正方形
思路:哈希,对角线
时间复杂度:
设有一个容器 ,用于记录坐标为 的点出现的次数。
对于每个 操作,仅需执行 。
对于每个 操作,可以枚举所有出现过的坐标 ,判断两者可否构成正方形的对角线,既满足下述两个需求:
- ,
如果满足上述需求,可以计算出正方形的另外两点的坐标:
拿到四个坐标后,从 中查询出,, 三个坐标上点的数量,记为 。
累加 即为本次 操作的答案。
class DetectSquares {
public:
// 定义容器 mark,key 为 坐标,value 为出现次数。
// key 的前32比特存储 x,后32比特存储y。这样就不用定义 hash 以及 operator== 啦
unordered_map<uint64_t, int> mark;
DetectSquares() {
}
void add(vector<int> point) {
// 更新点出现的次数
mark[uint64_t(point[0])<<32|point[1]]++;
}
int count(const vector<int> &point) {
int x = point[0], y = point[1];
int anw = 0; // anw 用于存储本次查询的答案
// 枚举所有出现过的点
for (const auto &p : mark) {
int x0 = (p.first>>32), y0 = p.first&0xffff;
// 判断(x,y) 和 (x0,y0) 能够构成正方形的对角线。
if (x == x0 || y == y0) continue;
if (abs(x-x0) != abs(y-y0)) continue;
int x1 = x, y1 = y0;
int x2 = x0, y2 = y;
auto it1 = mark.find(uint64_t(x1)<<32|y1);
auto it2 = mark.find(uint64_t(x2)<<32|y2);
// 判断另外两点是否存在。
if (it1 != mark.end() && it2 != mark.end()) {
// 累加c0*c1*c2
anw += it1->second * it2->second * p.second;
}
}
return anw;
}
};
/**
* Your DetectSquares object will be instantiated and called as such:
* DetectSquares* obj = new DetectSquares();
* obj->add(point);
* int param_2 = obj->count(point);
*/
2014. 重复 K 次的最长子序列
思路:全排列,枚举子集,剪枝
时间复杂度:。 为输入字符串的长度,为答案的长度上限。
首先我们将出现次数超过 的字符找出来,记为集合 。
同一字符每出现 次就加入 一次,这样方便处理序列中有重复字符的情况。
考虑到输入字符串的长度 。因此, 中的元素必然不超过 7 个。
暴力枚举 的所有子集的所有排列 ,大约有 种。对于每种 检查其在 中的出现次数。这样整体的时间复杂度为 。约为 ,必然是要超时的。下面介绍一种剪枝策略。
考虑排列 在 中出现次数小于 ,则所有以 为前缀的排列都不可能是候选答案,那就没必要枚举这些排列了。
考虑出现 次的,长度为 的子序列有 种,约为 种。因此加入剪枝之后,只会处理 种有效的排列,其他的排列会因为很短的前缀未出现而被过滤。因此时间复杂度的下限降至 ,至于上限没想到该如何估算🙁。。。
class Solution {
public:
int k_ = 0;
bool check(const std::string &input, const std::string &tmp) {
int i = 0, j = 0, k =0;
for (; j < input.size(); j++) {
if (tmp[i] == input[j]) {
i++;
if (i == tmp.size()) {
i = 0;
k++;
}
}
}
return k >= k_;
}
/*
* part 递归过程中构造的字符串
* pos 要处理 remain[pos] 了
* remain 预处理出来的字符集
* input 输入的字符串 s
* anw 用于存储答案
*/
void cst(std::string part, int pos, const std::string &remain, const std::string &input, std::string &anw) {
// 递归的边界
if (pos == remain.size()) { return; }
// 如果将剩余字符都加入 part,其长度仍小于 anw,那么就没必要处理了。
if (part.size() + remain.size()-pos < anw.size()) { return; }
// 将当前字符将入 part.
{
auto tmp = part;
tmp += remain[pos];
do {
// 检查 tmp 出现的字符是否超过 k
if (check(input, tmp)) {
// 尝试更新答案
if (tmp.size() > anw.size() || (tmp.size() == anw.size() && tmp > anw)) {
anw = tmp;
}
// tmp 出现了 k 次,尝试添加其他字符。
cst(tmp, pos+1, remain, input, anw);
}
} while(std::next_permutation(tmp.begin(), tmp.end()));
}
// 当前字符未加入 part 的情况。
{
cst(part, pos+1, remain, input, anw);
}
}
string longestSubsequenceRepeatedK(string s, int k) {
k_ = k; // 将k拷贝至成员函数,方便处理
std::string remain; // 至少出现 k 次的字符集合
unordered_map<char, int> count; // 用于统计每种字符的出现次数。
// 统计次数
for (auto c : s) {
count[c]++;
}
// 初始化 remain。字符每出现 k 次就加入 remain 一次。
for (auto &p : count) {
while (p.second >= k) {
p.second -= k;
remain += p.first;
}
}
// 排序是为了使用 std::next_permutation
sort(remain.begin(), remain.end());
std::string anw; // 用于存储答案
cst("", 0, remain, s, anw); // 枚举所有排列并寻找答案
return anw;
}
};