• 单周赛 240 题解


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

    人口最多的年份

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

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

    返回 人口最多最早 年份

    数据规定

    (1leq nleq 100)

    (1950leq 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)

    数据保证

    (1leq A.lengthleq 10^5)

    (1leq B.lengthleq 10^5)

    (1leq 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) 取余

    数据保证

    (1leq nleq 10^5)

    (1leq 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) 条边的 有向图,其中 (1leq n, mleq 10^5)

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

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

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

    题解

    注意到给定的是 有向图

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

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

    [dp_{i,j} = maxleft{dp_{i,j}, dp_{u,j} + add ight} ]

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

    [add = left{egin{matrix} 1, & j = colour_{i} \ 0, & j eq colour_{i} end{matrix} ight. ]

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

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

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

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

    时间复杂度为 (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 考虑,而对于判环和节点的先后顺序,可以使用 拓扑排序 解决,由此一来,这道题也就水到渠成了

  • 相关阅读:
    Firefox OS 开发者预览版手机上线数小时即售罄
    7 款风格新颖的 jQuery/CSS3 导航
    大数据时代的 9 大KeyValue存储数据库
    jQuery 2.0发布,不支持IE 6/7/8
    全面理解面向对象的 JavaScript
    Hdoop入门
    Java Web 高性能开发,第 2 部分: 前端的高性能
    MySQL数据库存储引擎和分支现状
    11 个超棒的 jQuery 分步指引插件
    [扩展/新建swap]将文件或分区加载为swap
  • 原文地址:https://www.cnblogs.com/ChenyangXu/p/14750655.html
Copyright © 2020-2023  润新知