ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

单周赛 240 题解

2021-05-10 13:35:12  阅读:137  来源: 互联网

标签:下标 int 题解 ++ leq vector 240 数组 单周赛


本次比赛囊括众多面试中高级知识点,具体为 差分数组,单调队列,双指针,单调栈,拓扑排序,DAG 上 dp

人口最多的年份

给定 \(n\) 个年份区间 \([L_{i},\ R_{i}]\),表示第 \(i\) 个人的出生年份到死亡年份

定义年份 \(x\) 的 人口 为这一年活着的人口数量,对于第 \(i\) 个人,若其被记入年份 \(x\) 的人口,则有 \(L_{i}\leq x < R_{i}\)

返回 人口最多最早 年份

数据规定

\(1\leq n\leq 100\)

\(1950\leq L_{i} < R_{i}\leq 2050\)

题解

问题等价于,给定多个区间 \([L_{i},\ R_{i}]\),对区间中所有年份人口加 \(1\),经过数次修改后,返回年份人口最大值的最小下标

区间修改定值,离线查询,可以考虑使用 差分数组

  • 区间修改定值 指的是,对于区间上每一个数,统一增减一个定值
  • 离线查询 指的是,多次操作后一次性查询结果

具体来讲,预计算差分数组 \(D\),给 \(D_{L}\) 加 \(1\),\(D_{R+1}\) 减 \(1\),多次操作后使用 前缀和 还原原数组

之后,对数组排序,返回期望的下标即可,时间复杂度为 \(O(n + mlogm)\),其中 \(m\) 为年份的区间长度最大值

当然,本题的数据规模很小,可以使用暴力算法,暴力修改每一个区间的值,时间复杂度为 \(O(nm + mlogm)\)

/* 差分数组 */
class Solution {
public:
    int maximumPopulation(vector<vector<int>>& logs) {
        vector<int> b(107);
        vector<int> d(107);
        for (int i = 0; i < logs.size(); ++i) {
            d[logs[i][0] - 1950]++;
            d[logs[i][1] - 1950]--;
        }
        for (int i = 1; i < 107; ++i) d[i] += d[i - 1];
        for (int i = 0; i < 107; ++i) b[i] = i;
        sort(b.begin(), b.end(), [&](int x, int y) {
            if (d[x] == d[y]) return x < y;
            return d[x] > d[y];
        });
        return 1950 + b[0];
    }
};
/* 暴力算法 */
class Solution {
public:
    int maximumPopulation(vector<vector<int>>& logs) {
        vector<int> a(107);
        vector<int> b(107);
        for (int i = 0; i < logs.size(); ++i) {
            for (int j = logs[i][0]; j < logs[i][1]; ++j) {
                a[j - 1950]++;
            }
        }
        for (int i = 0; i < 107; ++i) b[i] = i;
        sort(b.begin(), b.end(), [&](int x, int y) {
            if (a[x] == a[y]) return x < y;
            return a[x] > a[y];
        });
        return 1950 + b[0];
    }
};

下标中的最大值

给定两个 非递增 数组 A, B,下标从 0 开始计数

A 的下标 i,与 B 的下标 j 满足 i <= j, A[i] <= B[j],则称 i, j 为有效下标对,定义下标对的 距离j - i

返回所有 有效 下标对的 最大距离,若不存在有效下标对,返回 \(0\)

数据保证

\(1\leq A.length\leq 10^5\)

\(1\leq B.length\leq 10^5\)

\(1\leq A_{i},\ B_{i}\leq 10^5\)

题解

由于两个数组具有 单调性,因此可以考虑用双指针 动态扩充队列,队列维护 \(B\) 中元素的下标

具体来说,若 A[i] <= B[j],那么一定有 A[i + 1] <= B[j],因此可以动态扩充一个队列,对于队列中所有下标 j,都满足 A[i] <= B[j]

  • 考虑入队,只要 A[i] <= B[j],指针 j 就可以右移,直到移动到 \(B\) 的右边界
  • 考虑出队,每次把队首出队,因为其下标 j 不满足 i <= j

由于需要队尾入队,队首出队,可以使用 双端队列 来实现

对于 A[i],若队列不为空,每次拿队尾的下标 ji 作差即可,维护答案最大值

时间复杂度为 \(O(n)\)

class Solution {
public:
    int maxDistance(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(), m = nums2.size();
        deque<int> dq;
        int ans = 0;
        for (int i = 0, j = 0; i < n; ++i) {
            if (!dq.empty()) dq.pop_front();
            while (j < m && nums1[i] <= nums2[j])
                dq.push_back(j++);
            if (!dq.empty()) {
                ans = max(ans, dq.back() - i);
            }
        }
        return ans;
    }
};

子数组最小乘积

定义一个数组 \(A\) 的 最小乘积 为数组中 最小值 乘以 数组的和

  • 举例来讲,数组 [3, 2, 5] 的最小值为 2,数组和为 10,因此最小乘积为 20

现在给定一个长为 \(n\) 的正整数数组 nums,请计算 nums 中所有 非空子数组最小乘积最大值

  • 举例来讲,数组 [1, 2, 3, 2] 满足条件的子数组为 [2, 3, 2],答案为 2 * (2 + 3 + 2) = 14

题目保证,存储的答案可以使用 64 位有符号整数存储,但是最终的答案需要对 \(10^9 + 7\) 取余

数据保证

\(1\leq n\leq 10^5\)

\(1\leq A_{i}\leq 10^7\)

题解

直观来想,如果枚举子数组,一共需要计算 \(1 + 2 + .. + n = \frac{n(n + 1)}{2}\) 次,不考虑区间查询最小值,已经是 \(O(n^2)\) 的时间复杂度,无法通过全部数据规模

换个角度,枚举每一个元素 \(A_{i}\),考虑 \(A_{i}\) 所能 管辖的区域

具体来讲,我们需要为每一个 \(A_{i}\) 计算出一个区间 \([L,\ R]\),使得 \(A_{i}\) 是 \(A_{L},\ A_{L + 1},\ ..,\ A_{R}\) 中的最小值

那么我们需要分别计算出 \(A_{i}\) 右侧和左侧第一个更小值,这个可以使用 单调栈 解决,详见 下一个更大元素 I

预处理每一个元素所管辖的区间,维护答案的最大值,时间复杂度 \(O(n)\)

class Solution {
public:
    typedef long long LL;
    int maxSumMinProduct(vector<int>& nums) {
        const int MOD = 1e9 + 7;
        int n = nums.size();
        vector<LL> a(n + 1), sum(n + 1);
        for (int i = 1; i <= n; ++i) {
            a[i] = nums[i - 1];
            sum[i] = sum[i - 1] + a[i];
        }
        vector<int> rmin(n + 1, -1), lmin(n + 1, -1); // -1 表示没有更小的
        stack<int> mono1, mono2; // 递增
        for (int i = 1; i <= n; ++i) {
            while (!mono1.empty() && a[mono1.top()] > a[i]) {
                rmin[mono1.top()] = i;
                mono1.pop();
            }
            mono1.push(i);
        }
        for (int i = n; i >= 1; --i) {
            while (!mono2.empty() && a[mono2.top()] > a[i]) {
                lmin[mono2.top()] = i;
                mono2.pop();
            }
            mono2.push(i);
        }
        LL ans = 0;
        for (int i = 1; i <= n; ++i) {
             /* 若为 -1,则左/右边没有更小元素 */
             /* 管辖范围可以拓展到边界 */
            int L = (lmin[i] == -1 ? 1 : lmin[i] + 1);
            int R = (rmin[i] == -1 ? n : rmin[i] - 1);
            ans = max(ans, a[i] * (sum[R] - sum[L - 1]));
        }
        return ans % MOD;
    }
};

后记

这题我最早听说,是同学在今年春招面试美团后端时遇到的,后来牛客网上有同学爆料腾讯广告投放也出了这么一道题,如果不转换个枚举思路,这题是很难做的

有向图中最大颜色值

给定一个 \(n\) 个节点,\(m\) 条边的 有向图,其中 \(1\leq n,\ m\leq 10^5\)

给定一个长为 \(n\),并且由 小写字母 构成的字符串 \(color\),表示节点 \(1,\ 2,\ ..,\ n\) 的颜色

在图论中,我们用 路径 表示一个点序列 \(x_{1}\rightarrow x_{2}\rightarrow ... \rightarrow x_{k}\),其中 \(x_{i}\rightarrow x_{i + 1}\) 表示点 \(x_{i}\) 和点 \(x_{i + 1}\) 有单向连边,下标 \(i\) 满足 \(1\leq i < k\)

我们定义,路径中 出现次数最多的 颜色的节点数目为路径的 颜色值,请计算图中所有路径 最大颜色值,如果图里有环,返回 \(-1\)

题解

注意到给定的是 有向图

  • 对于有环的情况,可以使用 拓扑排序 侦测,拓扑排序详见 课程表
  • 对于无环的情况,即 有向无环图(DAG),非常适合做 动态规划(DP)

我们定义 \(dp_{i,\ j}\) 表示到第 \(i\) 个节点,颜色 \(j\) 出现的最大次数,考虑节点 \(i\) 的所有 前继节点 \(u\),我们可以轻松的写出状态转移方程

\[dp_{i,j} = max\left\{dp_{i,j},\ dp_{u,j} + add\right\} \]

其中 \(add\) 为指示变量,当节点 \(i\) 的颜色和 \(j\) 相等时,颜色个数要增 \(1\),当颜色不同时,只要继承前继节点的颜色数量即可,具体来讲

\[add = \left\{\begin{matrix} 1,\ & j = colour_{i} \\ 0,\ & j\neq colour_{i} \end{matrix}\right. \]

上面的分析要求给出 前继节点,这需要对图上节点的先后关系做分析,而拓扑排序正好可以帮助我们做到这点

在本题中,拓扑排序的作用有两个

  • 首先是判环
  • 其次是给定节点之间的 先后关系

我们在拓扑排序的过程中对状态进行转移,最后维护每个节点的颜色最大值即可

时间复杂度为 \(O(|\Sigma|(n + m))\),其中 \(|\Sigma|\) 是字符集的大小

class Solution {
public:
    int largestPathValue(string colors, vector<vector<int>>& edges) {
        int n = colors.size();
        int m = edges.size();
        vector<int> ind(n);
        vector<vector<int>> g(n);
        vector<vector<int>> dp(n, vector<int>(26, 0));
        queue<int> q;
        for (int i = 0; i < m; ++i) {
            ind[edges[i][1]]++;
            g[edges[i][0]].push_back(edges[i][1]);
        }
        for (int i = 0; i < n; ++i) {
            if (!ind[i]) {
                q.push(i);
                dp[i][colors[i] - 'a'] = 1;
            }
        }
        int cnt = 0;
        while (!q.empty()) {
            int u = q.front(); q.pop();
            ++cnt;
            for (auto &i: g[u]) {
                for (int j = 0; j < 26; ++j) {
                    int add = j == colors[i] - 'a' ? 1 : 0;
                    dp[i][j] = max(dp[i][j], dp[u][j] + add);
                }
                --ind[i];
                if (!ind[i]) q.push(i);
            }
        }
        if (cnt != n) return -1;
        int ans = 0;
        for (int i = 0; i < n; ++i)
            ans = max(ans, *max_element(dp[i].begin(), dp[i].end()));
        return ans;
    }
};

后记

这题很好想,注意到大部分 case 面向 有向无环图 (DAG),就会往 DAG 上 dp 考虑,而对于判环和节点的先后顺序,可以使用 拓扑排序 解决,由此一来,这道题也就水到渠成了

标签:下标,int,题解,++,leq,vector,240,数组,单周赛
来源: https://www.cnblogs.com/ChenyangXu/p/14750655.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有