2022年5月周赛习题笔记

本文汇总了LeetCode和ACwing平台近期的算法竞赛周赛题目,包括移除数字、卡牌选取、子数组计算、字符串引力、树形结构处理、字符串处理、括号路径检查等多个算法问题,并提供了详细的解决方案和代码实现,涵盖了动态规划、贪心算法、二分查找、树的遍历等多种算法思想。
摘要由CSDN通过智能技术生成

5月1日:LeetCode第 291 场周赛

2259. 移除指定数字得到的最大结果

题目链接

要注意不能将找出来的前半部分和后半部分转换成整数去比较,数字太长了!

class Solution {
public:
    bool compare(string s1, string s2) {
        if (s1.size() > s2.size())
            return true;
        else if (s1.size() < s2.size())
            return false;
        else {
            for (int i = 0; i < s1.size(); i++) {
                if (s1[i] < s2[i])
                    return false;
                else if (s1[i] > s2[i])
                    return true;
            }
        }
        return true;
    }

    string removeDigit(string number, char digit) {
        string res = "", str = "";
        for (int i = 0; i < number.size(); i++) {
            if (number[i] == digit) {
                string s1 = "", s2 = "";
                if (i != 0)
                    s1 = number.substr(0, i);

                if (i != number.size() - 1)
                    s2 = number.substr(i + 1, number.size() - i - 1);

                str = s1 + s2;

                if (compare(str, res))
                    res = str;
            }
        }
        return res;
    }
};

2260. 必须拿起的最小连续卡牌数

题目链接

class Solution {
public:
    unordered_map<int, int> mp;
    int minimumCardPickup(vector<int>& cards) {
        int res = 1e9;
        for (int i = 0; i < cards.size(); i ++ )
        {
            if (mp.count(cards[i]))
                res = min(i - mp[cards[i]] + 1, res);
            mp[cards[i]] = i;
        }

        if (res == 1e9) return -1;
        return res;
    }
};

2261. 含最多 K 个可整除元素的子数组——待优化

题目链接

居然可以直接暴力。。。

class Solution {
public:
    int countDistinct(vector<int>& nums, int k, int p) {
        set<vector<int>> hashset;
        int n = nums.size();
        for (int i = 0; i < n; i ++ )
        {
            vector<int> tmp;
            int count = 0;
            for (int j = i; j < n; j ++ )
            {
                tmp.push_back(nums[j]);
                if (nums[j] % p == 0) count ++ ;

                if (coutn <= k) hashset.insert(tmp);
                else break;
            }
        }

        return hashset.size();
    }
};

2262. 字符串的总引力

题目链接

注意:本题是求的子串而不是子序列!

集合
f[i]:表示以s[i]结尾的子串的引力总引力

目标
求最大值

集合划分
添加s[i]对原来子串的影响

  • s[i]在之前没有出现过的话,那么对于每一个以s[i-1]结尾的子串在其后面加上s[i]后其引力值都会加1,因为由i-1个以s[i-1]结尾的子串再加上s[i]单独构成的子串,所以f[i] = f[i-1] + i - 1 + 1 = f[i-1] + i
  • 若s[i]在之前出现过,出现位置为j,那么以s[0]~s[j]结尾的子串的引力值不会改变,但是以s[j + 1]~s[i-1]结尾的子串总共i - 1 - j - 1 + 1 = i - j - 1个都会加上1,再加上s[i]单独构成的子串,f[i] = f[i-1] + i - j - 1 + 1 = f[i-1] + i - j
  • 综上,状态表示即为在这里插入代码片f[i] = f[i-1] + i - j,第一种状态就是j = 0的情况

这里需要额外使用一个数组来存储每个字符的最新出现的位置。

class Solution {
public:
    long long appealSum(string s) {
        int n = s.size();
        s = '0' + s; // 使s下标从1开始
        vector<long long> f(n + 1); // 默认初始化为0
        vector<int> pos(27); // 存储每个字符最近出现的位置

        for (int i = 1; i <= n; i ++ )
        {
            f[i] = f[i - 1] + i - pos[s[i] - 'a'];
            pos[s[i] - 'a'] = i;
        }

        long long ans = 0;
        for (auto c : f) // 方案求和
            ans += c;
        
        return ans;
    }
}; 

滚动数组优化版本,省去了一部分f空间开销(求ans的过程可以放进for里面,不用单独求)

class Solution {
public:
    long long appealSum(string s) {
        int n = s.size();
        s = '0' + s; // 使s下标从1开始
        int f[2] = {0, 0};
        vector<int> pos(27); // 存储每个字符最近出现的位置
        long long ans = 0;

        for (int i = 1; i <= n; i ++ )
        {
            f[i & 1] = f[(i - 1) & 1] + i - pos[s[i] - 'a'];
            ans += f[i & 1];  // 存储每个方案的值
            pos[s[i] - 'a'] = i;
        }

        return ans;
    }
}; 

5月7日:ACwing 第 50 场周赛

4416. 缺少的数

题目链接

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    int n;
    cin >> n;
    vector<int> a(n + 2);
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        cin >> x;
        a[x] = 1;
    }
    for (auto i = 1; i < n + 2; i ++ )
        if (a[i] == 0)
        {
            printf("%d\n", i);
            break;
        }
    return 0;
}

4417. 选区间

题目链接

#include <iostream>
#include <algorithm>

using namespace std;

const int INF = 1e9;

int main()
{
    int n, m;
    scanf("%d", &n);
    
    int a = INF, b = -INF;
    while (n -- )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        a = min(a, r);
        b = max(b, l);
    }
    
    scanf("%d", &m);
    int res = 0;
    while (m -- )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        if (b > r) res = max(res, b - r);
        if (a < l) res = max(res, l - a);
    }
    
    printf("%d\n", res);
    
    return 0;
}

4418. 选元素

题目链接

集合
f[i, j]:从前i个数中选择j个数,且选择了第i个数的所有方案的集合,其集合的和的最大值

集合划分
按照第j-1个数的选法来分类,由于要保证题目条件原序列中的每一个长度为 k 的连续子序列都至少包含一个被选中的元素。所以第j-1个数的下标范围应该为i-k, i-k+1, ... , i - 1
在这里插入图片描述
假设第j-1个数选择的是uu ∈ [i-k, i-1],因为总共选择了j个数,所以应该在第[1, i-k]之间选择j-1个数,且第j-1个数必须选择u。简单描述为:从前第1到第u个数中选择j-1个数,并且第u个数被选择,第j个数选择第i个数的所有方案中的最大值,因为所有方案都含有i,要使方案求和后值最大,就必须使从前u个数中选择j-1个数的和最大,即f[u, j - 1] + vi
在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 210;

int n, k, m;
LL f[N][N];

int main()
{
    cin >> n >> k >> m;
    
    memset(f, -0x3f, sizeof f);
    f[0][0] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        int v;
        scanf("%d", &v);
        for (int j = 1; j <= m; j ++ ) // 因为最后一个数必须需选择,所以至少有一个数
            for (int u = max(i - k, 0); u < i; u ++ )
                f[i][j] = max(f[i][j], f[u][j - 1] + v);
    }
    
        
    LL res = -1;
    for (int i = n - k + 1; i <= n; i ++ )
        res = max(res, f[i][m]);
    
    printf("%lld\n", res);
    
    return 0;
}

5月8日:LeetCode 第292场周赛

2264. 字符串中最大的 3 位相同数字

题目链接

class Solution {
public:
    string largestGoodInteger(string num) {
        string res = "";

        if (num.size() == 3 && num[0] == num[1] && num[1] == num[2])
            return num;

        for (int i = 0; i <= num.size() - 3; i ++ )
        {
            string str = "";
            if (num[i] == num[i + 1] && num[i + 2] == num[i + 1])
                str = num.substr(i, 3);
            if (str != "" && atoi(str.c_str()) >= atoi(res.c_str()))
                res = str;
        }
        return res;
    }
};

2265. 统计值等于子树平均值的节点数

题目链接

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int res = 0;

    int averageOfSubtree(TreeNode* root) {
        dfs(root);
        return res;    
    }

    // 返回以root为根节点的子树(包含root本身)值的和与总节点个数
    vector<int> dfs(TreeNode * root)
    {
        if (root->left == nullptr && root->right == nullptr)
        {
            res ++ ;
            return vector<int>{root->val, 1};
        }

        // root左右子树为空的情况:若为空值的和为0,总个数也为0
        vector<int> l = {0, 0}, r = {0, 0};
        if (root->left != nullptr) l = dfs(root->left);
        if (root->right != nullptr) r = dfs(root->right);

        int sum = l[0] + r[0] + root->val;
        int cnt = l[1] + r[1] + 1;

        res += sum / cnt == root->val ? 1 : 0;

        return vector<int>{sum, cnt};
    }
};

2266. 统计打字方案数

题目链接

class Solution {
    const int MOD = 1e9+7;
public:
    int countTexts(string pressedKeys) {
        int n = pressedKeys.size();

        // f[i]、g[i]:表示将连续相同的数字分成不同长度的方案,就相当于楼梯爬一层、两层、三层
        vector<long long> f(n + 1), g(n + 1);
        f[0] = g[0] = 1;

        for (int i = 1; i <= n; i ++ )
        {
            for (int j = 1; j <= 3 && j <= i; j ++ ) f[i] = (f[i] + f[i - j]) % MOD;
            for (int j = 1; j <= 4 && j <= i; j ++ ) g[i] = (g[i] + g[i - j]) % MOD;
        }

        int cnt = 0;
        char last = 0;
        long long ans = 1;
        for (char c : pressedKeys)
        {
            if (c != last)
            {
                if (last == '7' || last == '9') ans = ans * g[cnt] % MOD;
                else ans = ans * f[cnt] % MOD;
                cnt = 0;
                last = c;
            }
            cnt ++ ;
        }

        if (last == '7' || last == '9') ans = ans * g[cnt] % MOD;
        else ans = ans * f[cnt] % MOD;

        return ans;
    }
};

2267. 检查是否有合法括号字符串路径

题目链接

// f[i][j][k]:表示能否存在以格子(i,j)结尾,且求和值为k的括号序列
class Solution {
public:
    bool hasValidPath(vector<vector<char>>& grid) {
        if (grid[0][0] == ')') return false;

        int n = grid.size(), m = grid[0].size();
        vector<vector<vector<bool>>> f;
        for (int i = 0; i < n; i ++ )
        {
            f.push_back(vector<vector<bool>> ());
            for (int j = 0; j < m; j ++ )
                f.back().push_back(vector<bool>(n + m)); // k 最大值 为 n+m-1
        }

        // 求和过程中值非负,且最后求和值为0为合法
        f[0][0][1] = true; // 第一个必为'('
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ )
                if (i || j) // 排除第一个点(0, 0)
                {
                    int t = (grid[i][j] == '(' ? 1 : -1);
                    for (int k = 0; k < m + n; k ++ )
                    {
                        int kk = k - t; // 表示之前一步的求和
                        if (kk < 0 || kk >= m + n) continue;
                        // 保证i与j索引合法->只要有其中一条转移路径成立即可,因此用||
                        if (i) f[i][j][k] = f[i][j][k] || f[i - 1][j][kk];
                        if (j) f[i][j][k] = f[i][j][k] || f[i][j - 1][kk];
                     }
                }
        return f[n - 1][m - 1][0];
    }
};

5月14日:ACwing 第51场周赛

4419. 上车

题目链接

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int main()
{
    int n;
    cin >> n;
    
    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int x, y;
        cin >> x >> y;
        if (y - x >= 2)
            res ++ ;
    }
    cout << res << endl;
    
    return 0;
}

4420. 连通分量(求连通块!)

题目链接

这个题我比赛的时候使用朴素的BFS提交后TLE了!

求连通块的方法:

  • dfs
  • bfs
  • 并查集

比赛时的代码(后面再来优化):

#include <iostream>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010, M = N * N;

int n, m;
char g[N][N];
string res[N];
PII q[M];
bool st[N][N];

int bfs(int sx, int sy) {
    memset(st, false, sizeof st);
    memset(q, 0, sizeof q);
    int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};

    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    st[sx][sy] = true;

    int cnt = 1;
    while (hh <= tt) {
        PII t = q[hh++];

        for (int i = 0; i < 4; i++) {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a < 0 || a >= n || b < 0 || b >= m) continue;
            if (g[a][b] == '*') continue;
            if (st[a][b]) continue;
            q[++tt] = {a, b};
            cnt++;
            st[a][b] = true;
        }
    }
    return cnt;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++)
        scanf("%s", g[i]);

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
        {
            if (g[i][j] == '*')
            {
                int n = bfs(i, j) % 10;
                res[i] += to_string(n);
            }
            else res[i].push_back(g[i][j]);
        }

    for (int i = 0; i < n; i++) {
        for (auto c : res[i])
            cout << c;
        cout << endl;
    }

    return 0;
}

AC代码:并查集求连通块

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010, M = N * N;

int n, m;
int p[M], s[M]; // 并查集以及每个并查集的大小
char g[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

// 将每一个坐标对应到一个唯一整数
int get(int x, int y) {
    return x * m + y;
}

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) scanf("%s", g[i]);
    for (int i = 0; i < n * m; i++) p[i] = i, s[i] = 1; // 初始化并查集

    // 将所有连通块合并:在每一个连通块中任意找一个格子作为根结点,用它来表示该连通块
    for (int i = 0; i < n; i++)  // 枚举每一个元素
        for (int j = 0; j < m; j++)
            if (g[i][j] == '.')
                for (int k = 0; k < 4; k++) { // 枚举每一个方向
                    int x = i + dx[k], y = j + dy[k];
                    if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == '.') {
                        int a = get(i, j), b = get(x, y);
                        a = find(a), b = find(b);
                        if (a != b) { // 合并并查集
                            s[b] += s[a];
                            p[a] = b;
                        }
                    }
                }

    // 枚举每一个格子
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++)
            if (g[i][j] == '.') printf(".");
            else { // 如果是障碍物
                int fathers[4], cnt = 0; // 枚举该点周围的代表元素,存储father中,cnt用于计数
                for (int k = 0; k < 4; k++) {
                    int x = i + dx[k], y = j + dy[k];
                    if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == '.') {
                        int a = get(x, y);
                        fathers[cnt++] = find(a);
                    }
                }

                // 对当前元素周围的连通块判重,有的连通块可能是连在一起的
                int sum = 1;
                if (cnt) { // 如果至少有2个连通块
                    sort(fathers, fathers + cnt);
                    // unique是对相邻元素进行去重,将重复元素放到数组末尾,返回值是去重之后的尾地址
                    // 去重之后的尾地址 - 首地址 = 数组中不重复的元素个数
                    cnt = unique(fathers, fathers + cnt) - fathers;
                    for (int k = 0; k < cnt; k++) // 将所有不同的连通块大小求和
                        sum += s[fathers[k]];
                }

                printf("%d", sum % 10);
            }
        puts("");
    }
    
    return 0;
}

4421. 信号(贪心)

首先考虑第一个信号发射器。这个信号发射器一定要覆盖掉第一个房间,否则,就不合法。

对于第一个发射器,假设存在如下图两个信号发射器(都能覆盖到第一个房间),对于最左边的发射器的所有选择方案就可以分为两类:一类是最左边的发射器选择上边一个,一类是最左边的发射器选择下边一个。

显然选择下边一个发射器更好。因为如果选择一个上边的发射器,那么选择选择一个下边的发射器会达到同样的覆盖效果,并且所需要的发射器数量可能更少(因为覆盖范围更大)。因此每选择一个上边的方案都可以在下边找到一个可能更好的方案,所以最优解应该尽可能选择下边的方案。所以第一个发射器选择下边一个。

对于后面的发射器同理,都应该选择尽可能靠右的路由器(下面的一种方案)。在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1010;

int n, r;
int q[N], cnt; // q存储所有发射器的房间编号

int main()
{
    cin >> n >> r;
    for (int i = 1; i <= n; i ++ )
    {
        int a;
        cin >> a;
        if (a) q[cnt ++ ] = i;
    }
    
    int res = 0, last = 0; // last表示上一个覆盖的房间
    for (int i = 0; i < cnt; i ++ )
    {
        if (last >= n) break; // 房间覆盖完了
        if (q[i] - r + 1 > last + 1) // 如果当前房间的信号不能覆盖到上一个房间边界
        {
            res = -1;
            break;
        }
        
        // 寻找能覆盖掉当前房间的最远的发射器
        int j = i;
        while (j + 1 < cnt && q[j + 1] - r + 1 <= last + 1) 
            j ++ ;
        last = q[j] + r - 1;
        res ++ ;
        i = j;
    }
    
    if (last < n) res = -1;
    cout << res << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值