队列题目:按递增顺序显示卡牌

本文详细介绍了如何解决编程问题——按递增顺序显示卡牌。首先,通过模拟显示过程得到卡牌显示顺序的下标数组,然后对原始卡牌数组进行排序,最后根据下标数组还原出原始顺序。文章提供了两种实现方式,分别是显式创建下标数组和不显式创建。整个过程的时间复杂度为O(nlogn),空间复杂度为O(n)。

题目

标题和出处

标题:按递增顺序显示卡牌

出处:950. 按递增顺序显示卡牌

难度

6 级

题目描述

要求

给定一个整数数组 deck \texttt{deck} deck,表示一套牌组,牌组中的每张卡牌都对应有一个唯一的整数。第 i \texttt{i} i 张卡牌上的数字是 deck[i] \texttt{deck[i]} deck[i]

你可以按你想要的顺序对这套卡片进行排序。最初,这些卡牌在牌组里是正面朝下的(即,未显示状态)。

现在,重复执行以下步骤,直到显示所有卡牌为止:

  1. 从牌组顶部抽一张牌,显示它,然后将其从牌组中移出。
  2. 如果牌组中仍有牌,则将下一张处于牌组顶部的牌放在牌组的底部。
  3. 如果仍有未显示的牌,那么返回步骤 1。否则,结束操作。

返回能以递增顺序显示卡牌的牌组顺序。

答案中的第一张牌被认为处于牌组顶部。

示例

示例 1:

输入: deck   =   [17,13,11,2,3,5,7] \texttt{deck = [17,13,11,2,3,5,7]} deck = [17,13,11,2,3,5,7]
输出: [2,13,3,11,5,17,7] \texttt{[2,13,3,11,5,17,7]} [2,13,3,11,5,17,7]
解释:
我们得到的牌组顺序为 [17,13,11,2,3,5,7] \texttt{[17,13,11,2,3,5,7]} [17,13,11,2,3,5,7](这个顺序不重要),然后将其重新排序。
重新排序后,牌组以 [2,13,3,11,5,17,7] \texttt{[2,13,3,11,5,17,7]} [2,13,3,11,5,17,7] 开始,其中 2 \texttt{2} 2 位于牌组的顶部。
我们显示 2 \texttt{2} 2,然后将 13 \texttt{13} 13 移到底部。牌组现在是 [3,11,5,17,7,13] \texttt{[3,11,5,17,7,13]} [3,11,5,17,7,13]
我们显示 3 \texttt{3} 3,然后将 11 \texttt{11} 11 移到底部。牌组现在是 [5,17,7,13,11] \texttt{[5,17,7,13,11]} [5,17,7,13,11]
我们显示 5 \texttt{5} 5,然后将 17 \texttt{17} 17 移到底部。牌组现在是 [7,13,11,17] \texttt{[7,13,11,17]} [7,13,11,17]
我们显示 7 \texttt{7} 7,然后将 13 \texttt{13} 13 移到底部。牌组现在是 [11,17,13] \texttt{[11,17,13]} [11,17,13]
我们显示 11 \texttt{11} 11,然后将 17 \texttt{17} 17 移到底部。牌组现在是 [13,17] \texttt{[13,17]} [13,17]
我们显示 13 \texttt{13} 13,然后将 17 \texttt{17} 17 移到底部。牌组现在是 [17] \texttt{[17]} [17]
我们显示 17 \texttt{17} 17
由于所有卡片都是按递增顺序排列显示的,所以答案是正确的。

示例 2:

输入: deck   =   [1,1000] \texttt{deck = [1,1000]} deck = [1,1000]
输出: [1,1000] \texttt{[1,1000]} [1,1000]

数据范围

  • 1 ≤ deck.length ≤ 1000 \texttt{1} \le \texttt{deck.length} \le \texttt{1000} 1deck.length1000
  • 1 ≤ deck[i] ≤ 10 6 \texttt{1} \le \texttt{deck[i]} \le \texttt{10}^\texttt{6} 1deck[i]106
  • deck \texttt{deck} deck 的所有元素都是唯一的

解法

思路和算法

这道题要求返回以递增顺序显示卡牌的牌组顺序。如果直接考虑从卡牌的递增顺序还原得到原始牌组会比较困难,可以考虑另一个问题:对于原始牌组,根据题目描述中的操作,会以什么顺序显示卡牌。考虑卡牌的显示顺序时,不需要考虑卡牌上的数字,只需要考虑在显示顺序中的每张卡牌在原始牌组中的下标。

假设牌组中有 n n n 张卡牌,即数组 deck \textit{deck} deck 的长度是 n n n,则下标范围是 [ 0 , n − 1 ] [0, n - 1] [0,n1]。用 indices \textit{indices} indices 记录显示顺序中的每张卡牌在原始牌组中的下标,用 original \textit{original} original 表示原始牌组的每张卡牌上的数字。

为了得到显示顺序中的每张卡牌在原始牌组中的下标,可以模拟显示卡牌的过程。由于显示卡牌的过程包括从牌组顶部抽牌和将牌放在牌组底部的操作,符合先进先出的特点,因此可以使用队列模拟。

将从 0 0 0 n − 1 n - 1 n1 的每个整数依次入队,然后对于范围 [ 0 , n − 1 ] [0, n - 1] [0,n1] 中的每个下标 i i i,执行以下操作:

  1. 将队首元素出队,记为 index \textit{index} index

  2. index \textit{index} index 赋给 indices [ i ] \textit{indices}[i] indices[i]

  3. 如果队列不为空,将队首元素出队,并将出队的元素在队尾入队。

上述操作之后, indices \textit{indices} indices 中的第 i i i 个元素表示在显示顺序中的第 i i i 个元素在原始牌组中的下标。

例如,当 n = 7 n = 7 n=7 时, indices = [ 0 , 2 , 4 , 6 , 3 , 1 , 5 ] \textit{indices} = [0, 2, 4, 6, 3, 1, 5] indices=[0,2,4,6,3,1,5],表示显示顺序中的每张卡牌在原始牌组中的下标依次是 0 0 0 2 2 2 4 4 4 6 6 6 3 3 3 1 1 1 5 5 5,显示顺序中的每张卡牌上的数字依次是 original [ 0 ] \textit{original}[0] original[0] original [ 2 ] \textit{original}[2] original[2] original [ 4 ] \textit{original}[4] original[4] original [ 6 ] \textit{original}[6] original[6] original [ 3 ] \textit{original}[3] original[3] original [ 1 ] \textit{original}[1] original[1] original [ 5 ] \textit{original}[5] original[5]

为了得到原始牌组,需要执行以下两步操作:

  1. deck \textit{deck} deck 排序;

  2. 根据 indices \textit{indices} indices 和排序后的 deck \textit{deck} deck 得到 original \textit{original} original

对于第 1 步操作,Java 有内置的排序方法, Arrays \texttt{Arrays} Arrays 类的 sort \texttt{sort} sort 静态方法可以对数组排序。

对于第 2 步操作,可以将 deck \textit{deck} deck 中的元素按照从小到大的顺序依次填入 original \textit{original} original。对于 0 ≤ i < n 0 \le i < n 0i<n deck [ i ] \textit{deck}[i] deck[i] 是第 i i i 张显示的卡牌。令 index = indices [ i ] \textit{index} = \textit{indices}[i] index=indices[i],根据 indices \textit{indices} indices 的定义可知, deck [ i ] = original [ index ] \textit{deck}[i] = \textit{original}[\textit{index}] deck[i]=original[index],因此将 deck [ i ] \textit{deck}[i] deck[i] 的值赋给 original [ index ] \textit{original}[\textit{index}] original[index]

上述操作结束之后, original \textit{original} original 即为以递增顺序显示卡牌的原始牌组。

具体实现时,可以显性创建并计算 indices \textit{indices} indices,然后计算 original \textit{original} original,也可以不显性创建 indices \textit{indices} indices 直接计算 original \textit{original} original。第二种实现虽然可以节省一个数组的空间,但是由于有队列空间,因此两种实现的空间复杂度都是 O ( n ) O(n) O(n)

下面的代码提供了这两种实现。

代码

下面的代码为显性创建并计算 indices \textit{indices} indices 的做法。

class Solution {
    public int[] deckRevealedIncreasing(int[] deck) {
        int n = deck.length;
        Queue<Integer> queue = new ArrayDeque<Integer>();
        for (int i = 0; i < n; i++) {
            queue.offer(i);
        }
        int[] indices = new int[n];
        for (int i = 0; i < n; i++) {
            int index = queue.poll();
            indices[i] = index;
            if (!queue.isEmpty()) {
                queue.offer(queue.poll());
            }
        }
        Arrays.sort(deck);
        int[] original = new int[n];
        for (int i = 0; i < n; i++) {
            int index = indices[i];
            original[index] = deck[i];
        }
        return original;
    }
}

下面的代码为不显性创建 indices \textit{indices} indices 的做法。

class Solution {
    public int[] deckRevealedIncreasing(int[] deck) {
        int n = deck.length;
        Queue<Integer> queue = new ArrayDeque<Integer>();
        for (int i = 0; i < n; i++) {
            queue.offer(i);
        }
        int[] original = new int[n];
        Arrays.sort(deck);
        for (int i = 0; i < n; i++) {
            int index = queue.poll();
            original[index] = deck[i];
            if (!queue.isEmpty()) {
                queue.offer(queue.poll());
            }
        }
        return original;
    }
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 deck \textit{deck} deck 的长度。对数组 deck \textit{deck} deck 排序的时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn),计算 indices \textit{indices} indices original \textit{original} original 的时间复杂度是 O ( n ) O(n) O(n),因此总时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 deck \textit{deck} deck 的长度。空间复杂度主要取决于队列空间,队列内的元素个数不会超过 n n n

后记

这道题的解法涉及到排序。由于题目要求以递增顺序显示卡牌,但是输入的数组 deck \textit{deck} deck 不一定是有序的,因此对数组 deck \textit{deck} deck 排序是必不可少的操作。

Java 的 Arrays \texttt{Arrays} Arrays 类和 Collections \texttt{Collections} Collections 类有内置的 sort \texttt{sort} sort 静态方法,分别可以对数组和列表( List \texttt{List} List 接口的对象)排序。只有当数组或列表中的元素可以比较大小时,才能通过调用 sort \texttt{sort} sort 方法排序,默认为升序排序。对于引用类型,可以在调用 sort \texttt{sort} sort 方法时自定义比较方法。

内置的 sort \texttt{sort} sort 方法的时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn),空间复杂度是 O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 是待排序的元素个数。

关于排序的种类、每种排序方法的实现和复杂度分析、排序的应用等内容,将在排序算法部分具体讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值