ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-07-16 06:00:08  阅读:134  来源: 互联网

标签:排列 return nums 复杂度 perm 法和下 func 回溯



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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有