#P3347. 第1题-子序列的字典序
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 176
            Accepted: 48
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              京东
                                
            
                        
              时间 :2025年8月9日
                              
                      
          
 
- 
                        算法标签>栈          
 
第1题-子序列的字典序
解题思路
1. 基本思路
我们需要在原序列中挑选出一个长度为k的子序列,要求该子序列包含1, 2, ..., k,且每个数字只出现一次。字典序最小意味着我们在每一步选择时,要尽可能选择一个较小的数字。
2. 栈结构
为了解决这个问题,我们可以使用栈(Stack)来辅助选择数字。栈结构的特点是后进先出(LIFO),可以帮助我们在遍历过程中保持字典序最小。
3. 具体步骤
- 
倒序遍历:从左到右遍历整个序列,但在选择时,需要考虑后面是否还有该数字。如果后面没有该数字了,那么该数字必须被选中。
 - 
栈的使用:我们使用栈来存储已经选择的数字,确保字典序最小。
- 当栈顶的数字比当前数字大,并且栈顶数字后面还有更多的数字时,我们可以弹出栈顶数字,选择当前数字。
 - 每次选择时,尽量保证栈中的数字是递增的,这样才能确保字典序最小。
 
 - 
避免重复选择:每个数字最多选择一次,因此我们需要用一个数组记录每个数字是否已经被选择过。
 
4. 复杂度分析
- 遍历序列一次,每个数字最多入栈一次或出栈一次,因此时间复杂度为
O(n)。 - 空间复杂度为
O(k),用于存储最终的子序列。 
代码实现
Python代码实现
def find_subsequence(n, k, a):
    # 用栈来构建最终的子序列
    stack = []
    # 记录每个数字是否已经选择过
    in_stack = [False] * (k + 1)
    # 记录每个数字在后续序列中的出现次数
    count = [0] * (k + 1)
    # 统计每个数字出现的次数
    for num in a:
        count[num] += 1
    # 遍历序列
    for num in a:
        count[num] -= 1  # 当前数字已经处理过,剩余的次数减少
        # 如果栈中已经有该数字,则跳过
        if in_stack[num]:
            continue
        # 如果栈顶的数字比当前数字大,并且栈顶的数字在后面还会出现,那么就弹出栈顶
        while stack and stack[-1] > num and count[stack[-1]] > 0:
            # 弹出栈顶数字
            in_stack[stack.pop()] = False
        # 将当前数字加入栈中
        stack.append(num)
        in_stack[num] = True
    return stack
# 输入处理
n, k = map(int, input().split())
a = list(map(int, input().split()))
# 获取字典序最小的子序列
result = find_subsequence(n, k, a)
# 输出结果
print(" ".join(map(str, result)))
Java代码实现
import java.util.*;
public class Main {
    public static List<Integer> findSubsequence(int n, int k, int[] a) {
        Stack<Integer> stack = new Stack<>();
        boolean[] inStack = new boolean[k + 1];
        int[] count = new int[k + 1];
        // 统计每个数字的出现次数
        for (int num : a) {
            count[num]++;
        }
        for (int num : a) {
            count[num]--;
            // 如果栈中已经有该数字,跳过
            if (inStack[num]) {
                continue;
            }
            // 如果栈顶的数字比当前数字大,并且栈顶的数字在后面还会出现,则弹出栈顶
            while (!stack.isEmpty() && stack.peek() > num && count[stack.peek()] > 0) {
                inStack[stack.pop()] = false;
            }
            // 将当前数字加入栈中
            stack.push(num);
            inStack[num] = true;
        }
        return 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 = findSubsequence(n, k, a);
        for (int num : result) {
            System.out.print(num + " ");
        }
    }
}
C++代码实现
#include <bits/stdc++.h>
using namespace std;
vector<int> findSubsequence(int n, int k, vector<int>& a) {
    stack<int> s;
    vector<bool> inStack(k + 1, false);
    vector<int> count(k + 1, 0);
    vector<int> result;
    // 统计每个数字的出现次数
    for (int num : a) {
        count[num]++;
    }
    // 遍历序列
    for (int num : a) {
        count[num]--;
        // 如果栈中已经有该数字,跳过
        if (inStack[num]) {
            continue;
        }
        // 如果栈顶的数字比当前数字大,并且栈顶的数字在后面还会出现,则弹出栈顶
        while (!s.empty() && s.top() > num && count[s.top()] > 0) {
            inStack[s.top()] = false;
            s.pop();
        }
        // 将当前数字加入栈中
        s.push(num);
        inStack[num] = true;
    }
    // 将栈中的元素存入结果
    while (!s.empty()) {
        result.push_back(s.top());
        s.pop();
    }
    // 因为栈是从后向前存的,所以要反转结果
    reverse(result.begin(), result.end());
    return result;
}
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 = findSubsequence(n, k, a);
    for (int num : result) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}
        题目内容
现在有一个长度为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个空格隔开的正整数,表示该数字序列ai。 1≤k≤n≤5∗104,1≤ai≤k
输出描述
输出一行k个数字,表示字典序最小的子序列。不要输出行末空格。
样例1
输入
5 3
2 1 3 3 2
输出
1 3 2
说明
其中一种可行的方案为:2 {1} {3} 3 {2},选定括号中的数字,构成子序列,可知此时字典序最小。