🍭 大家好这里是KK爱Coding ,一枚热爱算法的程序员
✨ 本系列打算持续跟新淘天近期的春秋招笔试题汇总~
💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导
👏 感谢大家的订阅➕ 和 喜欢💗
📧 KK这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 KK领取,会在飞书进行同步的跟新。
文章目录
01.卢小姐的幸运数字
问题描述
卢小姐最近在研究一种幸运数字:任何一个整数,只要它由其他整数拼接而成,并且这个拼接后的整数能被 3 整除,她就认为它是幸运的。现在,卢小姐手头有一个整数序列,她想知道序列中某些特定区间内的数字拼接起来后是否构成了她的幸运数字。
输入格式
第一行包含两个整数 n , q n, q n,q,表示整数序列的长度和卢小姐拟进行的询问次数。
第二行包含 n n n 个整数,表示整数序列 a i a_i ai。
接下来的 q q q 行,每行包含两个整数,表示卢小姐询问的区间 [ l , r ] [l, r] [l,r]。
输出格式
对于每个询问,如果区间内的整数拼接起来是 3 的倍数,则输出 YES
,否则输出 NO
。
样例输入
3 2
11 45 14
1 3
2 2
样例输出
NO
YES
数据范围
对于所有测试数据,满足 1 ≤ n , q ≤ 1 0 5 1 \leq n, q \leq 10^5 1≤n,q≤105 和 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109。
题解
卢小姐的问题可以通过计算区间内所有数的和来简化。由于一个数字如果要被 3 整除,那么该数字的每一位数加起来的和也必须能被 3 整除。我们可以先计算出整数序列的前缀和,然后通过前缀和来快速得到任意区间内所有数的和,从而判断该区间内的数拼接起来是否能被 3 整除。
参考代码
- Python
n, q = map(int, input().split())
a = list(map(int, input().split()))
prefix_sum = [0] * (n + 1)
for i in range(1, n + 1):
prefix_sum[i] = prefix_sum[i - 1] + a[i - 1] % 3
for _ in range(q):
l, r = map(int, input().split())
if (prefix_sum[r] - prefix_sum[l - 1]) % 3 == 0:
print("YES")
else:
print("NO")
- Java
import java.io.*;
import java.util.*;
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 + 1];
int[] prefixSum = new int[n + 1];
for (int i = 1; i <= n; i++) {
a[i] = sc.nextInt();
prefixSum[i] = prefixSum[i - 1] + a[i] % 3;
}
for (int i = 0; i < q; i++) {
int l = sc.nextInt();
int r = sc.nextInt();
if ((prefixSum[r] - prefixSum[l - 1]) % 3 == 0) {
System.out.println("YES");
} else {
System.out.println("NO");
}
}
}
}
- Cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, q;
cin >> n >> q;
vector<int> a(n + 1), prefixSum(n + 1, 0);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
prefixSum[i] = prefixSum[i - 1] + a[i] % 3;
}
while (q--) {
int l, r;
cin >> l >> r;
if ((prefixSum[r] - prefixSum[l - 1]) % 3 == 0) {
cout << "YES\n";
}
else
cout << "NO\n";
}
}
02.灯泡大作战
问题描述
K小姐有一条由 n n n 个灯泡组成的灯带,一开始所有灯泡都是熄灭的。每个灯泡都有自己的颜色,用一个整数表示。K小姐可以进行以下两种操作之一:
- 选择一个熄灭的灯泡,将其点亮。
- 选择一个区间 [ l , r ] [l, r] [l,r],如果区间两端的灯泡颜色不同且都是熄灭的,那么可以将区间内所有灯泡点亮。
K小姐想知道,最少需要多少次操作才能将所有灯泡点亮?
输入格式
第一行包含一个正整数 T T T,表示测试数据的组数。
接下来对于每组测试数据:
第一行包含一个正整数 n n n,表示灯带的长度。
第二行包含 n n n 个空格分开的正整数 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,…,an,表示每个灯泡的颜色。
输出格式
对于每组测试数据,输出一行一个整数,表示将所有灯泡点亮的最少操作次数。
样例输入
2
3
1 2 1
2
1 1
样例输出
2
2
数据范围
- 1 ≤ T ≤ 1 0 4 1 \leq T \leq 10^4 1≤T≤104
- 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1≤n≤2×105
- 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109
题解
本题可以使用动态规划来求解。设 f [ i ] f[i] f[i] 表示将前 i i i 个灯泡点亮的最少操作次数。对于第 i i i 个灯泡,我们可以考虑两种情况:
- 单独将第 i i i 个灯泡点亮,此时操作次数为 f [ i − 1 ] + 1 f[i-1] + 1 f[i−1]+1。
- 找到一个位置 j < i j < i j<i,使得 a [ j ] ≠ a [ i ] a[j] \neq a[i] a[j]=a[i],将区间 [ j + 1 , i ] [j+1, i] [j+1,i] 内的灯泡全部点亮,此时操作次数为 f [ j ] + 1 f[j] + 1 f[j]+1。
综合以上两种情况,我们可以得到状态转移方程:
f [ i ] = min { f [ i − 1 ] + 1 , min j < i , a [ j ] ≠ a [ i ] { f [ j ] + 1 } } f[i] = \min\{f[i-1] + 1, \min_{j < i, a[j] \neq a[i]}\{f[j] + 1\}\} f[i]=min{f[i−1]+1,j<i,a[j]=a[i]min{f[j]+1}}
最终答案即为 f [ n ] f[n] f[n]。
为了优化第二种情况的转移,我们可以用一个哈希表来维护每种颜色最后一次出现的位置对应的最小操作次数。这样可以将时间复杂度降低到 O ( n ) O(n) O(n)。
参考代码
- Python
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
if a[0] != a[-1]:
print(1)
continue
f = [0] * (n + 1)
m = {a[0]: 0}
for i in range(1, n + 1):
f[i] = 1 + f[i - 1]
for v, min_f in m.items():
if v != a[i - 1]:
f[i] = min(f[i], 1 + min_f)
if a[i - 1] not in m or f[i - 1] < m[a[i - 1]]:
m[a[i - 1]] = f[i - 1]
print(f[n])
- Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int t = scanner.nextInt();
while (t-- > 0) {
int n = scanner.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
if (a[0] != a[n - 1]) {
System.out.println(1);
continue;
}
int[] f = new int[n + 1];
Map<Integer, Integer> m = new HashMap<>();
m.put(a[0], 0);
for (int i = 1; i <= n; i++) {
f[i] = 1 + f[i - 1];
for (Map.Entry<Integer, Integer> entry : m.entrySet()) {
int v = entry.getKey();
int min_f = entry.getValue();
if (v != a[i - 1]) {
f[i] = Math.min(f[i], 1 + min_f);
}
}
if (!m.containsKey(a[i - 1]) || f[i - 1] < m.get(a[i - 1])) {
m.put(a[i - 1], f[i - 1]);
}
}
System.out.println(f[n]);
}
}
}
- Cpp
#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
if (a[0] != a[n - 1]) {
cout << 1 << endl;
continue;
}
vector<int> f(n + 1, 0);
map<int, int> m;
m[a[0]] = 0;
for (int i = 1; i <= n; ++i) {
f[i] = 1 + f[i - 1];
for (auto &[v, min_f] : m) {
if (v != a[i - 1]) {
f[i] = min(f[i], 1 + min_f);
}
}
if (m.find(a[i - 1]) == m.end() || f[i - 1] < m[a[i - 1]]) {
m[a[i - 1]] = f[i - 1];
}
}
cout << f[n] << endl;
}
return 0;
}
03.K小姐的区间问题
问题描述
K小姐有一个长度为 n n n 的数组 n u m s nums nums,她定义函数 f ( l e f t , r i g h t , v a l ) f(left, right, val) f(left,right,val) 表示在区间 [ l e f t , r i g h t ] [left, right] [left,right] 内数字 v a l val val 出现的次数。
现在K小姐想知道有多少对 ( i , j ) (i, j) (i,j) 满足 i < j i < j i<j 且 f ( 1 , i , n u m s [ i ] ) > f ( j , n , n u m s [ j ] ) f(1, i, nums[i]) > f(j, n, nums[j]) f(1,i,nums[i])>f(j,n,nums[j])。
输入格式
第一行包含一个正整数 n n n,表示数组 n u m s nums nums 的长度。
第二行包含 n n n 个用空格分开的整数 n u m s [ 1 ] , n u m s [ 2 ] , … , n u m s [ n ] nums[1], nums[2], \dots, nums[n] nums[1],nums[2],…,nums[n],表示数组 n u m s nums nums 的元素。
输出格式
输出一个整数,表示满足条件的 ( i , j ) (i, j) (i,j) 对数。
样例输入
6
1 2 1 2 2 1
样例输出
5
样例说明
存在以下五对 ( i , j ) (i, j) (i,j) 满足条件: ( 3 , 5 ) (3, 5) (3,5), ( 3 , 6 ) (3, 6) (3,6), ( 4 , 5 ) (4, 5) (4,5), ( 4 , 6 ) (4, 6) (4,6), ( 5 , 6 ) (5, 6) (5,6)。
数据范围
- 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105
- 1 ≤ n u m s [ i ] ≤ 1 0 9 1 \leq nums[i] \leq 10^9 1≤nums[i]≤109
题解
我们可以使用树状数组和哈希表来优化。具体做法如下:
- 从后往前遍历数组 n u m s nums nums,用哈希表 c o u n t e r R i g h t counterRight counterRight 统计每个数字在区间 [ j , n ] [j, n] [j,n] 内出现的次数,同时将这些次数信息插入树状数组中。
- 从前往后遍历数组 n u m s nums nums,对于每个位置 i i i,我们需要查询在区间 [ i + 1 , n ] [i+1, n] [i+1,n] 内有多少个数字 v a l val val 满足 f ( i + 1 , n , v a l ) < f ( 1 , i , n u m s [ i ] ) f(i+1, n, val) < f(1, i, nums[i]) f(i+1,n,val)<f(1,i,nums[i])。这可以通过查询树状数组中小于 c o u n t e r L e f t [ n u m s [ i ] ] counterLeft[nums[i]] counterLeft[nums[i]] 的前缀和来实现。
- 在统计完 i i i 位置后,我们需要将 c o u n t e r R i g h t [ n u m s [ i + 1 ] ] counterRight[nums[i+1]] counterRight[nums[i+1]] 从树状数组中删除,以保证后续查询的正确性。
时间复杂度为 O ( n log n ) O(n \log n) O(nlogn),空间复杂度为 O ( n ) O(n) O(n)。
参考代码
- Python
from collections import defaultdict
class FenwickTree:
def __init__(self, n):
self.MXN = n + 2
self.tree = [0] * self.MXN
def lowbit(self, x):
return x & (-x)
def update(self, index, x):
i = index + 1
while i < self.MXN:
self.tree[i] += x
i += self.lowbit(i)
def queryPre(self, n):
ans = 0
while n:
ans += self.tree[n]
n -= self.lowbit(n)
return ans
def query(self, a, b):
return self.queryPre(b + 1) - self.queryPre(a)
def solution():
n = int(input())
nums = list(map(int, input().split()))
fenwick_tree = FenwickTree(n)
counterRight = defaultdict(int)
for i in range(n - 1, 1, -1):
counterRight[nums[i]] += 1
fenwick_tree.update(counterRight[nums[i]], 1)
ans = 0
counterLeft = defaultdict(int)
counterLeft[nums[0]] += 1
for i, val in enumerate(nums[1:n-1],1):
counterLeft[val] += 1
ans += fenwick_tree.queryPre(counterLeft[val])
fenwick_tree.update(counterRight[nums[i + 1]], -1)
counterRight[nums[i + 1]] -= 1
print(ans)
if __name__ == "__main__":
solution()
- Java
import java.util.*;
class FenwickTree {
private int MXN;
private int[] tree;
private int lowbit(int x) {
return x & (-x);
}
public FenwickTree(int n) {
MXN = n + 2;
tree = new int[MXN];
}
public void update(int index, int x) {
int i = index + 1;
while (i < MXN) {
tree[i] += x;
i += lowbit(i);
}
}
public int queryPre(int n) {
int ans = 0;
while (n > 0) {
ans += tree[n];
n -= lowbit(n);
}
return ans;
}
public int query(int a, int b) {
return queryPre(b + 1) - queryPre(a);
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
}
FenwickTree fenwickTree = new FenwickTree(n);
Map<Integer, Integer> counterRight = new HashMap<>();
for (int i = n - 1; i > 1; i--) {
counterRight.put(nums[i], counterRight.getOrDefault(nums[i], 0) + 1);
fenwickTree.update(counterRight.get(nums[i]), 1);
}
int ans = 0;
Map<Integer, Integer> counterLeft = new HashMap<>();
counterLeft.put(nums[0], 1);
for (int i = 1; i < n - 1; i++) {
int val = nums[i];
counterLeft.put(val, counterLeft.getOrDefault(val, 0) + 1);
ans += fenwickTree.queryPre(counterLeft.get(val));
fenwickTree.update(counterRight.get(nums[i + 1]), -1);
counterRight.put(nums[i + 1], counterRight.get(nums[i + 1]) - 1);
}
System.out.println(ans);
}
}
- Cpp
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
class FenwickTree {
private:
int MXN;
vector<int> tree;
int lowbit(int x) {
return x & (-x);
}
public:
FenwickTree(int n) : MXN(n + 2), tree(n + 2, 0) {}
void update(int index, int x) {
int i = index + 1;
while (i < MXN) {
tree[i] += x;
i += lowbit(i);
}
}
int queryPre(int n) {
int ans = 0;
while (n) {
ans += tree[n];
n -= lowbit(n);
}
return ans;
}
int query(int a, int b) {
return queryPre(b + 1) - queryPre(a);
}
};
void solution() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
FenwickTree fenwick_tree(n);
unordered_map<int, int> counterRight;
for (int i = n - 1; i > 1; i--) {
counterRight[nums[i]]++;
fenwick_tree.update(counterRight[nums[i]], 1);
}
int ans = 0;
unordered_map<int, int> counterLeft;
counterLeft[nums[0]]++;
for (int i = 1; i < n - 1; i++) {
int val = nums[i];
counterLeft[val]++;
ans += fenwick_tree.queryPre(counterLeft[val]);
fenwick_tree.update(counterRight[nums[i + 1]], -1);
counterRight[nums[i + 1]]--;
}
cout << ans << endl;
}
int main() {
solution();
return 0;
}
写在最后
📧 KK这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 KK领取,会在飞书进行同步的跟新。