🍭 大家好这里是KK爱Coding ,一枚热爱算法的程序员
✨ 本系列打算持续跟新拼多多近期的春秋招笔试题汇总~
💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导
👏 感谢大家的订阅➕ 和 喜欢💗
文章目录
01.小明与K小姐的游戏
问题描述
A 小镇上有个奇特的游戏。游戏开始时,A 先生准备了一排 n n n 个物品。K 小姐和卢小姐则轮流进行操作。
首先,K 小姐可以选择移除至多 x x x 个物品。接着,卢小姐也可以选择移除剩余物品中的至多 y y y 个。
游戏的目标是,K 小姐想尽量让剩余物品的价值之和最大化,而卢小姐则想将剩余物品的价值之和最小化。假设两人都采取最优策略,请问最后剩余物品的价值之和是多少?
输入格式
第一行包含五个正整数 n , m , k , x , y n, m, k, x, y n,m,k,x,y,分别表示物品的总数、K 小姐最多可移除的物品数、卢小姐最多可移除的物品数、以及每件物品的价值上限。
第二行共 n n n 个空格分开的正整数 A 1 , A 2 , . . . , A n A_1, A_2, ... , A_n A1,A2,...,An,表示每件物品的价值。
输出格式
输出一个整数,表示最终剩余物品的价值之和。
样例输入
3 1 1 1 1
4 3 2
样例输出
1
数据范围
- 2 ≤ n ≤ 1 0 5 2 \leq n \leq 10^5 2≤n≤105
- 0 ≤ m , d ≤ n 0 \leq m, d \leq n 0≤m,d≤n
- 1 ≤ k ≤ 1 0 4 1 \leq k \leq 10^4 1≤k≤104
- 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109
- 保证 ∑ n \sum{n} ∑n 不超过 1 0 5 10^5 105
题解
本题可以转化为一个博弈问题。我们首先对物品价值进行降序排序,然后计算前缀和。
如果 K 小姐不移除任何物品,那么卢小姐为了最小化剩余物品价值之和,就需要将价值最大的 m m m 件物品的价值取负数。这时候最终价值之和为前 n − m n-m n−m 件物品的价值之和减去剩余 m m m 件物品的价值之和。
如果 K 小姐移除了 d d d 件物品,那么卢小姐为了最小化剩余物品价值之和,也需要将剩余物品中价值最大的 m m m 件物品的价值取负数。这时候最终价值之和为前 n − d − m n-d-m n−d−m 件物品的价值之和减去剩余 m m m 件物品的价值之和。
因此,我们可以枚举 K 小姐移除的物品数量 d d d,对于每个 d d d,再枚举卢小姐取负的物品数量 m m m,计算出最终的价值之和,取其中的最大值即可。
时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n)。
参考代码
- Python
from typing import List
def maxValueAfterOperations(n: int, m: int, k: int, x: int, y: int, A: List[int]) -> int:
A.sort(reverse=True)
for i in range(1, len(A)):
A[i] += A[i - 1]
ans = A[-1]
for d in range(x + 1):
for rm in range(min(y, len(A) - d)):
val = A[-1] - A[len(A) - d - rm - 1]
sub = A[len(A) - d - 1] - (0 if d == 0 else A[d - 1])
ans = max(ans, val - k * sub)
return ans
if __name__ == '__main__':
n, m, k, x, y = map(int, input().split())
A = list(map(int, input().split()))
print(maxValueAfterOperations(n, m, k, x, y, A))
- Java
import java.util.*;
class Solution {
public static long maxValueAfterOperations(int n, int m, int k, int x, int y, int[] A) {
List<Long> vals = new ArrayList<>();
for (int a : A) {
vals.add((long) a);
}
Collections.sort(vals, Collections.reverseOrder());
for (int i = 1; i < vals.size(); i++) {
vals.set(i, vals.get(i) + vals.get(i - 1));
}
long ans = vals.get(vals.size() - 1);
for (int d = 0; d <= x; d++) {
for (int rm = 0; rm <= Math.min(y, vals.size() - d - 1); rm++) {
long val = vals.get(vals.size() - 1);
long sub = (d == 0 ? 0 : vals.get(d - 1));
val -= vals.get(vals.size() - rm - d - 1) - sub;
sub = vals.get(vals.size() - rm - 1) - sub;
ans = Math.max(ans, val - k * sub);
}
}
return ans;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt(), k = sc.nextInt(), x = sc.nextInt(), y = sc.nextInt();
int[] A = new int[n];
for (int i = 0; i < n; i++) {
A[i] = sc.nextInt();
}
System.out.println(maxValueAfterOperations(n, m, k, x, y, A));
}
}
- Cpp
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
long long maxValueAfterOperations(int n, int m, int k, int x, int y, vector<int>& A) {
sort(A.begin(), A.end(), greater<int>());
vector<long long> vals(A.begin(), A.end());
for (int i = 1; i < n; i++) {
vals[i] += vals[i - 1];
}
long long ans = vals.back();
for (int d = 0; d <= x; d++) {
for (int rm = 0; rm <= min(y, n - d - 1); rm++) {
long long val = vals.back();
long long sub = (d == 0 ? 0 : vals[d - 1]);
val -= vals[n - rm - d - 1] - sub;
sub = vals[n - rm - 1] - sub;
ans = max(ans, val - k * sub);
}
}
return ans;
}
};
int main() {
int n, m, k, x, y;
cin >> n >> m >> k >> x >> y;
vector<int> A(n);
for (int i = 0; i < n; i++) {
cin >> A[i];
}
Solution sol;
cout << sol.maxValue
AfterOperations(n, m, k, x, y, A) << endl;
return 0;
}
02.LYA小姐的字符串拼接探奇
问题描述
LYA 小姐非常喜欢探索字符串的奇特性质。有一天,她想到了一种新的字符串生成方法。她有两个由 0 和 1 组成的字符串 A A A 和 B B B,其中 A A A 的长度为 m m m, B B B 的长度为 n n n( 1 ≤ n ≤ m ≤ 5000 1 \le n \le m \le 5000 1≤n≤m≤5000)。她将从 A A A 中选取一个长度为 n n n 的子串,并对这个子串的每一个字符与 B B B 中对应位置的字符进行异或操作,生成一个新的字符串。
LYA 小姐认为,如果生成的字符串所有字符的异或结果为 0 0 0,那么这个字符串就是一个"合法"的字符串。她现在想知道,对于给定的 A A A 和 B B B,一共有多少种不同的子串可以生成"合法"的字符串?需要注意的是,即使两个子串生成的字符串相同,只要子串本身不同,就应当被视为不同的子串。
输入格式
第一行包含两个正整数
m
m
m 和
n
n
n,分别表示字符串
A
A
A 和
B
B
B 的长度。
第二行是一个长度为
m
m
m 的字符串
A
A
A。
第三行是一个长度为
n
n
n 的字符串
B
B
B。
输出格式
输出一个整数,表示可以生成"合法"字符串的不同子串的个数。
样例输入
8 2
10100000
01
样例输出
2
数据范围
对于所有测试数据,都满足 1 ≤ n ≤ m ≤ 5000 1 \le n \le m \le 5000 1≤n≤m≤5000。
题解
这个问题可以使用滑动窗口的思路来解决。我们首先计算出 B B B 字符串所有字符的异或结果 c n t B cntB cntB。然后遍历 A A A 字符串,维护一个长度为 n n n 的滑动窗口,用 c n t A cntA cntA 记录该窗口内所有字符的异或结果。如果 c n t A ⊕ c n t B = 0 cntA \oplus cntB = 0 cntA⊕cntB=0,则说明该窗口对应的子串可以生成一个"合法"字符串,我们将其添加到一个集合中。由于集合的特性,相同的子串只会被添加一次。最后返回集合的大小即可。
需要注意的是,在滑动窗口的过程中,我们需要维护 c n t A cntA cntA 的值。当窗口向右滑动时,需要将最右边的字符的值异或到 c n t A cntA cntA 中;当窗口向左滑动时,需要将最左边的字符的值从 c n t A cntA cntA 中异或掉。
参考代码
- Python
def soln(m, n, A, B):
cntB = 0
for b in B:
cntB ^= int(b)
cntA = 0
window = []
ans = set()
for a in A:
window.append(a)
cntA ^= int(a)
if len(window) > n:
cntA ^= int(window.pop(0))
if len(window) == n and (cntA ^ cntB) == 0:
ans.add(''.join(window))
return len(ans)
- Java
import java.util.*;
public class Solution {
public static int soln(int m, int n, String A, String B) {
int cntB = 0;
for (char b : B.toCharArray()) {
cntB ^= (b - '0');
}
int cntA = 0;
Deque<Character> window = new LinkedList<>();
Set<String> ans = new HashSet<>();
for (char a : A.toCharArray()) {
window.addLast(a);
cntA ^= (a - '0');
if (window.size() > n) {
cntA ^= (window.removeFirst() - '0');
}
if (window.size() == n && (cntA ^ cntB) == 0) {
ans.add(window.stream().map(Object::toString).collect(Collectors.joining()));
}
}
return ans.size();
}
}
- Cpp
#include <iostream>
#include <string>
#include <unordered_set>
#include <deque>
using namespace std;
int soln(int m, int n, string A, string B) {
int cntB = 0;
for (char b : B) {
cntB ^= (b - '0');
}
int cntA = 0;
deque<char> window;
unordered_set<string> ans;
for (char a : A) {
window.push_back(a);
cntA ^= (a - '0');
if (window.size() > n) {
cntA ^= (window.front() - '0');
window.pop_front();
}
if (window.size() == n && (cntA ^ cntB) == 0) {
ans.insert(string(window.begin(), window.end()));
}
}
return ans.size();
}
03.热气球俱乐部的路径探索
问题描述
LYA 小姐是一家热气球俱乐部的会员。这个俱乐部有 n n n 个起飞点,通过 m m m 条单向航线相连。热气球只能沿着单向航线飞行,不能返回起飞点。也就是说, n n n 个起飞点组成了一张有向无环图。
如果从某个起飞点 u u u 出发,可以到达所有其他起飞点,或者所有其他起飞点都可以到达 u u u,那么就认为 u u u 是一个"超级起飞点"。LYA 小姐想知道,在这个俱乐部中一共有多少个"超级起飞点"?
输入格式
第一行包含两个正整数 n n n 和 m m m,分别表示起飞点的个数和单向航线的条数,其中 2 ≤ n ≤ 3 × 1 0 5 2 \le n \le 3 \times 10^5 2≤n≤3×105, 1 ≤ m < 3 × 1 0 5 1 \le m < 3 \times 10^5 1≤m<3×105。
接下来的 m m m 行,每行包含两个正整数 u u u 和 v v v ( 1 ≤ u , v ≤ n , u ≠ v 1 \le u, v \le n, u \neq v 1≤u,v≤n,u=v),表示存在一条从起飞点 u u u 到起飞点 v v v 的单向航线。
输出格式
输出一个整数,表示"超级起飞点"的个数。
样例输入
7 7
1 2
2 3
3 4
4 7
2 5
5 4
6 4
样例输出
2
数据范围
对于所有测试数据,都满足 2 ≤ n ≤ 3 × 1 0 5 , 1 ≤ m < 3 × 1 0 5 2 \le n \le 3 \times 10^5,1 \le m < 3 \times 10^5 2≤n≤3×105,1≤m<3×105。
题解
要解决这个问题,我们可以先对有向图进行拓扑排序,得到一个点的拓扑序。然后计算每个点的最早开始时间和最晚开始时间。超级起飞点必须满足最早开始时间和最晚开始时间相等。
我们按照最晚开始时间从大到小、最早开始时间从小到大的顺序遍历所有点。对于每个点,如果它满足最早开始时间等于最晚开始时间,那么它就是一个候选的超级起飞点。但是,如果存在多个点的最早开始时间和最晚开始时间都相等,那么我们只计算第一个点,因为后面的点都在第一个点的支配范围内。
另外,如果一个点的最早开始时间等于某个已经计算过的超级起飞点的最早开始时间,那么这个点也不是超级起飞点,因为它在那个超级起飞点的支配范围内。
通过这种方式,我们可以正确地计算出所有的超级起飞点。
参考代码
- Python
from collections import deque
def soln(n, m, edges):
adj = [[] for _ in range(n)]
for u, v in edges:
u -= 1
v -= 1
adj[u].append(v)
# 拓扑排序
ind = [0] * n
for u in range(n):
for v in adj[u]:
ind[v] += 1
q = deque([u for u in range(n) if ind[u] == 0])
order = []
while q:
u = q.popleft()
order.append(u)
for v in adj[u]:
ind[v] -= 1
if ind[v] == 0:
q.append(v)
# 计算最早开始时间和最晚开始时间
min_time = [0] * n
max_time = [0] * n
for u in order:
for v in adj[u]:
min_time[v] = max(min_time[v], min_time[u] + 1)
max_time[v] = max(max_time[v], max_time[u] + 1)
# 找出超级起飞点
super_points = []
min_start = float('inf')
for u in sorted(range(n), key=lambda x: (-max_time[x], min_time[x])):
if min_time[u] == max_time[u]:
if min_time[u] >= min_start:
super_points.append(u)
min_start = min_time[u]
else:
continue
else:
min_start = min(min_start, min_time[u])
return len(super_points)
04.K小姐的字符串操作
问题描述
K小姐有一串由小写字母组成的字符串,她可以对这个字符串进行多次操作。每次操作,她都可以删除字符串中的一个回文子串(包括长度为 1 1 1 的单个字母)。删除后,剩余的字符串部分会自动拼接在一起。K小姐想知道,她最少需要进行多少次操作,才能将整个字符串删除干净。
输入格式
第一行包含一个正整数 T ( 1 ≤ T ≤ 20 ) T\ (1 \le T \le 20) T (1≤T≤20),表示测试用例的数目。
每个测试用例占一行,给出一个仅由小写字母组成的字符串,字符串长度 n ( 1 ≤ n ≤ 500 ) n\ (1 \le n \le 500) n (1≤n≤500),并且所有字符串的总长度不超过 3000 3000 3000。
输出格式
对于每个测试用例,输出一个整数,表示K小姐最少需要进行多少次操作,才能将给定的字符串删除干净。
样例输入
3
mwapd
tvuvv
yxxml
样例输出
5
3
4
数据范围
对于所有测试用例,满足:
- 1 ≤ T ≤ 20 1 \le T \le 20 1≤T≤20
- 1 ≤ n ≤ 500 1 \le n \le 500 1≤n≤500
- ∑ n ≤ 3000 \sum n \le 3000 ∑n≤3000
题解
我们可以使用动态规划的方法来解决这个问题。定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示将子串 s [ i . . j ] s[i..j] s[i..j] 删除需要的最小操作次数。
初始时,对于任意长度为 1 1 1 的子串 s [ i . . i ] s[i..i] s[i..i],我们有 d p [ i ] [ i ] = 1 dp[i][i] = 1 dp[i][i]=1。
对于长度大于 1 1 1 的子串 s [ i . . j ] s[i..j] s[i..j],我们分两种情况讨论:
-
如果 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j],那么 s [ i . . j ] s[i..j] s[i..j] 就是一个回文串,我们可以一次性删除它,因此 d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] dp[i][j] = dp[i+1][j-1] dp[i][j]=dp[i+1][j−1]。
-
如果 s [ i ] ≠ s [ j ] s[i] \neq s[j] s[i]=s[j],那么我们需要枚举一个分割点 k k k,分别删除 s [ i . . k ] s[i..k] s[i..k] 和 s [ k + 1.. j ] s[k+1..j] s[k+1..j],最后取最小值,即 d p [ i ] [ j ] = min i ≤ k < j { d p [ i ] [ k ] + d p [ k + 1 ] [ j ] } dp[i][j] = \min\limits_{i \le k < j}\{dp[i][k] + dp[k+1][j]\} dp[i][j]=i≤k<jmin{dp[i][k]+dp[k+1][j]}。
最终的答案就是 d p [ 0 ] [ n − 1 ] dp[0][n-1] dp[0][n−1],表示删除整个字符串 s s s 需要的最小操作次数。
参考代码
- Python
def soln(s):
n = len(s)
dp = [[float('inf')] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
for l in range(2, n + 1):
for i in range(n - l + 1):
j = i + l - 1
if s[i] == s[j]:
dp[i][j] = dp[i + 1][j - 1]
else:
for k in range(i, j):
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j])
return dp[0][n - 1]
t = int(input())
for _ in range(t):
s = input()
print(soln(s))
- Java
import java.util.*;
public class Main {
public static int soln(String s) {
int n = s.length();
int[][] dp = new int[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE);
}
for (int i = 0; i < n; i++) {
dp[i][i] = 1;
}
for (int l = 2; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
int j = i + l - 1;
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i + 1][j - 1];
} else {
for (int k = i; k < j; k++) {
dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k + 1][j]);
}
}
}
}
return dp[0][n - 1];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
while (t-- > 0) {
String s = sc.next();
System.out.println(soln(s));
}
}
}
- Cpp
#include <bits/stdc++.h>
using namespace std;
int soln(string s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n, INT_MAX));
for (int i = 0; i < n; i++) {
dp[i][i] = 1;
}
for (int l = 2; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
int j = i + l - 1;
if (s[i] == s[j]) {
dp[i][j] = dp[i + 1][j - 1];
} else {
for (int k = i; k < j; k++) {
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
}
}
}
}
return dp[0][n - 1];
}
int main() {
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
cout << soln(s) << endl;
}
return 0;
}
写在最后
📧 KK这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 KK领取~