LeetCode 484. 寻找排列

484. 寻找排列

由范围 [1,n] 内所有整数组成的 n 个整数的排列 perm 可以表示为长度为 n - 1 的字符串 s ,其中:

  • 如果 perm[i] < perm[i + 1] ,那么 s[i] == 'I'
  • 如果 perm[i] > perm[i + 1] ,那么 s[i] == 'D' 。

给定一个字符串 s ,重构字典序上最小的排列 perm 并返回它。

示例 1:

输入: s = "I"
输出: [1,2]
解释: [1,2] 是唯一合法的可以生成秘密签名 "I" 的特定串,数字 1 和 2 构成递增关系。

示例 2:

输入: s = "DI"
输出: [2,1,3]
解释: [2,1,3] 和 [3,1,2] 可以生成秘密签名 "DI",
但是由于我们要找字典序最小的排列,因此你需要输出 [2,1,3]。

提示:

  • 1 <= s.length <= 10^5
  • s[i] 只会包含字符 'D' 和 'I'

解法1:使用栈


首先,我们回顾一下给定问题陈述的重要点。对于给定的 n,我们需要使用范围在 (1,n) 中的所有整数来生成满足字符串 s 中的模式的这些 n 数字的字典序最小的排列。

首先,我们注意到,生成的字典序最小的排列(无关于给定的模式 s)使用的是 n 个整数,从 (1,n),就是 [1,2,3,..,n](比如说 min)。因此,在生成所需的排列时,我们可以肯定地说,生成的排列应该尽可能接近 min。

现在,我们还可以注意到,只有在满足模式 s 的条件下,越靠近最有效位的数字越小,生成的数字才会尽可能小。现在,为了理解这些观察如何帮助解决给定问题,我们来看一个简单的例子。

比如,给定的模式 s 是 "DDIIIID"。这对应的 n=8。因此,可能的 min 排列将是 [1, 2, 3, 4, 5, 6, 7, 8]。现在,为了满足前两个 "DD" 模式,我们可以观察到生成最小排序的最佳做法是只重新排列 1, 2, 3,因为这些是可以放在生成到目前为止的满足给定排序的最小排序的三个最重要位置的最小数字,这导致现在形成的是 [3, 2, 1, 4, 5, 6, 7, 8]。我们可以注意到,将任何大于3的数字放在这些位置上都会导致生成字典序较大的排列,如上所述。

我们也可以注意到,无论我们如何重新排列前三个 1, 2, 3,最后一个数字与下一个数字之间的关系始终满足满足 s 中第一个 I 的条件。此外,注意到,直到现在生成的模式已经满足 s 中的子模式 "IIII"。这将始终得到满足,因为最初被考虑的 min 数字总是满足递增的条件。

现在,当我们找到模式 s 中的最后一个 "D" 时,我们又需要在最后两个位置进行重新排列,而且我们只需要在这些位置进行重新排列使用的数字 7, 8。这是因为,再次,我们想要尽可能地将较大的数字保持在尽可能小的有效位置以生成字典序最小的排序。因此,为了满足最后一个 "D",最后两个数字的最佳排列是 8, 7 这将生成 [3, 2, 1, 4, 5, 6, 8, 7] 作为所需的输出。

基于以上的例子,我们可以总结,为了生成所需的排列,我们可以从给定的 n 可以形成的 min 数字开始。然后,为了满足给定的模式 s,我们只需要反转 min 数组中的那些子区域,这些子区域在其相应位置的模式中有一个 D。

为了执行这些操作,我们不必一开始就创建 min 数组,因为它只包含按升序的从 1 到 n 的数字。

为了执行上述操作,我们可以使用 stack。我们可以从 1 到 n 考虑数字 i。对于每个当前的数字,当我们在模式 s 中找到一个 D 时,我们只是将该数字推到 stack 上。这样做是因为,稍后,当我们找到下一个 I 时,我们可以从栈中弹出这些数字,从而形成在 s 中与 D's 相对应的那些数字的反向(降序)子模式(如上所述)。

当我们在模式中遇到一个 I 时,如上所述,我们也将当前的数字推到 stack 上,然后弹出 stack 上的所有数字,并将这些数字添加到直到现在形成的结果模式中。

现在,我们可能会在模式 s 的末尾看到一串 D's。在这种情况下,我们将无法找到结束的 I 来弹出 stack 上的数字。因此,最后,我们将值 n 推到堆栈上,然后弹出堆栈上的所有值,并将它们附加到此时此刻形成的结果模式中。现在,如果 s 中的倒数第二个字符是 I,那么 n 将正确地添加到结果排列的末尾。如果倒数第二个字符是 D,那么附加到结果排列末尾的反转模式将包括最后一个数字 n 来进行反转。在这两种情况下,结果排列都是正确的。

Java版:

class Solution {
    public int[] findPermutation(String s) {
        int i = 1;
        Deque<Integer> stack = new LinkedList<>();
        List<Integer> ans = new ArrayList<>();
        for (int j = 0; j < s.length(); j++) {
            stack.push(i);
            i++;
            while (s.charAt(j) == 'I' && !stack.isEmpty()) {
                ans.add(stack.pop());
            }
        }
        stack.push(i);
        while (!stack.isEmpty()) {
            ans.add(stack.pop());
        }
        return ans.stream().mapToInt(Integer::intValue).toArray();
    }
}

Python3版:

class Solution:
    def findPermutation(self, s: str) -> List[int]:
        stack = []
        ans = []
        i = 1
        for c in s:
            stack.append(i)
            i += 1
            while c == 'I' and stack:
                ans.append(stack.pop())
        stack.append(i)
        while stack:
            ans.append(stack.pop())
        return ans

复杂度分析

  • 时间复杂度:O(n)。 n 个数字将从 stack 中被压入和弹出。这里的 n 是指结果排列中的元素数。
  • 空间复杂度:O(n)。 在最坏情况下,stack 可以增长到 n 的深度。

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值