恭喜发现宝藏!搜索公众号【TechGuide】回复公司名,解锁更多新鲜好文和互联网大厂的笔经面经。
作者@TechGuide【全网同名】
订阅专栏: 【专享版】2024最新大厂笔试真题解析,错过必后悔的宝藏资源!
第一题:消灭怪物得最高分
题目描述
一款2D游戏,你作为玩家可以释放一个技能,从而摧毁所选中的释放位置AABB[W,H]范围内的所有小怪。现在地图上有N(N ≤ 10000)个怪物,用整数Xi,Yi(其值在[0,4000])表示怪物在地图上的位置,以及该怪物被摧毁可获得对应的分数Vi。将作用范围为[W,H]的技能作用在不同位置,可以获得不同的分数值,请设计程序计算最大的分数值Vm。
输入描述
第一行3个正整数为怪物数目N和AABB的长度W、宽度H
接下来N行每行3个正整数,分别为Xi、Yi、Vi
输出描述
释放该技能摧毁掉地图上怪物可获得的最大分数
样例
输入
10 21 21
264 107 123
55 337 226
247 358 376
292 100 198
227 228 215
48 144 195
219 306 183
85 342 265
282 214 363
114 373 286
输出
376
思路
遍及即可,注意前缀和小技巧,遍历二维数组,把每个位置的值改为该位置及其左上角区域内所有元素的和,方便后续的区域分数计算。之后,计算所有可能的技能释放位置的分数。算法时间复杂度为O(N + MAX_SIZE ^ 2)级别的,其中N为怪物数目,MX为地图大小。因为遍历所有怪物以及计算前缀和都是O(n)时间复杂度,而遍历所有可能的技能释放位置需要MX^2。
代码
Java版本
import java.util.Scanner;
public class Main {
public static int calculateMaxScore(int numberOfMonsters, int lengthW, int widthH, int[][] monsters) {
final int MAX_VALUE = 4005;
int[][] prefixSums = new int[MAX_VALUE][MAX_VALUE];
// 填充分数
for (int[] monster : monsters) {
int x = monster[0];
int y = monster[1];
int score = monster[2];
prefixSums[x][y] += score;
}
// 计算二维前缀和
for (int i = 1; i < MAX_VALUE; i++) {
for (int j = 1; j < MAX_VALUE; j++) {
prefixSums[i][j] += prefixSums[i - 1][j] + prefixSums[i][j - 1] - prefixSums[i - 1][j - 1];
}
}
// 遍历,计算最大值
int maxScore = 0;
for (int i = lengthW; i < MAX_VALUE; i++) {
for (int j = widthH; j < MAX_VALUE; j++) {
// 计算当前位置的分数
int currentScore = prefixSums[i][j] - prefixSums[i - lengthW][j] - prefixSums[i][j - widthH] + prefixSums[i - lengthW][j - widthH];
maxScore = Math.max(maxScore, currentScore);
}
}
return maxScore;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int numberOfMonsters = scanner.nextInt();
int lengthW = scanner.nextInt();
int widthH = scanner.nextInt();
int[][] monsters = new int[numberOfMonsters][3];
for (int i = 0; i < numberOfMonsters; i++) {
for (int j = 0; j < 3; j++) {
monsters[i][j] = scanner.nextInt();
}
}
int maxScore = calculateMaxScore(numberOfMonsters, lengthW, widthH, monsters);
System.out.println(maxScore);
}
}
// vx公众号关注TechGuide,专业生产offer收割机,代码可能需要少量调试。
CPP版本
#include <iostream>
#include <vector>
using namespace std;
int maxScore(int numberOfMonsters, int lengthW, int widthH, vector<vector<int>>& monsters) {
const int MAX_VALUE = 4005;
vector<vector<int>> prefixSums(MAX_VALUE, vector<int>(MAX_VALUE, 0));
for (auto& monster : monsters) {
int x = monster[0];
int y = monster[1];
int score = monster[2];
prefixSums[x][y] += score;
}
for (int i = 1; i < MAX_VALUE; i++) {
for (int j = 1; j < MAX_VALUE; j++) {
prefixSums[i][j] += prefixSums[i - 1][j] + prefixSums[i][j - 1] - prefixSums[i - 1][j - 1];
}
}
int maxScore = 0;
for (int i = lengthW; i < MAX_VALUE; i++) {
for (int j = widthH; j < MAX_VALUE; j++) {
int currentScore = prefixSums[i][j] - prefixSums[i - lengthW][j] - prefixSums[i][j - widthH] + prefixSums[i - lengthW][j - widthH];
maxScore = max(maxScore, currentScore);
}
}
return maxScore;
}
int main() {
int numberOfMonsters, lengthW, widthH;
cin >> numberOfMonsters >> lengthW >> widthH;
vector<vector<int>> monsters(numberOfMonsters, vector<int>(3));
for (int i = 0; i < numberOfMonsters; i++) {
for (int j = 0; j < 3; j++) {
cin >> monsters[i][j];
}
}
int maxScoreValue = maxScore(numberOfMonsters, lengthW, widthH, monsters);
cout << maxScoreValue << endl;
return 0;
}
// vx公众号关注TechGuide,专业生产offer收割机,代码可能需要少量调试。
第二题:圆形周长
题目描述
游戏的UI渲染中,表现为若干矩形区域的多层级覆盖绘制操作。每个UI渲染区域都是一个矩形,并且都是和屏幕边框平行或垂直的。每个渲染的矩形区域也可以部分或全部被其他区域覆盖。所有矩形并集的边界的长度称为周长。
输入描述
第一行输入数组的长度n(n范围在0到5000)
接下来每行输入矩形的左下和右上的坐标点x1,y1,x2,y2
输出描述
并集的周长
样例
输入
20
0 0 20 20
5 5 15 15
8 8 12 12
-5 -5 25 25
30 0 40 20
35 5 45 15
38 8 42 12
25 -5 35 25
0 25 20 35
5 30 15 40
8 33 12 37
-5 20 25 40
30 25 40 35
35 30 45 40
38 33 42 37
25 20 35 40
15 15 25 25
18 18 22 22
10 10 30 30
-10 -10 50 50
输出
240
思路
扫描线,结合线段树。先用数组定义好左右边界、覆盖数量、区间长度等,之后模拟一根水平扫描线从下往上扫,因为我们只关心并集的最外轮廓,所以其实是忽略重叠区域,只关心入边和出边,没扫过一个矩形,就更新线段树维护的覆盖数量,更新区间左右边界和长度,注意对每个矩形处理过程,更新线段树并计算相邻矩形之间的垂直方向上的覆盖长度,本体难度较大,不知道为什么安排在第二题,可以结合代码理解。
代码
Java版本
import java.util.*;
public class Main {
static final int MXN = 5001;
static int[] leftBound = new int[MXN * 4];
static int[] rightBound = new int[MXN * 4];
static int[] countTree = new int[MXN * 4];
static int[] counts = new int[MXN * 4];
static int[] lengthTree = new int[MXN * 4];
// 更新线段树节点的信息
static void pushUp(int rt, int l, int r) {
if (counts[rt] != 0) {
leftBound[rt] = rightBound[rt] = 1;
lengthTree[rt] = r - l + 1;
countTree[rt] = 2;
} else if (l == r) {
lengthTree[rt] = countTree[rt] = leftBound[rt] = rightBound[rt] = 0;
} else {
leftBound[rt] = leftBound[rt << 1];
rightBound[rt] = rightBound[(rt << 1) | 1];
lengthTree[rt] = lengthTree[rt << 1] + lengthTree[(rt << 1) | 1];
countTree[rt] = countTree[rt << 1] + countTree[(rt << 1) | 1];
if (leftBound[(rt << 1) | 1] != 0 && rightBound[rt << 1] != 0) {
countTree[rt] -= 2;
}
}
}
// 更新线段树的节点
static void update(int L, int R, int c, int l, int r, int rt) {
if (L <= l && r <= R) {
counts[rt] += c;
pushUp(rt, l, r);
return;
}
int m = (l + r) >> 1;
if (L <= m) update(L, R, c, l, m, rt << 1);
if (R > m) update(L, R, c, m + 1, r, (rt << 1) | 1);
pushUp(rt, l, r);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
List<int[]> rectangles = new ArrayList<>();
int lBound = 5010, rBound = -1;
// 输入矩形信息并确定整体左右边界
for (int i = 0; i < n; i++) {
int[] rect = new int[4];
for (int j = 0; j < 4; j++) {
rect[j] = scanner.nextInt();
}
lBound = Math.min(lBound, rect[0]);
rBound = Math.max(rBound, rect[2]);
rectangles.add(new int[]{rect[0], rect[2], rect[1], 1});
rectangles.add(new int[]{rect[0], rect[2], rect[3], -1});
}
// 按照矩形的上边界进行排序
rectangles.sort(Comparator.comparingInt(rect -> rect[2]));
int ans = 0, last = 0;
// 处理矩形并计算周长
for (int i = 0; i < rectangles.size(); i++) {
if (rectangles.get(i)[0] < rectangles.get(i)[1]) {
update(rectangles.get(i)[0], rectangles.get(i)[1] - 1, rectangles.get(i)[3], lBound, rBound - 1, 1);
}
if (i + 1 < rectangles.size()) {
ans += countTree[1] * (rectangles.get(i + 1)[2] - rectangles.get(i)[2]);
}
ans += Math.abs(lengthTree[1] - last);
last = lengthTree[1];
}
System.out.println(ans);
}
}
// vx公众号关注TechGuide,专业生产offer收割机,代码可能需要少量调试。
CPP版本
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MXN = 5001;
int leftBound[MXN * 4], rightBound[MXN * 4], countTree[MXN * 4], counts[MXN * 4], lengthTree[MXN * 4];
// 更新线段树节点的信息
void pushUp(int rt, int l, int r) {
if (counts[rt] != 0) {
leftBound[rt] = rightBound[rt] = 1;
lengthTree[rt] = r - l + 1;
countTree[rt] = 2;
} else if (l == r) {
lengthTree[rt] = countTree[rt] = leftBound[rt] = rightBound[rt] = 0;
} else {
leftBound[rt] = leftBound[rt << 1];
rightBound[rt] = rightBound[(rt << 1) | 1];
lengthTree[rt] = lengthTree[rt << 1] + lengthTree[(rt << 1) | 1];
countTree[rt] = countTree[rt << 1] + countTree[(rt << 1) | 1];
if (leftBound[(rt << 1) | 1] != 0 && rightBound[rt << 1] != 0) {
countTree[rt] -= 2;
}
}
}
// 更新线段树的节点
void update(int L, int R, int c, int l, int r, int rt) {
if (L <= l && r <= R) {
counts[rt] += c;
pushUp(rt, l, r);
return;
}
int m = (l + r) >> 1;
if (L <= m) update(L, R, c, l, m, rt << 1);
if (R > m) update(L, R, c, m + 1, r, (rt << 1) | 1);
pushUp(rt, l, r);
}
int main() {
int n;
cin >> n;
vector<vector<int>> rectangles;
int lBound = 5010, rBound = -1;
// 输入矩形信息并确定整体左右边界
for (int i = 0; i < n; i++) {
vector<int> rect(4);
for (int j = 0; j < 4; j++) {
cin >> rect[j];
}
lBound = min(lBound, rect[0]);
rBound = max(rBound, rect[2]);
rectangles.push_back({rect[0], rect[2], rect[1], 1});
rectangles.push_back({rect[0
// vx公众号关注TechGuide,专业生产offer收割机,代码可能需要少量调试。
第三题:最短区间
题目描述
有一个含N个整数的序列,对任意给定得M个整数(可能出现重复值),求一个最短区间,要求该区间包含这M个整数值,并输出该区间长度。
输入描述
第一行输入N
第二行输入N个整数
第三行输入M
第四行输入M个整数
输出描述
输出最短区间的长度
输出描述
区间长度
思路
类似力扣原题76:最小覆盖子串 的简化版,思路简单说下,经典滑动窗口,通过一个哈希表来统计窗口中每个数的出现次数,之后从最左边位置开始滑动右边界,直到窗口包含了所有给定的整数,更新最短区间长度。
代码
Java版本
import java.util.*;
public class ShortestInterval {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt(); // 输入序列的长度
int[] nums1 = new int[N]; // 序列数组
for (int i = 0; i < N; i++) {
nums1[i] = scanner.nextInt(); // 输入序列的元素
}
int M = scanner.nextInt(); // 给定整数的个数
int[] nums2 = new int[M]; // 给定整数数组
for (int i = 0; i < M; i++) {
nums2[i] = scanner.nextInt(); // 输入给定整数
}
Set<Integer> nums2Set = new HashSet<>();
for (int num : nums2) {
nums2Set.add(num); // 将给定整数存入集合中
}
Map<Integer, Integer> window = new HashMap<>();
int left = 0, res = Integer.MAX_VALUE;
// 遍历序列,使用滑动窗口找到最短区间
for (int right = 0; right < N; right++) {
int num = nums1[right];
window.put(num, window.getOrDefault(num, 0) + 1); // 更新窗口中元素的出现次数
// 如果窗口中包含所有给定整数,更新最短区间长度
while (left <= right && containsAll(window, nums2Set)) {
res = Math.min(res, right - left + 1); // 更新最短区间长度
int leftNum = nums1[left];
window.put(leftNum, window.get(leftNum) - 1); // 左边界右移,更新窗口中元素的出现次数
left++; // 更新左边界
}
}
// 输出最短区间的长度
System.out.println(res);
}
// 检查窗口中是否包含所有给定整数
private static boolean containsAll(Map<Integer, Integer> window, Set<Integer> nums2Set) {
for (int num : nums2Set) {
if (!window.containsKey(num) || window.get(num) == 0) {
return false;
}
}
return true;
}
}
// vx公众号关注TechGuide,专业生产offer收割机,代码可能需要少量调试。
CPP版本
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;
int main() {
int N, M;
cin >> N;
vector<int> nums1(N);
for (int i = 0; i < N; i++) {
cin >> nums1[i];
}
cin >> M;
vector<int> nums2(M);
for (int i = 0; i < M; i++) {
cin >> nums2[i];
}
unordered_set<int> nums2Set;
for (int num : nums2) {
nums2Set.insert(num);
}
unordered_map<int, int> window;
int left = 0, res = INT_MAX;
// 遍历序列,使用滑动窗口找到最短区间
for (int right = 0; right < N; right++) {
int num = nums1[right];
window[num]++; // 更新窗口中元素的出现次数
// 如果窗口中包含所有给定整数,更新最短区间长度
while (left <= right && containsAll(window, nums2Set)) {
res = min(res, right - left + 1); // 更新最短区间长度
int leftNum = nums1[left];
window[leftNum]--; // 左边界右移,更新窗口中元素的出现次数
left++; // 更新左边界
}
}
// 输出最短区间的长度
cout << res << endl;
return 0;
}
// 检查窗口中是否包含所有给定整数
bool containsAll(unordered_map<int, int>& window, unordered_set<int>& nums2Set) {
for (int num : nums2Set) {
if (window.find(num) == window.end() || window[num] == 0) {
return false;
}
}
return true;
}
// vx公众号关注TechGuide,专业生产offer收割机,代码可能需要少量调试。