题目链接
题目描述
一位篮球教练需要从一份按考察顺序排列的 位候选球员名单中,挑选
名球员组成一支队伍。每位球员都有一个实力评分。
挑选规则:
- 保持相对顺序:如果球员 A 在名单中先于球员 B,那么在最终队伍中,如果两人都被选中,A 的位置也必须在 B 之前。
- 实力最强:队伍的实力通过字典序进行比较。从两队的第一名球员开始逐一比较,第一个出现评分差异的位置上,评分较高的球员所在的队伍实力更强。
任务:
找出实力最强的 人队伍,并输出他们的评分列表。
输入描述:
- 第一行:一串由空格分隔的整数,代表候选球员的实力评分。
- 第二行:一个正整数
,代表队伍人数。
- 约束:输入的评分必须是十进制整数,否则视为无效输入。
输出描述:
- 输出实力最强队伍的球员评分列表,各评分之间用空格隔开。
- 如果输入包含非十进制数字或格式错误,则输出
error。
解题思路
本题的目标是从一个数字序列中,选择 个数组成一个新的子序列,要求在保持原相对顺序的前提下,使这个子序列的字典序最大。这是一个经典的单调栈应用问题,也可以理解为一种贪心策略。
核心思想:
为了让最终选出的 人队伍的字典序尽可能大,我们应该让排在前面的球员实力评分尽可能高。
我们可以遍历所有候选球员,同时维护一个结果栈(或列表),这个栈将用来构建最终的队伍。
-
遍历与决策:
- 当我们考察第
位球员时,我们看结果栈的栈顶元素。
- 如果当前球员的实力比栈顶球员强,并且我们还有“淘汰名额”(即
(总人数 - 当前考察位置) + 栈中人数 > K),那么栈顶球员就可以被淘汰(出栈),因为用当前更强的球员替换他,可以使结果的字典序变得更大或保持不变(如果前面都一样)。 - 这个淘汰过程需要持续进行,直到栈顶球员实力不弱于当前球员,或者淘汰名额用完。
- 之后,将当前球员压入栈中。
- 当我们考察第
-
淘汰名额的计算:
- 设总球员数为
,需要挑选
人。那么我们总共可以淘汰
人。
- 更精细的判断是,在遍历到第
个球员时(从 0 开始),后面还剩下
个候选人。当前栈中有
len(stack)个人。为了凑齐个人,我们最多还需要
个人。只要后面剩下的人数
N - 1 - i大于我们还需要的人数,就说明我们还有选择的余地,可以淘汰栈顶较弱的球员。 - 一个更简洁的判断条件是:只要
len(stack) + (N - i) > K,就说明我们有淘汰的资本。
- 设总球员数为
-
构建单调栈:
- 创建一个空栈
result_stack。 - 遍历输入的球员评分列表
scores。 - 对于每个球员
score:- 当栈不为空,
score大于栈顶元素,且我们还有淘汰名额(len(result_stack) + (N - i) > K)时,就不断地将栈顶元素弹出。 - 然后将当前
score压入栈中。
- 当栈不为空,
- 遍历结束后,栈中的元素可能多于
个(因为可能有些较小的数无法被淘汰),因此需要截取前
个元素作为最终结果。
- 创建一个空栈
-
输入校验:
- 在读取输入时,需要检查每个评分字符串是否为纯十进制数字。这可以通过尝试将字符串转换为整数并捕获异常来实现。如果任何一个字符串转换失败,或者
不是一个正整数,都应输出
error。
- 在读取输入时,需要检查每个评分字符串是否为纯十进制数字。这可以通过尝试将字符串转换为整数并捕获异常来实现。如果任何一个字符串转换失败,或者
示例演练:
scores = [88, 83, 82, 84, 84, 83, 90, 85], K = 4
- 88入栈:
[88] - 83入栈:
[88, 83] - 82入栈:
[88, 83, 82] - 84 > 82, 淘汰82. 84 > 83, 淘汰83. 84 < 88, 停止. 84入栈:
[88, 84] - 84 == 84, 84入栈:
[88, 84, 84] - 83入栈:
[88, 84, 84, 83] - 90 > 83, 淘汰83. 90 > 84, 淘汰84. 90 > 84, 淘汰84. 90 > 88, 淘汰88. 90入栈:
[90](这里淘汰名额充足) - 85入栈:
[90, 85]
这里贪心选择似乎有问题,我们重新审视逻辑。可淘汰的人数是 。
修正后的贪心/单调栈逻辑:
- 可淘汰人数
removals = N - K。 - 遍历球员,维护结果栈
stack。 - 对于每个球员
score:- 当栈不为空,
score>stack.top(),且removals > 0时:stack.pop()removals--
stack.push(score)
- 当栈不为空,
- 遍历结束后,如果栈中元素多于
个(
removals可能未用完),则从栈底开始取个元素。
再次演练:
scores = [88, 83, 82, 84, 84, 83, 90, 85], K = 4, removals = 4
- 88入栈:
[88] - 83 < 88, 83入栈:
[88, 83] - 82 < 83, 82入栈:
[88, 83, 82] - 84 > 82, 82出栈, removals=3.
[88, 83]. 84 > 83, 83出栈, removals=2.[88]. 84入栈:[88, 84] - 84 == 84, 84入栈:
[88, 84, 84] - 83 < 84, 83入栈:
[88, 84, 84, 83] - 90 > 83, 83出栈, removals=1.
[88, 84, 84]. 90 > 84, 84出栈, removals=0.[88, 84]. 此时removals用完. 90入栈:[88, 84, 90] - 85 < 90, 85入栈:
[88, 84, 90, 85] - 遍历结束,栈中正好4个元素。结果:
88 84 90 85. 正确。
代码
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
using namespace std;
// 检查字符串是否为有效的十进制整数
bool is_valid_integer(const string& s) {
if (s.empty() || (s.length() > 1 && s[0] == '0') && s.find_first_not_of("0123456789") == string::npos) {
// 允许单个'0', 但不允许'01'这样的前导零
if (s.length() > 1 && s[0] == '0') return false;
}
if (s.length() == 1 && s[0] == '0') return true;
// 检查是否有非数字字符,除了可能有的负号
string temp = s;
if (s[0] == '-') {
temp = s.substr(1);
if(temp.empty()) return false;
}
return temp.find_first_not_of("0123456789") == string::npos;
}
int main() {
string line;
if (!getline(cin, line)) {
cout << "error" << endl;
return 0;
}
stringstream ss(line);
string score_str;
vector<int> scores;
while (ss >> score_str) {
if (!is_valid_integer(score_str)) {
cout << "error" << endl;
return 0;
}
scores.push_back(stoi(score_str));
}
string k_str;
if (!(cin >> k_str) || !is_valid_integer(k_str) || k_str[0] == '-') {
cout << "error" << endl;
return 0;
}
int k = stoi(k_str);
if (k <= 0 || k > scores.size()) {
// 虽然题目约束 k>0,但防御性编程处理
return 0;
}
vector<int> stack;
int n = scores.size();
int removals = n - k;
for (int score : scores) {
while (!stack.empty() && score > stack.back() && removals > 0) {
stack.pop_back();
removals--;
}
stack.push_back(score);
}
// 如果淘汰名额没用完,栈中可能多于k个,截取前k个
stack.resize(k);
for (int i = 0; i < k; ++i) {
cout << stack[i] << (i == k - 1 ? "" : " ");
}
cout << endl;
return 0;
}
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try {
// 读取第一行球员评分
String line = sc.nextLine();
String[] score_strs = line.split(" ");
List<Integer> scores = new ArrayList<>();
for (String s : score_strs) {
// 检查是否为有效十进制整数
if (!s.matches("-?\\d+")) {
System.out.println("error");
return;
}
scores.add(Integer.parseInt(s));
}
// 读取第二行队伍人数
int k = sc.nextInt();
if (k <= 0 || k > scores.size()) {
return; // 根据题目约束,k应为正数且不大于N
}
int n = scores.size();
// 可以淘汰的人数
int removals = n - k;
// 使用 Deque 作为单调栈
Deque<Integer> stack = new ArrayDeque<>();
for (int score : scores) {
// 当栈不为空,当前分数>栈顶,且还有淘汰名额时,淘汰栈顶
while (!stack.isEmpty() && score > stack.peekLast() && removals > 0) {
stack.removeLast();
removals--;
}
stack.addLast(score);
}
// 最终结果为栈中的前 k 个元素
StringBuilder sb = new StringBuilder();
Iterator<Integer> it = stack.iterator();
for (int i = 0; i < k; i++) {
sb.append(it.next()).append(i == k - 1 ? "" : " ");
}
System.out.println(sb.toString());
} catch (Exception e) {
// 捕获所有可能的异常,如 NumberFormatException, NoSuchElementException 等
System.out.println("error");
}
}
}
def solve():
# 读取第一行并进行合法性校验
try:
score_strs = input().split()
if not score_strs:
print("error")
return
scores = [int(s) for s in score_strs]
except ValueError:
print("error")
return
# 读取第二行并进行合法性校验
try:
k_str = input()
if not k_str.isdigit() or int(k_str) <= 0:
print("error")
return
k = int(k_str)
except (ValueError, IndexError):
print("error")
return
n = len(scores)
if k > n:
return
# 可淘汰的人数
removals = n - k
# 单调栈
stack = []
for score in scores:
# 当栈不为空,当前分数大于栈顶,且还有淘汰名额
while stack and score > stack[-1] and removals > 0:
stack.pop()
removals -= 1
stack.append(score)
# 截取前 k 个元素作为结果
result = stack[:k]
# 格式化输出
print(*result)
solve()
算法及复杂度
- 算法:本题使用了单调栈的思路,结合贪心策略。为了使最终子序列的字典序最大,我们遍历原序列,并维护一个结果栈。对于每个新元素,如果它比栈顶元素大,并且我们还有淘汰名额,就将栈顶元素弹出,以此来保证栈中靠前的元素尽可能大。
- 时间复杂度:
,其中
是候选球员的数量。每个球员最多被入栈和出栈一次,所以整个过程是线性的。
- 空间复杂度:
,在最坏的情况下(例如输入序列是降序的),所有球员都会被压入栈中,因此栈的空间复杂度是
。

京公网安备 11010502036488号