周赛传送门
2022. 将一维数组转变成二维数组
思路:计算下标
时间复杂度:
首先判断能否转变:如果一维数组和二维数组包含元素个数相同,则可转换。
若能转换,则不难计算出下标的映射关系,设有一维数组的下标 ,二维数组的下标 ,三者均从 0 开始,三者必然满足:
class Solution {
public:
vector<vector<int>> construct2DArray(vector<int>& original, int m, int n) {
if (m*n != original.size()) {
return vector<vector<int>>();
}
vector<vector<int>> anw(m, vector<int>(n,0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
anw[i][j] = original[i*n+j];
}
}
return anw;
}
};
2023. 连接后等于目标字符串的字符串对
思路:暴力枚举
时间复杂度:, 为字符串个数, 为字符串的平均长度。
比较粗暴的做法,直接暴力下标 ,然后进行拼接校验即可。
class Solution {
public:
int numOfPairs(vector<string>& nums, string target) {
int anw = 0;
for (int i = 0; i < nums.size(); i++) {
for (int j = 0; j < nums.size(); j++) {
if (i != j && nums[i] + nums[j] == target) {
anw++;
}
}
}
return anw;
}
};
思路:哈希
时间复杂度:。 为目标串的长度。
借助哈希表记录每种字符串出现的次数,然后枚举 的前缀和后缀,从哈希表中查找出两者的出现次数直接计算即可。
class Solution {
public:
int numOfPairs(vector<string>& nums, string target) {
unordered_map<string, int> cnt;
for (const auto &n : nums) {
cnt[n]++;
}
int anw = 0;
for (int i = 1; i < target.size(); i++) {
auto pre = target.substr(0, i);
auto suf = target.substr(i);
if (pre != suf) {
anw += cnt[pre] * cnt[suf];
} else {
anw += cnt[pre] * (cnt[pre]-1);
}
}
return anw;
}
};
2024. 考试的最大困扰度
思路:双指针
时间复杂度:
该问题可以转化为:找出一个最长的子数组,其包含的 letter 不超过 k 个。letter 可以是 T 也可是 F。
一个比较直观的思路是,枚举 作为子数组的左边界,枚举 作为子数组的有边界。
找出其中符合要求的最长的子数组, 即为答案。暴力求解所有子数组 的时间复杂度为 。
int bf(const string &anw, int k, char letter) {
int len = 0;
for (int i = 0; i < anw.size(); i++) {
for (int j = i, cnt = 0; j < anw.size(); j++) {
// 在枚举右边界 j 的过程中,统计区间[i,j]内 letter 的数量。
if (anw[j] == letter) {
cnt++;
}
if (cnt == k+1) {
// letter 数量超过了限制,尝试更新一次 len。
len = max(len, j-i);
break;
} else if (j == anw.size()-1) {
// 枚举到了最后一个字符了,尝试更新一次 len。
len = max(len, j-i+1);
}
}
}
return len;
}
考虑一个性质:随着 的增大,其对应的最大的 只会单调递增。因此每次 自增后,无需从 开始枚举 ,而从之前 继续枚举即可。
int work(const string &anw, int k, char letter) {
int len = 0;
// i 从 0 开始,j 从 -1 开始,表示初始时为空区间。
for (int i = 0, j = -1, cnt = 0; i < anw.size(); i++) {
// 尝试将 anw[j+1] 加入到子数组中
while (j+1 < anw.size()) {
if (anw[j+1] != letter) {
// 不是letter,直接加入即可
j++;
} else if (cnt < k) {
// 是letter,但添加后仍然不超过 k,因此可加入。
cnt++;
j++;
} else {
// 是 letter,但添加后超过 k 了,因此不能添加,需break。
break;
}
}
// 本次迭代结束,更新一次答案。
len = max(len, j-i+1);
// 从子数组中移除 anw[i],为下一轮迭代做好准备。
cnt -= (anw[i] == letter);
}
return len;
}
完整代码如下:
class Solution {
public:
int work(const string &anw, int k, char letter) {
int len = 0;
// i 从 0 开始,j 从 -1 开始,表示初始时为空区间。
for (int i = 0, j = -1, cnt = 0; i < anw.size(); i++) {
// 尝试将 anw[j+1] 加入到子数组中
while (j+1 < anw.size()) {
if (anw[j+1] != letter) {
// 不是letter,直接加入即可
j++;
} else if (cnt < k) {
// 是letter,但添加后仍然不超过 k,因此可加入。
cnt++;
j++;
} else {
// 是 letter,但添加后超过 k 了,因此不能添加,需break。
break;
}
}
// 本次迭代结束,更新一次答案。
len = max(len, j-i+1);
// 从子数组中移除 anw[i],为下一轮迭代做好准备。
cnt -= (anw[i] == letter);
}
return len;
}
int maxConsecutiveAnswers(string answerKey, int k) {
return max(work(answerKey, k , 'T'), work(answerKey, k , 'F'));
}
};
2025. 分割数组的最多方案数
思路:哈希表,前缀和
时间复杂度:
设有一维数组 表示前缀和:
另设 个元素的累加和为 。
当不做修改时,满足要求的 必有:
当修改了元素 ,,令 ,此时有
- 对于 , 没有发生变化,因此满足要求的 必有
- 对于 ,前缀和发生了变化,因此满足要求的 必有
因此,我们可以在枚举 的过程中,通过哈希表记录两个区间 以及 内 的值出现的次数,从而可以 的得出修改 时的分割方案数。
class Solution {
public:
int waysToPartition(vector<int>& nums, int k) {
// pre 用于存储前缀和
vector<int64_t> pre(nums.size(), nums[0]);
// sumL 被修改元素之前的前缀和
// sumR 被修改元素及之后的前缀和
unordered_map<int64_t, int> sumL, sumR;
// 统计前缀和,并更新 sumL。
for (int i = 1; i < nums.size(); i++) {
pre[i] = pre[i-1] + nums[i];
sumL[pre[i-1]]++;
}
int64_t total = pre.back();
int anw = (total%2) ? 0 : sumL[total/2];// 不做修改时的分割方案数
// 枚举被修改的元素 nums[p]
// 初始时,sumL 包含了长度从 1 到 n-1 的前缀的和。
// 初始时,sumR 为空。
for (int p = nums.size()-1; p >= 0; --p) {
// 修改 nums[p] 带来的变化
int diff = k - nums[p];
// 因为分割之后不能有空数组,所以长度为 n 的前缀的和不能加入 sumR。
if (p != nums.size()-1) {
sumR[pre[p]]++;
}
// 尝试更新答案
if ((diff+total)%2 == 0) {
anw = max(anw, sumL[(diff+total)/2] + sumR[(total-diff)/2]);
}
// 从 sumL 移除 pre[p-1],为下一轮迭代做准备。
if (p != 0) {
sumL[pre[p-1]]--;
}
}
return anw;
}
};