今天晚上和一个同学讨论了下字符串全排列的问题,发现了一些自己没有发现过的点,感觉这个问题虽然简单,但是其实要写的漂亮,其实还是有一些难度,要解决整个问题需要经历的分析过程其实还是挺漫长的。所以就做一个小结,把一些其他的知识点也串上,讲讲我是如何去分析这个问题的。
问题描述
字符串全排列问题
输入:一个非空字符串,例如“abc”,“a”,“abcdefg”。
输出:这个字符串所有不重复的排列,例如对于“abc”,就有“abc”,“acb”,“bac”,“bca”,“cab”,“cba”,这六个。
问题分析
首先看我们给出的求解思路:
状态与解的描述
这里我们先说明一下状态,状态可以简单理解成我们的“解”,但是,需要注意的是,这个解可能是一个完全解,也可以是一个部分解。这东西的定义对我们的求解分析有什么作用?我们来一点点分析。
对于我们的全排列问题,我们使用下划线来表示进行全排列的字符串的范围,例如,“
abcdef”表示需要对整个字符串进行排序,而“ab
cdef”则表示需要对后4个字符进行全排序,前2个字符不需要排序。
这种带有下划线的字符串就是我们的一个状态。
那为什么这种表示可以说它是一个“解”,我们看下图,对于字符串“
abc”的全排列,我们可以是看成是,分别以a,b,c开头的三个排列问题,即“a
bc”,“b
ac”,“c
ba”,这样不断的分解下去,我们可以发现整个树的最底层其实就是我们所希望的解。
将整个树的非叶子节点看成不分解,叶子节点看成是完全解,两者的并集就是我们所描述的所有状态。
注:我自己也不知道这样描述状态是不是准确,因为状态这玩意玄之又玄,怎么说都感觉不在着力点上。
如何求解
知道了上述的这个图,那求解的思路就很自然了,只要遍历这棵树即可,这里需要注意的两点就是:
这里必须遍历这棵树,因为完全解处在所有叶子节点上,没有走完所有叶子节点,无法获取到所有的解。
这里也不可能使用递推的思路去求解这个问题,因为我们的起点只可能在树的根部,无法直接跳跃到叶子,当然,反过来思考,如果我们可以字节获得叶子节点,何必需要这个树呢(当然,有些强词夺理了)。
状态转换
有了上述的分析,我们接下来面对的问题就是,如何去遍历这棵树了。
遍历树,能够想到的无非就是所谓的什么先序遍历、中序遍历、后续遍历这些。这些技术说白了都是基于递归技术实现,所以我们第一反应就是使用递归去遍历这棵树。
但是问题在于我们事先是没有构建好这棵树的,所以这个现在问题的关键点就变成了如何从一个节点跳转到另外一个节点,也就是不同的状态间是如何转换的。
这里的解决方法可以看下图,我们通过一个swap操作,就可以做到了,图中只画了其中的一层,其他层次之间的转移操作也是一样的。
所以,我们这里的状态转移开销=交换两个元素所带来的开销
如何用代码描述状态
认真观察上面的图,我们很容易发现,每个状态我们可以分解成两个部分”不带下划线的字母串+带有下划线的字母串“,两者之间可谓泾渭分明,所以我们可以只使用一个下标值表示这两者的分界线即可。
所以设定i为首个下划线字母在整个字符串中的下标值。所以我们的函数就可以写成: