链接:https://leetcode.cn/contest/weekly-contest-307/
日期:20220821
第一题:赢得比赛需要的最少训练时长
模拟遍历一遍,求过程中的最小值就行了
class Solution {
public:
int minNumberOfHours(int a, int b, vector<int>& c, vector<int>& d) {
int s1 = 0, s2 = 0;
int n = d.size();
for (int i = 0; i < n; i++) {
s1 = max(c[i] + 1 - a, s1);
a -= c[i];
s2 = max(d[i] + 1 - b, s2);
b += d[i];
}
return s1 + s2;
}
};
第二题:最大回文数字
统计每个数字的个数,然后贪心的构造就行了。
class Solution {
public:
string largestPalindromic(string num) {
vector<int> cnts(10);
for (auto c : num) {
cnts[c - '0']++;
}
string s;
for (int i = 9; i >= 0; i--) {
if (i == 0 && s.size() == 0) continue;
if (cnts[i] / 2 != 0) {
s += string(cnts[i] / 2, '0' + i);
}
cnts[i] %= 2;
}
string k = s;
reverse(k.begin(), k.end());
for (int i = 9; i >= 0; i--) {
if (cnts[i]) {
s += i + '0';
break;
}
}
return s + k;
}
};
第三题:感染二叉树需要的总时间
重新建图,一次BFS或者DFS即可。
dfs + 链式前向星写法
const int N = 100010;
int h[N], nxt[N * 2], e[N * 2], idx;
class Solution {
public:
void add(int a, int b) {
nxt[idx] = h[a];
e[idx] = b;
h[a] = idx++;
}
void dfs1(TreeNode *root) {
if (root->left) {
add(root->val, root->left->val);
add(root->left->val, root->val);
dfs1(root->left);
}
if (root->right) {
add(root->val, root->right->val);
add(root->right->val, root->val);
dfs1(root->right);
}
}
int cnt = 0;
int mx = 0;
void dfs2(int r, int p) {
if (p != -1) {
cnt++;
mx = max(mx, cnt);
}
for (int x = h[r]; ~x; x = nxt[x]) {
int b = e[x];
if (b == p) continue;
dfs2(b, r);
}
cnt--;
}
int amountOfTime(TreeNode* root, int start) {
memset(h, -1, N * 4);
idx = 0;
dfs1(root);
cout << idx << endl;
queue<int> que;
dfs2(start, -1);
return mx;
}
};
第四题:找出数组的第 K 大和
这道题非常难,只有100多个选手做出来。
比赛时想到用归并来做,但是没写出代码。因为即使归并也有长度限制,需要一定的编码技巧。事后看讲解,即使归并也未必能做出来。
思路如下:n 个数的子序列有
2
n
2^n
2n 种可能。因为每一个数都有选和不选两种可能。假设 前 i -1 个数,枚举出的子序列长度为 p,那么第 i 个数会导致这个序列长度变成 2p。幸运的是,我们只需要 前 k 大的数即可。此外我们可以在第一次进行排序,后面都归并得到新序列,并再次取得前 k 个数即可。这样我们得复杂度是 O(nk)。
2000
×
100000
=
2
×
1
0
8
2000 × 100000 = 2×10^8
2000×100000=2×108必然会超时。
其实最大值是可以轻易得到得:所有正数相加即可。其他的数可以看作从最大的数中减去正数或者 加上负数。我们可以把所有的数看成是正数。然后求其第k - 1 小值,用最大的正数减去他即可。
那有什么变化呢?取第 k- 1 小值我们可以笃定,从前 k 小值中取就行。这样就不用遍历整个数组了。如果不理解可以做一个还算比较恰当的类比:在图论中,求最短路径有很多算法,但是求最长路径,却是一个 np-hard 问题。因此往往将最大转换成求最小,会将问题变得简单许多。
这样问题复杂度变成
O
(
k
2
)
O(k^2)
O(k2) 就可没问题了。
代码如下
typedef long long LL;
class Solution {
public:
long long kSum(vector<int>& nums, int k) {
LL sum = 0;
vector<LL> arr; // 把nums处理为负数
for (auto c : nums) {
if (c >= 0) {
arr.push_back(-c);
sum += c; // 求最大值
} else {
arr.push_back(c);
}
}
sort(arr.begin(), arr.end(), greater<LL>()); // 处理为负数后,排一下序,让最大的负数(绝对值最小)在前面
if (arr.size() > k) arr.erase(arr.begin() + k, arr.end());
vector<LL> a, b; // 归并用,a放归并序列1,另一个归并序列就是a的平移。b是归并的目标序列
a.push_back(sum); // 最大值在前面
for (auto c : arr) {
int i = 0, j = 0;
// 归并的目标序列数量最多为k。
while (b.size() < k && i < a.size() && j < a.size()) {
if (a[i] > a[j] + c) {
b.push_back(a[i++]);
} else {
b.push_back(a[j++] + c);
}
}
while (b.size() < k && i < a.size()) b.push_back(a[i++]);
while (b.size() < k && j < a.size()) b.push_back(a[j++] + c);
// 交换一下a,b。a继承归并的结果
std::swap(a, b);
b.clear();
}
return a[k - 1]; //第k个值就是结果
}
};
第二种写法
class Solution {
public:
typedef long long LL;
long long kSum(vector<int>& nums, int k) {
int n = nums.size();
vector<int> arr;
LL mx = 0;
for (auto x : nums) {
if (x < 0) {
arr.push_back(-x);
} else {
arr.push_back(x);
mx += x;
}
}
if (k == 1) return mx;
sort(arr.begin(), arr.end());
if (arr.size() > k) arr.erase(arr.begin() + k, arr.end());
vector<LL> a, b;
a.push_back(0); // 技巧 最大值放前面
for (auto x : arr) {
int i = 0, j = 0;
while (b.size() < k && i < a.size() && j < a.size()) {
if (a[i] < a[j] + x) {
b.push_back(a[i++]);
} else {
b.push_back(a[j++] + x);
}
}
while(b.size() < k && i < a.size()) b.push_back(a[i++]);
while(b.size() < k && j < a.size()) b.push_back(a[j++] + x);
std::swap(a, b);
b.clear();
}
return mx - a[k - 1];
}
};