5750. 人口最多的年份
知识点:哈希
时间复杂度:O(n)*O(Years)
因为数据范围较小,直接暴力统计每一年的人数即可。
class Solution {
public:
int maximumPopulation(vector<vector<int>>& logs) {
int num[2050] = {0};
for (const auto &log : logs) {
for (int i = log[0]; i < log[1]; i++) {
num[i]++;
}
}
int anw = 100;
for (int i = 2049; i >= 1950; --i) {
if (num[i] >= num[anw]) {
anw = i;
}
}
return anw;
}
};
5751. 下标对中的最大距离
知识点:双指针
时间复杂度:O(n+m)
设有指针 i,j,初始时分别指向 nums1 和 nums2 的起始位置。
因为 nums1 和 nums2 都是单调递增的,所以随着 i 向右移动,满足要求的i <= j 且 nums[i] <= nums[j] 的指针 j 也必然向右移动。
换言之,对于每个 i,在寻找最佳的 ji时,没必要从头寻找,而是从i-1的答案ji-1处开始寻找即可。
这样就保证了nums1中的每个元素只会被访问一次,nums2中的每个元素的平均访问次数不会超过2。
class Solution {
public:
int maxDistance(vector<int>& nums1, vector<int>& nums2) {
int anw = 0;
for (int i = 0, j = 0; i < nums1.size(); i++) {
j = max(j, i);
if (j >= nums2.size() || nums[i] > nums[j]) {
continue;
}
while(j+1 < nums2.size() && nums1[i] <= nums2[j+1]) {
j++;
anw = max(anw, j-i);
}
}
return anw;
}
};
5752. 子数组最小乘积的最大值
知识点:并查集
时间复杂度:O(n*lgn)
将所有子数组按照其最小值分类,则最多会分成 n 中类型,即同一分类的子数组的最小值相同。
考虑到要寻找 最小乘积 最大 的子数组,那么答案必然为某种分类中的累加和最大的那个子数组的最小乘积。
那么现在有思路:枚举分类 fac,然后寻找每个元素均不小于 fac的 累加和最大的 子数组。这个子数组的最小乘积就是 fac分类的最优解。所有分类的最优解中的最优解即为答案。
那么如何寻找fac的最优解呢?
设初始时,nums中的每个元素构成一个子数组,即初始时有 n 个长度为 1 的子数组。
接下来,我们从大向小枚举fac,将不小于fac分类的 相邻的 子数组融合。这些刚刚融合而成的子数组和原本就存在的fac分类的子数组中的最优解即为fac分类的最优解。
熟悉并查集的老铁,应该已经想到可以用并查集实现融合的操作了。不熟悉的老铁,可以看下包教不包会的并查集教程。
class Solution {
public:
int fa[100001];
int64_t sum[100001];
int64_t fac[100001];
map<int, vector<int>> pos;
int find(int x) {
int t = x;
while(x != fa[x]) {
x = fa[x];
}
while(t != fa[t]) {
int tmp = fa[t];
fa[t] = x;
t = tmp;
}
return x;
}
int merge(int u, int v) {
int fu = find(u);
int fv = find(v);
if (fu != fv) {
sum[fu] += sum[fv];
fac[fu] = min(fac[fu], fac[fv]);
fa[fv] = fu;
}
return fu;
}
int maxSumMinProduct(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++) {
fa[i] = i;
}
for (int i = 0; i < nums.size(); i++) {
fac[i] = nums[i];
}
for (int i = 0; i < nums.size(); i++) {
sum[i] = nums[i];
}
for (int i = 0; i < nums.size(); i++) {
pos[nums[i]].push_back(i);
}
int64_t anw = 0;
for (auto it = pos.rbegin(); it != pos.rend(); ++it) {
int64_t limit = it->first;
anw = max(anw, limit*limit);
for (auto p : it->second) {
if (p+1 < nums.size() && nums[p+1] >= limit) {
int root = merge(p, p+1);
anw = max(anw, sum[root] * fac[root]);
}
if (p-1 >= 0 && nums[p-1] >= limit) {
int root = merge(p-1, p);
anw = max(anw, sum[root] * fac[root]);
}
}
}
return anw%1000000007;
}
};
5753. 有向图中最大颜色值
知识点:有向无环图,DFS
时间复杂度:O(点数 + 边数)
题目要求有环的情况输出-1,所以我们下面只讨论输入为有向无环图的情况。
设 dpij 为以点i 为起点的路径集合中,颜色 j 在该颜色出现次数最多的路径上的出现次数。
那么可知,答案必为 dpij,i∈[0,n), j∈[0,26) 中的最大值。
易得递推公式: 其中 next 为 i 的后继节点。
那么如何判断有环呢?如果在dfs过程中,某个点的访问次数超过了其入度,则说明有环。
class Solution {
public:
int incnt[100001] = {0};
int cnt[100001][26];
int visit[100001] = {0};
int anw = -1;
string col;
vector<int> next[100001];
bool dfs(int cur) {
if (visit[cur] > incnt[cur]) {
return false;
}
visit[cur]++;
if (cnt[cur][0] != -1) {
return true;
}
for (int i = 0; i < next[cur].size(); i++) {
int n = next[cur][i];
if(!dfs(n)) {
return false;
}
for (int j = 0; j < 26; j++) {
cnt[cur][j] = max(cnt[n][j], cnt[cur][j]);
anw = max(anw, cnt[cur][j]);
}
}
for (int i = 0; i < 26; i++) {
cnt[cur][i] = max(0, cnt[cur][i]);
}
int c = col[cur]-'a';
cnt[cur][c] = max(0, cnt[cur][c]) + 1;
anw = max(anw, cnt[cur][c]);
return true;
}
int largestPathValue(string colors, vector<vector<int>>& edges) {
col = colors;
for (const auto &edge : edges) {
incnt[edge[1]]++;
next[edge[0]].push_back(edge[1]);
}
memset(cnt, -1, sizeof(cnt));
memset(visit, 0, sizeof(visit));
for (int i = 0; i < colors.size(); i++) {
if (visit[i] == 0) {
if(!dfs(i)) {
return -1;
}
}
}
for (int i = 0; i < colors.size(); i++) {
if (visit[i] == 0) {
return -1;
}
}
return anw;
}
};