题目描述
由范围[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]
示例3:
输入:s = “DDIII”
输出:[3, 2, 1, 4, 5, 6]
提示:
1 <= s.length <= 10^5s[i]只包含字符“D”和“I”
分析思路
小窍门:从数据范围看,1w的数据范围,如果平方一定会超时,要考虑nlogn的实现,这里考虑单调栈。
通过分析可以发现,答案长度是 输入s的长度+1。
如何字典序最小?只要数据是有顺序(从小到大)的取数据,来从左往右生成答案,就是最小的排列,按照这个思路。
把有序整数数组的arr[i],依次添加到栈里边,直到出现变化。
什么情况下变化,变化了什么?出现拐点,一般就是低谷,或者山峰。
变化了怎么办?根据题意的判定条件,对存在栈内的元素,正确的转移到答案内,或者正确的排序栈内元素的位置。
这里答案,用新的数组来存储。
贪心的思考,我一开始定义为,都是‘D’,那么我把 每一个 arr[i],放进去 stack,如果一直是‘D’,那么就持续的放进去,最终反转一下,就是满足递减顺序的最小排列。
-
如果 遇到 “I”,当前的 arr[i],也是大于堆顶元素的,直接入栈。但是要考虑下当前元素和栈内元素的关系。
-
这个arr[i],会参与到之前位置和前前的“DI”关系吗,并不会。它就属于这个位置了,不参与,就不要整体和之前的放到一个栈内。那么就把之前的栈内元素清空到答案内。所以遇到 ‘I’时,要把之前的反转,并且出栈到答案内。并且也把当前的这个转移到答案内。
-
如果 遍历到n,栈内还有元素,说明栈内元素都是递减顺序的,需要反转到答案内。
代码实现
public int[] findPermutation(String s) {
ArrayDeque<Integer> stack = new ArrayDeque<>();
int n = s.length() + 1;
int[] ans = new int[n];
int idx = 0;
for (int i = 1; i <= n; i++) {
stack.addLast(i);
if (i == n || s.charAt(i - 1) == 'I') {
while (!stack.isEmpty()) {
ans[idx++] = stack.pollLast();
}
}
}
return ans;
}
输出 [3, 2, 1, 4, 5, 6]
纠结误区
以下是错误的想法
我只判断当前i位置元素和栈顶元素的大小关系,进行反转,这样会造成反转后的结果影响已有的大小关系。要考虑连续性的问题。所以循环判断,并且及时出栈。
public int[] findPermutation(String s) {
ArrayDeque<Integer> stack = new ArrayDeque<>();
int n = s.length() + 1;
int[] ans = new int[n];
if (s == null || s.length() ==0 ) return ans;
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = i + 1;
}
stack.addLast(nums[0]);
char[] chars = s.toCharArray();
int idx = 1;
for (char ch : chars) {
if (ch == 'I') {
stack.addLast(nums[idx]);
} else if (ch == 'D') {
int pop = stack.pollLast();
stack.addLast(nums[idx]);
stack.addLast(pop);
} else {
return ans;
}
idx++;
}
int i = 0;
for (Integer v : stack) {
ans[i++] = v;
}
return ans;
}
输出 [2, 3, 1, 4, 5, 6]