代码详解:2024美团春招实习笔试第一场0309,是难还是简单?

前言:

1.第一题(模拟)

2.第二题(模拟)

3.第三题(二维前缀和)

4.第四题的思维(双指针)

5.第五题难度比较大(并查集+删边+离散化)

一.小美的MT

MT 是美团的缩写,因此小美很喜欢这两个字母。

现在小美拿到了一个仅由大写字母组成字符串,她可以最多操作k次,每次可以修改任意一个字符。小美想知道,操作结束后最多共有多少个'M'和'T'字符?

输入描述

第一行输入两个正整数n,k,代表字符串长度和操作次数。第二行输入一个长度为n的、仅由大写字母组成的字符串。1<=k<=n<=10^5

输出描述

输出操作结束后最多共有多少个'M'和'T'字符。

示例 1

输入

5 2
MTUAN

输出

4

说明

修改第三个和第五个字符,形成的字符串为 MTTAM,这样共有 4 个'M'和'T'。

思路与代码

1.首先统计字符串中除了M和T以外的字符的个数,记为add。

2.操作次数为k,因此最多可以min(k,add)将个字符变为M和T

3.再加上之前的M和T字符的个数。

4.结果为n-add+min(k,add);

C++:

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n, k, sum = 0;
    cin >> n >> k;
    string s;
    cin >> s;
    int add = 0;
    for (int i = 0; i < s.size(); i++) {
        if (s[i] != 'M' && s[i] != 'T') {
            add++;
        }
    }
    sum = n - add + min(k, add);
    cout << sum << "\n";
    return 0;
}

Java:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n, k, sum = 0;
        n = scanner.nextInt();
        k = scanner.nextInt();
        String s = scanner.next();
        int add = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != 'M' && s.charAt(i) != 'T') {
                add++;
            }
        }
        sum = n - add + Math.min(k, add);
        System.out.println(sum);
    }
}

二.小美的数组询问

小美拿到了一个由正整数组成的数组,但其中有一些元素是未知的(用 0 来表示)。

现在小美想知道,如果那些未知的元素在区间[l,r]范围内随机取值的话,数组所有元素之和的最小值和最大值分别是多少?

共有q次询问。

输入描述

第一行输入两个正整数n,q,代表数组大小和询问次数。

第二行输入n个整数ai,其中如果输入ai的为 0,那么说明ai是未知的。

接下来的q行,每行输入两个正整数l,r,代表一次询问。

1<=n,q<=10^5
0<=ai<=10^9
1<=l<=r<=10^9

输出描述

输出q行,每行输出两个正整数,代表所有元素之和的最小值和最大值。

示例 1

输入

3 2
1 0 3
1 2
4 4

输出

5 6
8 8

说明

只有第二个元素是未知的。
第一次询问,数组最小的和是 1+1=3=5,最大的和是 1+2+3=6。
第二次询问,显然数组的元素和必然为 8。

思路与代码

1.循环一遍,sum记录非0的情况下的和,res统计0的数量。

2.最小值就是0的数量 * 区间左值(res*l),反之最大值就是0的数量*区间右值(res*r)。

3.答案就是最小:sum+res*l,最大:sum+res*r;

C++

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, q;
    cin >> n >> q;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    int res = 0;
    int sum = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] == 0) {
            res++;
        }
        sum += a[i];
    }
    for (int i = 0; i < q; i++) {
        int l, r;
        cin >> l >> r;
        cout << sum + res *l << " " << sum + res *r << endl;
    }
    return 0;
}

Java

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int q = sc.nextInt();

        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = sc.nextInt();
        }

        int res = 0;
        int sum = 0;
        for (int i = 0; i < n; i++) {
            if (a[i] == 0) {
                res++;
            }
            sum += a[i];
        }

        for (int i = 0; i < q; i++) {
            int l = sc.nextInt();
            int r = sc.nextInt();
            System.out.println((sum + res * l) + " " + (sum + res * r));
        }
    }
}

三.小美的平衡矩阵

小美拿到了一个n*n 的矩阵,其中每个元素是 0 或者 1。

小美认为一个矩形区域是完美的,当且仅当该区域内 0 的数量恰好等于 1 的数量。

现在,小美希望你回答有多少个i*i的完美矩形区域。你需要回答1<=i<=n的所有答案。

输入描述

第一行输入一个正整数n,代表矩阵大小。

接下来的n行,每行输入一个长度为n的01 串,用来表示矩阵。

输出描述

输出n行,第i行输出的i*i完美矩形区域的数量。

示例 1

输入

4
1010
0101
1100
0011

输出

0
7
0
1

思路与代码

二维前缀和。n<=200.所以可以n^3进行解决题目

1.枚举所有的边长i的正方形.

2.如果0和1的数量各一半,那么就是判断正方形的和是否为i*i / 2,如果是就++。

3.计算时:

第一层循环枚举矩形长度i,

第二,三层循环分别枚举矩形的左上角的端点(x,y)

对应右下角的端点则为(x+i-1,y+i-1)

C++:

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n;
char a[N][N];
int s[N][N];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> a[i][j];
        }
    }
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + (a[i][j] == '1');
    for (int i = 1; i <= n; i++) {
        int cnt = 0;
        for (int x = 1; x <= n - i + 1; x++) {
            for (int y = 1; y <= n - i + 1; y++) {
                int sum = s[x + i - 1][y + i - 1] - s[x + i - 1][y - 1] - s[x - 1][y + i - 1] + s[x - 1][y - 1];
                if (sum * 2 == i * i) {
                    cnt++;
                }
            }
        }
        cout << cnt << endl;
    }
    return 0;
}

Java:


import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = 510;
        int n = scanner.nextInt();
        char[][] a = new char[N][N];
        int[][] s = new int[N][N];

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                a[i][j] = scanner.next().charAt(0);
            }
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + (a[i][j] == '1' ? 1 : 0);
            }
        }

        for (int i = 1; i <= n; i++) {
            int cnt = 0;
            for (int x = 1; x <= n - i + 1; x++) {
                for (int y = 1; y <= n - i + 1; y++) {
                    int sum = s[x + i - 1][y + i - 1] - s[x + i - 1][y - 1] - s[x - 1][y + i - 1] + s[x - 1][y - 1];
                    if (sum * 2 == i * i) {
                        cnt++;
                    }
                }
            }
            System.out.println(cnt);
        }
    }
}

四.小美的区间删除

小美拿到了一个大小为n的数组,她希望删除一个区间后,使得剩余所有元素的乘积末尾至少有k个 0。小美想知道,一共有多少种不同的删除方案?

输入描述

第一行输入两个正整数n,k。第二行输入n个正整数ai,代表小美拿到的数组。

1<=n,k<=10^5
1<=ai<=10^9

输出描述

一个整数,代表删除的方案数。

示例 1

输入

5 2
2 5 3 4 20

输出

4

说明

第一个方案,删除[3]。
第二个方案,删除[4]。
第三个方案,删除[3,4]。
第四个方案,删除[2]。

思路与代码

2和5的因子个数。2*5=10;2^i*5^j == min(i,j)==0的数量 2*5=10

使用双指针

区间的一个长度越大,那么末尾0的个数越多,单调性,

使用双指针

1.我们首先统计整个数组的2和5的因子3数量,和每个元素对应的5的因子数量。

2.维护2个指针l,r

3.如果l,r的区间min(cnt2,cnt5)>=k,我们答案就累加l-r+1;

C++:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a2[N], a5[N];
int cnt2 = 0, cnt5 = 0;
int n, k, x;

void cnt2_cnt5_count() {
	for (int i = 0; i < n; i ++) {
		cin >> x;
		while (x % 2 == 0) {
			a2[i] ++;
			x /= 2;
			cnt2 ++;
		}
		while (x % 5 == 0) {
			a5[i] ++;
			x /= 5;
			cnt5 ++;
		}
	}
}

int main() {
	cin >> n >> k;
	cnt2_cnt5_count();
	int l = 0;
	long long res = 0;
	for (int r = 0; r < n; r ++) {
		cnt2 -= a2[r];
		cnt5 -= a5[r];
		while (min(cnt2, cnt5) < k && l <= r) {
			cnt2 += a2[l];
			cnt5 += a5[l];
			l ++;
		}
		res += (long long)(r - l + 1);
	}
	cout << res << endl;
	return 0;
}

Java:

import java.util.Scanner;

public class Main {
    static final int N = 1000010;
    static int[] a2 = new int[N];
    static int[] a5 = new int[N];
    static int cnt2 = 0, cnt5 = 0;
    static int n, k, x;

    public static void cnt2Cnt5Count() {
        Scanner input = new Scanner(System.in);
        for (int i = 0; i < n; i++) {
            x = input.nextInt();
            while (x % 2 == 0) {
                a2[i]++;
                x /= 2;
                cnt2++;
            }
            while (x % 5 == 0) {
                a5[i]++;
                x /= 5;
                cnt5++;
            }
        }
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        n = input.nextInt();
        k = input.nextInt();
        cnt2Cnt5Count();
        int l = 0;
        long res = 0;
        for (int r = 0; r < n; r++) {
            cnt2 -= a2[r];
            cnt5 -= a5[r];
            while (Math.min(cnt2, cnt5) < k && l <= r) {
                cnt2 += a2[l];
                cnt5 += a5[l];
                l++;
            }
            res += (long) (r - l + 1);
        }
        System.out.println(res);
    }
}

五.小美的朋友关系

小美认为,在人际交往中,但是随着时间的流逝,朋友的关系也是会慢慢变淡的,最终朋友关系就淡忘了。

现在初始有一些朋友关系,存在一些事件会导致两个人淡忘了他们的朋友关系。小美想知道某一时刻中,某两人是否可以通过朋友介绍互相认识?

事件共有 2 种:

1 u v:代表编号 u 的人和编号 v 的人淡忘了他们的朋友关系。

2 u v:代表小美查询编号 u 的人和编号 v 的人是否能通过朋友介绍互相认识。

注:介绍可以有多层,比如 2 号把 1 号介绍给 3 号,然后 3 号再把 1 号介绍给 4 号,这样 1 号和 4 号就认识了。

输入描述

第一行输入三个正整数n,m,q,代表总人数,初始的朋友关系数量,发生的事件数量。接下来的m行,每行输入两个正整数u,v,代表初始编号u的人和编号v的人是朋友关系。接下来的q行,每行输入三个正整数op,u,v,含义如题目描述所述。

1<=n <= 10^9
1<= m,q<= 10^5
1<= u,v<= n
1<= op <= 2
保证至少存在一次查询操作。

输出描述

对于每次 2 号操作,输出一行字符串代表查询的答案。如果编号 u 的人和编号 v 的人能通过朋友介绍互相认识,则输出"Yes"。否则输出"No"。

示例 1

输入

5 3 5
1 2
2 3
4 5
1 1 5
2 1 3
2 1 4
1 1 2
2 1 3

输出

Yes
No
No

说明

第一次事件,1 号和 5 号本来就不是朋友,所以无事发生。

第二次事件是询问,1 号和 3 号可以通过 2 号的介绍认识。

第三次事件是询问,显然 1 号和 4 号无法互相认识。

第四次事件,1 号和 2 号淡忘了。

第五次事件,此时 1 号无法再经过 2 号和 3 号互相认识了。

思路与代码

离线处理数据(map)+并查集+逆向思维。

1.离线处理,因为这里的n非常大1e9,后面是使用并查集构造n个节点,father数组肯定装不下.

2.并查集处理加边,删边转换下思路

3.如果我们已经找出所有可能要删除的边,然后假设所有边都删除了,构建一个最终的并查集(这个并查集并不影响后面的删边的情况)。

4.逆向遍历所有的q次操作,如果是查询(1操作),使用并查集直接查出即可;如果是删除(2操作),则往并查集添加边。

C++:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
unordered_map<int, int> father;

int find(int x) {
    return father[x] == x ? x : (father[x] = find(father[x]));
}

void merge(int x, int y) {
    int i = find(x), j = find(y);
    if (i != j)
        father[i] = j;
}

struct node {
    int op;
    int u;
    int v;
} A[N];

int main() {
    int n, m, p;
    cin >> n >> m >> p;
    vector<node> all(p);
    set<pair<int, int> > q;
    set<pair<int, int> > q_del;
    for (int i = 0; i < m; i ++) {
        int u, v;
        cin >> u >> v;
        father[u] = u;
        father[v] = v;
        q.insert({u, v});//初始化需要建立的边
    }
    int add = 0;
    for (int i = 0; i < p; i ++) {
        cin >> all[i].op >> all[i].u >> all[i].v;
        father[all[i].u] = all[i].u;
        father[all[i].v] = all[i].v;
        if (all[i].op == 1) {
            if (q.count({all[i].u, all[i].v}))
                q_del.insert({all[i].u, all[i].v});
            else  if ( q.count({all[i].v, all[i].u}))
                q_del.insert({all[i].v, all[i].u});
            else
                continue;
        }
        A[add++] = {all[i].op, all[i].u, all[i].v};
    }
    for (auto e : q_del) {
        int u = e.first, v = e.second;
        if (q.count({u, v}) || q.count({v, u})) {
            q.erase(e);
        }
    }
    for (auto e : q) {
        merge(e.first, e.second);
    }
    vector<string> res;
    for (int i = add - 1; i >= 0; i --) {
        if (A[i].op == 1) {
            merge(A[i].u, A[i].v);
        } else {
            if (find(A[i].u) == find(A[i].v)) {
                res.push_back("Yes");
            } else {
                res.push_back("No");
            }
        }
    }
    reverse(res.begin(), res.end());
    for (string s : res) {
        cout << s << endl;
    }
    return 0;
}

Java:

import java.util.*;

class Main {
    static HashMap<Integer, Integer> father = new HashMap<>();

    static int find(int x) {
        return father.get(x) == x ? x : find(father.get(x));
    }

    static void merge(int x, int y) {
        int i = find(x);
        int j = find(y);
        if (i != j) {
            father.put(i, j);
        }
    }

    static class Node {
        int op;
        int u;
        int v;

        Node(int op, int u, int v) {
            this.op = op;
            this.u = u;
            this.v = v;
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int p = scanner.nextInt();

        List<Node> all = new ArrayList<>();
        Set<List<Integer>> q = new HashSet<>();
        Set<List<Integer>> q_del = new HashSet<>();

        for (int i = 0; i < m; i++) {
            int u = scanner.nextInt();
            int v = scanner.nextInt();
            father.put(u, u);
            father.put(v, v);
            List<Integer> edge = new ArrayList<>();
            edge.add(u);
            edge.add(v);
            q.add(edge);
        }

        List<Node> a = new ArrayList<>();
        int add = 0;
        for (int i = 0; i < p; i++) {
            int op = scanner.nextInt();
            int u = scanner.nextInt();
            int v = scanner.nextInt();
            father.put(u, u);
            father.put(v, v);

            if (op == 1) {
                List<Integer> edge = new ArrayList<>();
                edge.add(u);
                edge.add(v);
                if (q.contains(edge)) {
                    q_del.add(edge);
                } else {
                    Collections.reverse(edge);
                    if (q.contains(edge)) {
                        q_del.add(edge);
                    } else {
                        continue;
                    }
                }
            }
            all.add(new Node(op, u, v));
        }

        for (List<Integer> e : q_del) {
            int u = e.get(0);
            int v = e.get(1);
            if (q.contains(Arrays.asList(u, v)) || q.contains(Arrays.asList(v, u))) {
                q.remove(e);
            }
        }

        for (List<Integer> e : q) {
            merge(e.get(0), e.get(1));
        }

        List<String> res = new ArrayList<>();
        for (int i = add - 1; i >= 0; i--) {
            if (a.get(i).op == 1) {
                merge(a.get(i).u, a.get(i).v);
            } else {
                if (find(a.get(i).u) == find(a.get(i).v)) {
                    res.add("Yes");
                } else {
                    res.add("No");
                }
            }
        }

        Collections.reverse(res);
        for (String s : res) {
            System.out.println(s);
        }
    }
}

最后:

觉得讲解不错的可以关注,反响不错的话,会更新此系列讲解视频,最后的最后祝大家offer满满!!!


 

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法编程张老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值