• 单周赛 250 题解


    题外话:自己在 okr 中立了个 flag,希望在 7-8 双月逐渐熟练掌握 python, go,所以在之后的周赛题解中,我会逐渐引入 python, go 的代码,有兴趣的小伙伴可以一起学习

    本次周赛着重讲解一下第三题,前面两题一笔带过,最后一题涉及到 (01 trie) 树,后续单独出一篇文章讲解

    本次周赛知识点:字符串,数学,上取整,动态规划,高性能,前缀后缀

    可以输入的最大单词数

    给定一个字符串 (w) 表示一串单词,给定一个字符串 (b) 表示破坏的字母,对于 (w) 中的所有单词,如果包含 (b) 中的字母,那么该单词不可用,计算可用的单词数

    题解

    用一个数组存放 w 中的单词,用桶存放 b 中的字符,循环判断即可

    // cpp
    class Solution {
    public:
        int canBeTypedWords(string t, string b) {
            vector<string> w;
            string temp = "";
            int bucket[27] = {0};
            for (int i = 0; i < b.size(); ++i) bucket[b[i] - 'a']++;
            for (int i = 0; i <= t.size(); ++i) {
                if (!(t[i] >= 'a' && t[i] <= 'z')) w.push_back(temp), temp = "";
                else temp += t[i];
            }
            int ans = 0;
            for (auto i: w) {
                int f = 0;
                for (int j = 0; j < i.size(); ++j) if (bucket[i[j] - 'a']) {f = 1; break;}
                if (!f) ans++;
            }
            return ans;
        }
    };
    
    # py
    class Solution:
        def canBeTypedWords(self, t: str, b: str) -> int:
            st = set(b)
            ans = 0
            for word in t.split():
                flag = 0
                for ch in word:
                    if ch in st:
                        flag = 1
                        break
                if not flag:
                    ans += 1
            return ans
    
    // go
    func canBeTypedWords(t string, b string) (ans int) {
        for _, word := range strings.Split(t, " ") {
            if !strings.ContainsAny(word, b) {
                ans++
            }
        }
        return ans
    }
    

    新增的最少台阶数

    给一个严格递增的数组 (r),表示台阶的高度,给一个正整数 (dist) 表示爬一次最高能上的台阶高度,初始时站在高度 (0) 位置,计算最少还需要放置几个台阶,使得可以爬到最后一个台阶

    例如 r = [1, 3, 5, 10], dist = 2,需要放置 2 个台阶,高度分别为 7, 9(方案不唯一)

    例如 r = [3, 6, 8, 10], dist = 3,不需要放置台阶

    例如 r = [3, 4, 6, 7], dist = 2,需要放置 1 个台阶,高度为 1

    题解

    对于任意两个台阶,高度分别为 (i, j),设高度差为 (h),则可以放 (lceilfrac{h}{dist} ceil - 1) 个台阶

    因此我们计算出差分数组 (d),依次做判断即可

    在实现 (lceilfrac{a}{b} ceil - 1) 时,使用 (a - 1) / b 即可

    // cpp
    class Solution {
    public:
        int addRungs(vector<int>& r, int dist) {
            int n = r.size();
            vector<int> d(n);
            d[0] = r[0];
            for (int i = 1; i < n; ++i) {
                d[i] = r[i] - r[i - 1];
            }
            int ans = 0;
            for (int i = 0; i < n; ++i) {
                if (d[i] % dist == 0) ans += d[i] / dist - 1;
                else ans += d[i] / dist;
            }
            return ans;
        }
    };
    
    # py
    class Solution:
        def addRungs(self, r: list[int], dist: int) -> int:
            pre = ans = 0
            for i in r:
                h = i - pre
                ans += (h - 1) // dist
                pre = i
            return ans
    
    // go
    func addRungs(r []int, dist int) (ans int) {
        pre := 0
        for _, h := range r {
            d := h - pre
            ans += (d - 1) / dist
            pre = h
        }
        return ans
    }
    

    扣分后的最大得分

    给定一个 (m imes n) 的矩阵 (p),每一个格子有一个权值 (p[i][j])

    现在从第一行到最后一行,每一行选一个格子,收益为权值和

    但是,对于第 (i) 行和第 (i - 1) 行,如果分别选了 (j, k) 列,那么收益减去 (mid j - kmid)

    数据保证

    (1leq m, n, m imes nleq 10^5)

    (0leq p[i][j]leq 10^5)

    题解

    考虑二维动态规划,定义 (dp[i][j]) 表示走到 ((i, j)) 位置的收益最大值,则状态转移方程为

    [dp[i][j]= maxleft{dp[i][j], dp[i - 1][k] +p[i][j] - mid j - kmid ight} ]

    其中 (0leq k < n)

    但是我们需要枚举 (i, j, k),复杂度为 (O(nmk)),不能接受,因此考虑优化决策,对于状态转移方程,我们发现实际上要计算的是

    [left{egin{matrix} dp[i - 1][k] + k + p[i][j] - j, & jgeq k\ dp[i - 1][k] - k + p[i][j] + j, & j < k end{matrix} ight. ]

    对于当前决策,我们只需要分别计算出 (jgeq k) 部分的最大值和 (j < k) 部分的最大值即可

    时间复杂度 (O(nm log n)) 算法

    我们用数组 (h_1, h_2) 分别维护两个值,具体来说

    [h_1[j] = dp[i - 1][k] + k + p[i][j] - j\ h_2[j] = dp[i - 1][k] - k + p[i][j] + j\ ]

    然后用两棵红黑树维护二元组 ((j, h_1[j]), (j, h_2[j])),这样便可以在 (O(log n)) 时间计算出两段决策的最大值,在 c++ 中,我选用 set 来实现,需要重载运算符

    随着决策 (j) 指针右移,第一棵红黑树中要删去节点 ((j, h_1[j])),第二颗红黑树中要插入节点 ((j, h_2[j])),时间复杂度均为 (O(log n))

    因此总的时间复杂度为 (O(nm log n))

    // cpp
    typedef long long LL;
    struct node{
        int idx;
        LL val;
        node() {}
        node(int _idx, LL _val):
            idx(_idx), val(_val) {}
    };
    bool operator< (const node &x, const node &y) {
        if (x.val == y.val) return x.idx < y.idx;
        return x.val > y.val;
    }
    
    class Solution {
    public:
        long long maxPoints(vector<vector<int>>& a) {
            int m = a.size();
            int n = a[0].size();
            vector<vector<LL>> dp(m, vector<LL>(n));
            for (int j = 0; j < n; ++j) {
                dp[0][j] = a[0][j];
            }
            for (int i = 1; i < m; ++i) {
                vector<LL> h1(n), h2(n);
                set<node> st1, st2;
                for (int j = 0; j < n; ++j) {
                    h1[j] = dp[i - 1][j] - j;
                    h2[j] = dp[i - 1][j] + j;
                    st1.insert(node(j, h1[j]));
                }
                for (int j = 0; j < n; ++j) {
                    if (st1.size()) {
                        auto x = st1.begin();
                        dp[i][j] = max(dp[i][j], x->val + a[i][j] + j);
                    }
                    if (st2.size()) {
                        auto y = st2.begin();
                        dp[i][j] = max(dp[i][j], y->val + a[i][j] - j);
                    }
                    st1.erase(node(j, h1[j]));
                    st2.insert(node(j, h2[j]));
                    /*
                    for (int k = 0; k < n; ++k) {
                        dp[i][j] = max(dp[i][j], dp[i - 1][k] + a[i][j] - abs(j - k));
                    }
                    */
                }
            }
            LL ans = 0;
            for (int j = 0; j < n; ++j) ans = max(ans, dp[m - 1][j]);
            return ans;
        }
    };
    

    时间复杂度 (O(nm)) 算法

    (pre[j]) 维护 (maxleft{dp[i - 1][k] + kmid 0leq kleq j ight})

    (suf[j]) 维护 (maxleft{dp[i - 1][k] - kmid jleq kleq n - 1 ight})

    则状态转移方程更新为

    [dp[i][j] = maxleft{dp[i][j], maxleft{pre[j] - j, suf[j] + j ight} + p[i][j] ight} ]

    同时使用滚动数组把 (dp) 数组的第一维状态优化掉

    总的时间复杂度为 (O(nm)),空间复杂度为 (O(n))

    注意此题爆 int,不开 long long 见祖宗

    // cpp
    typedef long long LL;
    class Solution {
    public:
        long long maxPoints(vector<vector<int>>& a) {
            int m = a.size(), n = a[0].size();
            vector<LL> dp(n);
            vector<LL> pre(n), suf(n);
            for (int i = 0; i < m; ++i) {
                for (int j = 0; j < n; ++j) {
                    if (!i) dp[j] = a[i][j];
                    else {
                        LL x = max(pre[j] - j, suf[j] + j) + a[i][j];
                        dp[j] = max(dp[j], x);
                    }
                }
                pre[0] = dp[0] + 0;
                suf[n - 1] = dp[n - 1] - (n - 1);
                for (int j = 1; j < n; ++j)
                    pre[j] = max(pre[j - 1], dp[j] + j);
                for (int j = n - 2; j >= 0; --j)
                    suf[j] = max(suf[j + 1], dp[j] - j);
            }
            LL ans = *max_element(dp.begin(), dp.end());
            return ans;
        }
    };
    

    使用前后缀代替红黑树,代码更好写了,运行效率也更快

  • 相关阅读:
    软件测试(3)--coverage graph
    st_lab1
    数据结构与算法—单向链表
    数据结构与算法—顺序表
    Python的正则表达式(re包)
    Python的内置装饰器@property、@staticmethod、@classmethod
    Python的装饰器
    Python的生成器和迭代器
    Python变量的引用、拷贝和回收机制
    git常用命令总结
  • 原文地址:https://www.cnblogs.com/ChenyangXu/p/15059230.html
Copyright © 2020-2023  润新知