|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[899. 有序队列](https://leetcode.cn/problems/orderly-queue/solution/by-ac_oier-443m/)** ,难度为 **困难**。 |
| 4 | + |
| 5 | +Tag : 「构造」、「最小表示法」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +给定一个字符串 `s` 和一个整数 `k` 。你可以从 `s` 的前 `k` 个字母中选择一个,并把它加到字符串的末尾。 |
| 10 | + |
| 11 | +返回 在应用上述步骤的任意数量的移动后,字典上最小的字符串 。 |
| 12 | + |
| 13 | +示例 1: |
| 14 | +``` |
| 15 | +输入:s = "cba", k = 1 |
| 16 | +
|
| 17 | +输出:"acb" |
| 18 | +
|
| 19 | +解释: |
| 20 | +在第一步中,我们将第一个字符(“c”)移动到最后,获得字符串 “bac”。 |
| 21 | +在第二步中,我们将第一个字符(“b”)移动到最后,获得最终结果 “acb”。 |
| 22 | +``` |
| 23 | +示例 2: |
| 24 | +``` |
| 25 | +输入:s = "baaca", k = 3 |
| 26 | +
|
| 27 | +输出:"aaabc" |
| 28 | +
|
| 29 | +解释: |
| 30 | +在第一步中,我们将第一个字符(“b”)移动到最后,获得字符串 “aacab”。 |
| 31 | +在第二步中,我们将第三个字符(“c”)移动到最后,获得最终结果 “aaabc”。 |
| 32 | +``` |
| 33 | + |
| 34 | +提示: |
| 35 | +* $1 <= k <= S.length <= 1000$ |
| 36 | +* `s` 只由小写字母组成。 |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +### 最小表示法 |
| 41 | + |
| 42 | +当 $k > 1$ 时,我们能够构造出任意的字符串方案,因此当 $k > 1$ 时,我们可以直接通过对字符串排序来得到答案,复杂度为 $O(n\log{n})$。 |
| 43 | + |
| 44 | +当 $k = 1$ 时,我们共有 $n$ 种候选方案(将字符串 `s` 看作一个首尾相接的循环字符串,共有 $n$ 个起点可枚举),枚举过程中需要与当前最优的方案进行比较,比较复杂度为 $O(n)$,因此整体复杂度为 $O(n^2)$。 |
| 45 | + |
| 46 | +上述的做法已经可以通过本题,可以看出瓶颈在于对 $k = 1$ 的处理。 |
| 47 | + |
| 48 | +而实际上,对于给定字符串 `s`,求其循环同构的所有方案中字典序最小的方案,可以使用「最小表示法」来做,复杂度为 $O(n)$。 |
| 49 | + |
| 50 | +最小表示法将「方案比较」与「构造更优方案」进行结合:假设我们当前有两字符串 `a` 和 `b` 需要进行比较,其均为原串 `s` 的循环同构具体方案。假设 `a` 和 `b` 分别对应了原串下标为 `i` 和 `j` 的具体方案,且假设两字符串前 $k$ 个字符均相同。当两字符串第一个不同的字符大小关系为 $cs[i + k] > cs[j + k]$ 时,可以发现以下标范围 $[i, i + k]$ 作为起点的新方案必然不可能比字符串 `b` 更优,因此我们可以直接从 $i + k + 1$ 位置构造新的更优方案,并与 `b` 再次比较。而 $cs[i + k] < cs[j + k]$ 的分析同理。 |
| 51 | + |
| 52 | +Java 代码: |
| 53 | +```Java |
| 54 | +class Solution { |
| 55 | + public String orderlyQueue(String s, int _k) { |
| 56 | + char[] cs = s.toCharArray(); |
| 57 | + if (_k == 1) { |
| 58 | + int i = 0, j = 1, k = 0, n = cs.length; |
| 59 | + while (i < n && j < n && k < n) { |
| 60 | + char a = cs[(i + k) % n], b = cs[(j + k) % n]; |
| 61 | + if (a == b) k++; |
| 62 | + else { |
| 63 | + if (a > b) i += k + 1; |
| 64 | + else j += k + 1; |
| 65 | + if (i == j) i++; |
| 66 | + k = 0; |
| 67 | + } |
| 68 | + } |
| 69 | + i = Math.min(i, j); |
| 70 | + return s.substring(i) + s.substring(0, i); |
| 71 | + } else { |
| 72 | + Arrays.sort(cs); |
| 73 | + return String.valueOf(cs); |
| 74 | + } |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | +TypeScript 代码: |
| 79 | +```TypeScript |
| 80 | +function orderlyQueue(s: string, _k: number): string { |
| 81 | + if (_k == 1) { |
| 82 | + let i = 0, j = 1, k = 0, n = s.length |
| 83 | + while (i < n && j < n && k < n) { |
| 84 | + const a = s[(i + k) % n], b = s[(j + k) % n] |
| 85 | + if (a == b) k++; |
| 86 | + else { |
| 87 | + if (a > b) i += k + 1 |
| 88 | + else j += k + 1 |
| 89 | + if (i == j) i++ |
| 90 | + k = 0 |
| 91 | + } |
| 92 | + } |
| 93 | + i = Math.min(i, j) |
| 94 | + return s.substring(i) + s.substring(0, i) |
| 95 | + } else { |
| 96 | + return [...s].sort().join(''); |
| 97 | + } |
| 98 | +}; |
| 99 | +``` |
| 100 | +* 时间复杂度:当 $k = 1$ 时,复杂度为 $O(n)$;当 $k > 1$ 时,复杂度为 $O(n\log{n})$ |
| 101 | +* 空间复杂度:当 $k > 1$ 时,需要使用额外的排序空间 $O(n\log{n})$ |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +### 最后 |
| 106 | + |
| 107 | +这是我们「刷穿 LeetCode」系列文章的第 `No.899` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 108 | + |
| 109 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 110 | + |
| 111 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 112 | + |
| 113 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 114 | + |
0 commit comments