京东笔试 京东笔试题 0809
笔试时间:2025年8月9日
往年笔试合集:
第一题:子序列的字典序
现在有一个长度为 n 的数字序列,每个数都在 1~k 的范围内,且 1~k 内每个数字都至少出现过一次。现在我们想在这其中找一个子序列,使得 1~k 恰好出现一次,且字典序最小。请你通过程序得出结果。我们认为:B 是 A 的子序列,当且仅当可以从 A 中删除 0 个或任意个元素之后按照原来的顺序拼接起来得到 B。序列 A 的字典序小于 B,当且仅当存在一个位置 k,使得 A[k] < B[k] 且 A[i] = B[i](i = 1..k-1)。
输入描述
第一行两个空格隔开的正整数 n 和 k;
第二行 n 个空格隔开的正整数,表示该数字序列 a。
约束:1 ≤ k ≤ n ≤ 5×10⁴,1 ≤ aᵢ ≤ k
输出描述
输出一行 k 个数字,表示字典序最小的子序列。不要输出行末空格。
样例输入
5 3
2 1 3 3 2
样例输出
1 3 2
提示:其中一种可行的方案为:2(1)3(3)2,选定括号中的数字,构成子序列,可知此时字典序最小。
参考题解
本题使用贪心+单调栈的的思路, 问题本质分析:需要从原数组中选出长度为 k 的子序列(保持原顺序),且该子序列的字典序最小。字典序最小的核心是「前面的元素尽可能小」,这天然指向「贪心策略」—— 每一步都选择当前能选的最小元素。贪心的约束:选择小元素时不能 “只顾眼前”,必须保证后面还有足够的元素来凑齐 k 个。例如,若当前栈顶元素较大,但后面再也没有该元素了,就不能弹出它(否则凑不够 k 个)。因此需要统计每个元素剩余的出现次数,作为能否弹出栈顶的依据。单调栈的适配性:要维护 “前面元素尽可能小” 的序列,单调栈是天然的工具。栈可以动态维护已选元素,通过弹出 “较大且后续仍有出现” 的栈顶元素,确保新加入的元素能让序列更优(字典序更小)。同时栈的长度可以直观地控制在 k 以内,满足子序列长度要求。去重需求:若问题隐含 “子序列元素不重复”(从代码中 inStack 的判断可推测),则需要额外记录元素是否已在栈中,避免重复加入破坏唯一性。因此,贪心策略保证了 “选最小” 的核心目标,单调栈提供了动态维护序列的高效方式,剩余次数统计解决了 “凑够 k 个” 的约束
C++:
#include <iostream> #include <vector> #include <unordered_map> using namespace std; vector<int> smallestSubsequence(vector<int>& a, int k) { int n = a.size(); // 统计每个数字剩余出现的次数 unordered_map<int, int> count; for (int num : a) { count[num]++; } vector<int> stack; // 记录栈中是否已经包含某个数字 unordered_map<int, bool> inStack; for (int num : a) { // 每遍历一个数字,该数字剩余出现次数减 1 count[num]--; // 如果数字已经在栈中,跳过 if (inStack[num]) continue; // 维护单调栈的字典序最小性质 while (!stack.empty() && stack.back() > num && count[stack.back()] > 0) { inStack[stack.back()] = false; stack.pop_back(); } stack.push_back(num); inStack[num] = true; // 当栈的大小达到 k 时,提前终止(已经找到结果) if (stack.size() == k) break; } return stack; } int main() { int n, k; cin >> n >> k; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } vector<int> result = smallestSubsequence(a, k); for (int i = 0; i < k; i++) { cout << result[i]; if (i != k - 1) { cout << " "; } } cout << endl; return 0; }
Java:
import java.util.*; public class SmallestSubsequence { public static List<Integer> smallestSubsequence(int[] a, int k) { int n = a.length; // 统计每个数字剩余出现的次数 Map<Integer, Integer> count = new HashMap<>(); for (int num : a) { count.put(num, count.getOrDefault(num, 0) + 1); } Deque<Integer> stack = new ArrayDeque<>(); // 记录栈中是否已经包含某个数字 Map<Integer, Boolean> inStack = new HashMap<>(); for (int num : a) { // 每遍历一个数字,该数字剩余出现次数减 1 count.put(num, count.get(num) - 1); // 如果数字已经在栈中,跳过 if (inStack.getOrDefault(num, false)) { continue; } // 维护单调栈的字典序最小性质 while (!stack.isEmpty() && stack.peekLast() > num && count.get(stack.peekLast()) > 0) { inStack.put(stack.peekLast(), false); stack.pollLast(); } stack.offerLast(num); inStack.put(num, true); // 当栈的大小达到 k 时,提前终止(已经找到结果) if (stack.size() == k) { break; } } return new ArrayList<>(stack); } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); int k = scanner.nextInt(); int[] a = new int[n]; for (int i = 0; i < n; i++) { a[i] = scanner.nextInt(); } List<Integer> result = smallestSubsequence(a, k); for (int i = 0; i < k; i++) { System.out.print(result.get(i)); if (i != k - 1) { System.out.print(" "); } } System.out.println(); } }
Python:
from collections import defaultdict def smallest_subsequence(a, k): n = len(a) # 统计每个数字剩余出现的次数 count = defaultdict(int) for num in a: count[num] += 1 stack = [] # 记录栈中是否已经包含某个数字 in_stack = defaultdict(bool) for num in a: # 每遍历一个数字,该数字剩余出现次数减 1 count[num] -= 1 # 如果数字已经在栈中,跳过 if in_stack[num]: continue # 维护单调栈的字典序最小性质 while stack and stack[-1] > num and count[stack[-1]] > 0: in_stack[stack[-1]] = False stack.pop() stack.append(num) in_stack[num] = True # 当栈的大小达到 k 时,提前终止(已经找到结果) if len(stack) == k: break return stack if __name__ == "__main__": import sys input = sys.stdin.read().split() idx = 0 n = int(input[idx]) idx += 1 k = int(input[idx]) idx += 1 a = [] for i in range(n): a.append(int(input[idx])) idx += 1 result = smallest_subsequence(a, k) for i in range(k): print(result[i], end="") if i != k - 1: print(" ", end="") print()
第二题:01串划分
小钟有一个长度为 n 的 01 串 s(仅由字符 0 和 1 组成 ),另外还有 m 个数字,分别用 a₁, a₂, ..., aₘ 表示。小钟想知道,能否选择 m 个不相交的区间 [l₁, r₁], [l₂, r₂], ..., [lₘ, rₘ],使得对于任意的 aᵢ,其二进制表示(没有前导 0,0 的二进制表示就是 0 ),都能用 s 的某个连续子串 s[lⱼ, lⱼ + 1, ..., rⱼ] 来表示。
输入描述
输入包括多组测试数据:第一行输入一个正整数 T(1 ≤ T ≤ 20),表示测试数据的组数。每组测试数据的第一行有两个整数 n(1 ≤ n ≤ 100)
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
2025打怪升级记录,大厂笔试合集 C++, Java, Python等多种语言做法集合指南