大家好这里是清隆Coding ,一枚热爱算法的程序员
ACM金牌团队️ | 多次AK大厂笔试 | 编程一对一辅导
✨ 本系列打算持续跟新
秋招笔试题
感谢大家的订阅➕ 和 喜欢 和 手里的小花花
LYA 是一家大型物流公司的系统工程师。她正在设计一个新的物流网络优化系统。在这个系统中,有 个物流节点,每个节点代表一个城市或仓库。节点之间存在单向的物流通道,形成了一个复杂的网络结构。
LYA 需要找出所有的"安全节点"。一个节点被称为"安全节点",当且仅当从该节点出发的所有可能路径最终都能到达终端节点(没有出边的节点)。换句话说,如果一个节点的所有可能下游路径都能到达终端节点,那么这个节点就是安全的。
请你帮助 LYA 设计一个算法,找出所有的安全节点,并按升序排列输出它们的编号。
第一行输入一个正整数 ,表示物流网络中节点的数量。
接下来 行,每行描述一个节点的出边情况。第 行()包含若干个整数,表示从节点 出发可以直接到达的节点编号。如果该行只有一个 ,则表示该节点是终端节点。
输出一行,包含若干个用空格分隔的整数,表示所有安全节点的编号,要求按升序排列。
5
1 2 3 4
1 2
3 4
0 4
-1
4
在这个例子中,只有节点 4 是安全的,因为它是唯一的终端节点。
7
1 2
2 3
5
0
5
-1
-1
2 4 5 6
5 和 6 为终端节即为“安全节点”, 2 和4开始的所有下游节点最终都指向终端节点,按照升序排列最终结果为2,4,5,6
这道题乍一看可能有点复杂,但其实我们可以换一种思路来解决。
首先,我们需要换个角度思考这个问题。与其考虑从一个节点出发能否到达终点,不如反过来想:从终点开始,哪些节点可以到达它?这样一来,我们就把问题转化成了一个更容易处理的形式。
具体做法是这样的:
这个过程就像是剥洋葱,从外层一层层往里剥,直到剥不动为止。剥下来的每一层都是安全节点。
整个剥洋葱的过程也就是我们熟悉的拓扑排序。
这种方法的时间复杂度是 ,其中排序需要 ,图的遍历需要 。空间复杂度是 。
from collections import deque
def find_safe(n, links):
# 构建反向连接和出度计数
back = [[] for _ in range(n)]
out = [0] * n
for src in range(n):
for dst in links[src]:
if dst != -1:
back[dst].append(src)
out[src] += 1
# 初始化安全点集合和队列
safe = set(i for i in range(n) if out[i] == 0)
q = deque(safe)
# BFS遍历
while q:
cur = q.popleft()
for prev in back[cur]:
out[prev] -= 1
if out[prev] == 0:
safe.add(prev)
q.append(prev)
# 返回排序后的安全点列表
return sorted(safe)
# 读取输入
n = int(input())
links = []
for _ in range(n):
line = list(map(int, input().split()))
links.append(line if line != [-1] else [])
# 计算结果并输出
result = find_safe(n, links)
print(' '.join(map(str, result)))
import java.util.*;
public class Main {
public static List<Integer> findSafe(int n, int[][] links) {
// 构建反向连接和出度计数
List<List<Integer>> back = new ArrayList<>();
for (int i = 0; i < n; i++) {
back.add(new ArrayList<>());
}
int[] out = new int[n];
for (int src = 0; src < n; src++) {
for (int dst : links[src]) {
if (dst != -1) {
back.get(dst).add(src);
out[src]++;
}
}
}
// 初始化安全点集合和队列
Set<Integer> safe = new HashSet<>();
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (out[i] == 0) {
safe.add(i);
q.offer(i);
}
}
// BFS遍历
while (!q.isEmpty()) {
int cur = q.poll();
for (int prev : back.get(cur)) {
if (--out[prev] == 0) {
safe.add(prev);
q.offer(prev);
}
}
}
// 返回排序后的安全点列表
List<Integer> result = new ArrayList<>(safe);
Collections.sort(result);
return result;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[][] links = new int[n][];
scanner.nextLine(); // 消耗换行符
for (int i = 0; i < n; i++) {
String[] line = scanner.nextLine().split(" ");
links[i] = new int[line.length];
for (int j = 0; j < line.length; j++) {
links[i][j] = Integer.parseInt(line[j]);
}
}
List<Integer> result = findSafe(n, links);
for (int node : result) {
System.out.print(node + " ");
}
}
}
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <algorithm>
using namespace std;
vector<int> findSafe(int n, vector<vector<int>>& links) {
// 构建反向连接和出度计数
vector<vector<int>> back(n);
vector<int> out(n, 0);
for (int src = 0; src < n; src++) {
for (int dst : links[src]) {
if (dst != -1) {
back[dst].push_back(src);
out[src]++;
}
}
}
// 初始化安全点集合和队列
set<int> safe;
queue<int> q;
for (int i = 0; i < n; i++) {
if (out[i] == 0) {
safe.insert(i);
q.push(i);
}
}
// BFS遍历
while (!q.empty()) {
int cur = q.front();
q.pop();
for (int prev : back[cur]) {
if (--out[prev] == 0) {
safe.insert(prev);
q.push(prev);
}
}
}
// 返回排序后的安全点列表
vector<int> result(safe.begin(), safe.end());
sort(result.begin(), result.end());
return result;
}
int main() {
int n;
cin >> n;
vector<vector<int>> links(n);
for (int i = 0; i < n; i++) {
int x;
while (cin >> x && x != -1) {
links[i].push_back(x);
}
if (links[i].empty()) {
links[i].push_back(-1);
}
}
vector<int> result = findSafe(n, links);
for (int node : result) {
cout << node << " ";
}
cout << endl;
return 0;
}
卢小姐是一位著名的魔药大师。她最近发明了一种新的魔法药剂,这种药剂有一个独特的特性:它的魔力可以被分成 份(),而最终的魔法效果等于这 份魔力的乘积。
魔法议会认为这种药剂可能过于强大,因此要求对其魔法效果设置一个上限。现在给你魔法效果的上限和卢小姐的药剂魔力值,请判断这种药剂的实际魔法效果是否会超出给定的上限。
输入包含两个整数 和 ,用空格分隔。
其中 表示魔法效果的上限, 表示药剂的魔力值。
输出一个字符串,为 true
或 false
。
如果药剂的魔法效果超出上限,输出 true
;否则输出 false
。
2920 22
false
对于魔力值 22,最优的分配方式是 3 + 3 + 3 + 3 + 3 + 3 + 4,得到的魔法效果为 3^6 * 4 = 2916。 这个值不超过给定的上限 2920,因此输出 false。
1 2
false
魔力值为 2,只能分成 1 + 1,魔法效果为 1 * 1 = 1,不超过上限 1,因此输出 false。
161 14
true
魔力值 14 的最优分配方式是 3 + 3 + 3 + 3 + 2,得到的魔法效果为 3 * 3 * 3 * 3 * 2 = 162。 这个值超过了给定的上限 161,因此输出 true。
这个问题可以简化为:给定一个正整数(魔力值),如何将其拆分成至少两个正整数,使得这些正整数的乘积最大化,并判断这个最大乘积是否超过给定的上限。
关键思路如下:
对于小于等于 4 的魔力值,我们有固定的拆分方式:
对于大于 4 的魔力值,最优的拆分策略是:
这个策略基于以下数学事实:
因此,使用 3 作为基本单位可以让乘积最大化。唯一的例外是 4,因为 2×2 > 3×1。
在实际实现时,我们可以使用一个 while 循环来模拟拆分过程:
这种方法的时间复杂度是 ,其中 是魔力值。
def is_over_limit(limit, power):
"""
判断给定魔力值能产生的最大魔法效果是否超过上限
:param limit: 魔法效果上限
:param power: 魔力值
:return: 是否超过上限
"""
if power <= 4:
return [1, 1, 2, 4][power-1] > limit
product = 1
while power > 4:
product *= 3
power -= 3
if product > limit:
return True
product *= power
return product > limit
# 读取输入
limit, power = map(int, input().split())
# 计算结果并输出
result = is_over_limit(limit, power)
print(str(result).lower())
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long limit = scanner.nextLong();
int power = scanner.nextInt();
boolean result = isOverLimit(limit, power);
System.out.println(result);
}
/**
* 判断给定魔力值能产生的最大魔法效果是否超过上限
* @param limit 魔法效果上限
* @param power 魔力值
* @return 是否超过上限
*/
private static boolean isOverLimit(long limit, int power) {
if (power <= 4) {
return new long[]{1, 1, 2, 4}[power-1] > limit;
}
long product = 1;
while (power > 4) {
product *= 3;
power -= 3;
if (product > limit) {
return true;
}
}
product *= power;
return product > limit;
}
}
#include <iostream>
using namespace std;
/**
* 判断给定魔力值能产生的最大魔法效果是否超过上限
* @param limit 魔法效果上限
* @param power 魔力值
* @return 是否超过上限
*/
bool isOverLimit(long long limit, int power) {
if (power <= 4) {
int results[] = {1, 1, 2, 4};
return results[power-1] > limit;
}
long long product = 1;
while (power > 4) {
product *= 3;
power -= 3;
if (product > limit) {
return true;
}
}
product *= power;
return product > limit;
}
int main() {
long long limit;
int power;
cin >> limit >> power;
bool result = isOverLimit(limit, power);
cout << (result ? "true" : "false") << endl;
return 0;
}
LYA 是一个音乐节目的制作人。她正在为一个新的音乐节目安排表演顺序。节目中有 个表演项目,每个项目有不同的表演时长,有些项目因为技术原因无法进行(用 表示)。为了保持节目的流畅性,LYA 可以在规定的范围内调整表演顺序,但每次调整都需要消耗相应的时间来重新布置舞台。
LYA 的目标是找到一个从节目开始到结束的表演顺序,使得总耗时(包括表演时间和舞台重置时间)最少。如果存在多个总耗时相同的顺序,她希望选择按项目编号更小的顺序。如果无法安排到最后一个项目,则视为无法完成节目。
第一行包含一个正整数 ,表示表演项目的数量。
第二行包含 个整数 ,其中 表示第 个项目的表演时长( 表示该项目无法进行)。
第三行包含一个正整数 ,表示每次调整顺序时最多可以跳过的项目数。
输出一行,包含若干个用空格分隔的整数,表示最优的表演顺序(用项目的编号表示,编号从 1 开始)。如果无法完成节目,则输出空数组 []
。
5
1 2 4 -1 2
2
1 3 5
最优的表演顺序是第 1、3、5 个项目,总耗时为 1 + 4 + 2 = 7。
5
1 2 4 -1 2
1
[]
无法安排到最后一个项目,则视为无法完成节目,输出 []
这个问题可以简化为:在一个数组中找到一条路径,使得路径上的数字和最小,同时满足一些特定的跳跃规则。
想象 LYA 正在玩一个特殊的跳房子游戏。地上画了一排格子,每个格子上写着一个数字(表演时长),有些格子是坏的(无法表演的项目)。LYA 需要从第一个格子开始,一直跳到最后一个格子,但她每次最多只能跳过 m 个格子。
解决这个问题的关键是使用动态规划。可以把它想象成 LYA 在每个格子上都留下了一个笔记,记录到达这个格子的最小总耗时。
我们考虑如下步骤:
这个方法之所以有效,是因为它利用了问题的最优子结构特性。
到达每个格子的最优路径,一定是由到达之前某个格子的最优路径再加上一步得到的。
具体实现可以看以下的代码实现
时间复杂为 ,其中 是格子数, 是最大跳跃距离。
def arrange_performance(n, times, max_step):
"""
安排最优表演顺序
:param n: 项目数量
:param times: 各项目表演时长
:param max_step: 最大调整步长
:return: 最优表演顺序
"""
# 初始化动态规划数组和路径记录
dp = [floa