题干: https://leetcode.com/problems/permutation-sequence/
要求1~n(1<=n<=9)的数字的全排列序列,中的第k个字符串。
毕竟不是Hard模式,思路还是比较快粗来的,我的思路还是逐层降级法,就是先先确定第一个数字选啥,再确定第二个数字选啥,逐层推下去,直到所有位都选定。产生这个思路的原因是,理论上这题的 暴力解法是把全排列(并排好序)都算出来,然后在取第k个串,这样做的坏处是浪费了很多工作量,因为我既然只要找第k个为何要把所有的排列都找出来呢?明显浪费工作量吧。于是乎那么接下来 的思路就是用一些取巧的方法,直接排除掉一些排列即可,这样就不用一个个数了。那么怎么取巧呢?
如果把这种排列看成一个树形选择结构的话,就会知道,以任意数字开头的字符串个数都是一样的,例如1,2,3三个数以1开头的有2个,以2开头的也有两个,以3开头的同样是两个,用更一般地说法,以x开头的n个元素的排列数为(n-1)!个,既然如此,在上面的例子中,如果我要求第4个元素,显然就不需要到从1开头的串中去找了,直接到2开头的串中去找即可,这样就直接跳过了1开头的所有串。如果有更多元素,那么原理也是一样的,从左到右每选一个位数,就相当于一个确定分组的过程,最终会收敛到唯一的一个值。
代码如下:
package com.example.demo.leetcode; import java.util.ArrayList; import java.util.List; public class PermutationSequence { int accumulated = 0; static class PickCondition{ int n; int k; List<Integer> selected; public PickCondition(int n, int k, List<Integer> s){ this.n = n; this.k = k; this.selected = s; } } public String getPermutation(int n, int k) { accumulated = 0; List<Integer> selected = new ArrayList<>(); while(selected.size()<n){ pickOne(new PickCondition(n,k,selected)); } StringBuilder sb = new StringBuilder(); for(Integer it: selected){ sb.append(it.toString()); } return sb.toString(); } int nextChoice(int choice, int n, List<Integer> selected){ int p = choice+1; while(p<=n && selected.contains(p)){ p++; } if(p>n){ //代表没有下一个可选的了 return -1; }else{ return p; } } /** * tested * @param num * @return */ int perm(int num){ int ret = 1; int p = num; while(p>=1){ ret=ret*p; p--; } return ret; } public void pickOne(PickCondition pk){ //设置为第一个可用选项 int choice = 0; int numLeft = pk.n-pk.selected.size(); int tempAccumulated = accumulated; int preAcc = tempAccumulated; int groupCnt = perm(numLeft-1); while(tempAccumulated<pk.k){ preAcc = tempAccumulated; tempAccumulated+=groupCnt; choice = nextChoice(choice, pk.n, pk.selected); } accumulated = preAcc; pk.selected.add(choice); } public static void main(String[] args) { PermutationSequence demo = new PermutationSequence(); String ret = demo.getPermutation(4,3); System.out.println(ret); } }
反思:本次代码一次过OJ,感觉很爽。