其他分享
首页 > 其他分享> > 字符串全排列【回溯法和下一个排列】两种解法详解

字符串全排列【回溯法和下一个排列】两种解法详解

作者:互联网


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