字符串全排列【回溯法和下一个排列】两种解法详解
作者:互联网
package array
import (
"sort"
"testing"
)
// 题目:输入一个字符串,打印出该字符串中字符的所有排列。
// 要求:不能重复,比如:字符串中可能为abcb,但结果中不能有两个abbc
//直接运行此测试方法即可
func TestPermutation(t *testing.T) {
//这里演示一下切片截取,【大可略过】
perm := "12345"
//perm = perm[:len(perm)-1]
//切片截取原则:左闭右开(左臂有铠)
t.Logf(perm[1:5]) //2345
t.Logf(perm[1:4]) //234
t.Logf(perm[0:1]) //1
//以abc为例,分别调用两种解法
s := "abc"
t.Log(permutation1(s)) //回溯法
t.Log(permutation2(s)) //下一个排列
}
// 解法1:回溯法【即深度优先搜索】
// 我们将这个问题看作有 n 个排列成一行的空位,我们需要从左往右依次填入题目给定的 n 个字符,每个字符只能使用一次
// 大白话就是:我们先对第一个空位进行插入元素,如果决定第一个空位插入a,则要把接下来的第二个一直到第n个空位全部填入【一路填到底:深度优先】,然后再决定第一个空位插入b,,,以此类推
func permutation1(s string) (ans []string) {
//字符串进来后,先转成byte切片
t := []byte(s)
//排序【从小到大】sort.slice(t, func(i,j int ) bool{return t[i] < t[j]})
sort.Slice(t, func(i, j int) bool { return t[i] < t[j] })
n := len(t)
//当前排列,正向使用时append,回溯时,逐个清除
perm := make([]byte, 0, n)
//标记状态切片,正向使用时设置为true,回溯时设置为false
vis := make([]bool, n)
//定义递归函数【回溯】
var backtrack func(int)
// i代表第几个空位,从0开始
backtrack = func(i int) {
if i == n {
//如果n个空位都排满了,塞入结果集中,并返回
ans = append(ans, string(perm))
return
}
//对当前的第i个空位,进行选择使用哪个字符填入
for j, b := range vis {
//加入有两个B,sort排序后,两个B一定紧邻,如果B1未使用,则不允许使用B2,这样就不会出现ABBC,ABBC重复的情况
if b || j > 0 && !vis[j-1] && t[j-1] == t[j] {
continue
}
//递归标记当前的元素
vis[j] = true
//递归使用当前的元素
perm = append(perm, t[j])
backtrack(i + 1)
//递归清除perm,已使用的当前元素
perm = perm[:len(perm)-1]
//递归取消标记当前的元素
vis[j] = false
}
}
//从0开始启动
backtrack(0)
return
}
//复杂度分析
//
//时间复杂度:O(n \times n!)O(n×n!),其中 nn 为给定字符串的长度。这些字符的全部排列有 O(n!)O(n!) 个,每个排列平均需要 O(n)O(n) 的时间来生成。
//
//空间复杂度:O(n)O(n)。我们需要 O(n)O(n) 的栈空间进行回溯,注意返回值不计入空间复杂度。
//解法2:下一个(更大的)排列,我先先把整个字符串按有小到大进行排列,然后搜索紧邻的下一个更大的排列,一直到找到最大的排列为止
//如:字符串123,下一个紧邻的更大的排列,一定是132,然后是213,再是231,312最后到321,
//因为是查找紧邻的下一个最大排列,所以即使给的字符为abbc这种带重复字符的,也会自动判断,无需额外去重。
func permutation2(s string) (ans []string) {
t := []byte(s)
// 按照有小到大进行排列,此时处于最小的排列
sort.Slice(t, func(i, j int) bool { return t[i] < t[j] })
for {
//把当前的排列塞入结果集
ans = append(ans, string(t))
//查找下一个最大的排列,如果找不下一个更大的排列,说明已经搜索结束了
if !nextPermutation(t) {
break
}
}
return
}
// 紧邻的下一个更大的排列
func nextPermutation(nums []byte) bool {
n := len(nums)
i := n - 2
//先自己检查(从最后开始检查),是否是由大到小排列,直到找到第一个不是由大到小的字符,记住此时的位置i(i之后都是由大到小排列的)
for i >= 0 && nums[i] >= nums[i+1] {
i--
}
if i < 0 {
//如果已经全部都是由大到小排列了,则返回false
return false
}
j := n - 1
// 借助另一个指针j,逐个判断位置i的字符与j字符,是否符合由大到小排列,如果是,j向前移动
for j >= 0 && nums[i] >= nums[j] {
j--
}
// 直到找到不是由大到小排列的字符j以及当前的i,对i、j进行位置交换,
nums[i], nums[j] = nums[j], nums[i]
// 由于i之后的元素可以确定是由大到小排列的,但我们要的是下一个最大的配列,所以要把i之后的排列进行一个倒排(即反转),
// 比如之前是321,现在我们要123,这样才是紧邻的下一个最大排列
reverse(nums[i+1:])
return true
}
// 反转字符串
func reverse(a []byte) {
for i, n := 0, len(a); i < n/2; i++ {
a[i], a[n-1-i] = a[n-1-i], a[i]
}
}
//复杂度分析
//
//时间复杂度:O(n \times n!)O(n×n!),其中 nn 为给定字符串的长度。我们需要 O(n \log n)O(nlogn) 的时间得到第一个排列,\texttt{nextPermutation}nextPermutation 函数的时间复杂度为 O(n)O(n),我们至多执行该函数 O(n!)O(n!) 次,因此总时间复杂度为 O(n \times n! + n \log n) = O(n \times n!)O(n×n!+nlogn)=O(n×n!)。
//
//空间复杂度:O(1)O(1)。注意返回值不计入空间复杂度。
标签:排列,return,nums,复杂度,perm,法和下,func,回溯 来源: https://www.cnblogs.com/lz0925/p/16483260.html