leetcode竞赛:307场周赛

链接: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];
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值