全排列

46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]

输出:

[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

分析

分治

自然的思路:如果已经得到前n-1个元素的全排列,再加一个元素的全排列也就不难得到:只需要遍历前n-1个元素全排列,对每个排列,在每个空隙插入新元素。

func permute(nums []int) [][]int {
    res := [][]int{nil}
    for _, v := range nums {
        var tmp [][]int
        for _, s := range res {
            for i := 0; i <= len(s); i++ {
                // 在s的空位i处插入v
                ss := append(append(s[:i:i], v), s[i:]...) 
                tmp = append(tmp, ss)
            }
        }
        res = tmp
    }
    return res
}

也可以写成递归:

func permute(nums []int) [][]int {
    if len(nums) < 2 {
        return [][]int{nums}
    }
    var res [][]int
    v := nums[len(nums)-1]
    for _, s := range permute(nums[:len(nums)-1]) {
        for i := 0; i <= len(s); i++ {
            ss := append(append(s[:i:i], v), s[i:]...)
            res = append(res, ss)
        }
    }
    return res
}

DFS: 填空

将这个问题看作有 n个排列成一行的空格,需要从左往右依次填入题目给定的 n个数,每个数只能使用一次。

那么很直接的可以想到一种穷举的算法,即从左往右每一个位置都依此尝试填入一个数,看能不能填完这 n 个空格,在程序中我们可以用「回溯法」来模拟这个过程。

func permute(nums []int) [][]int {
    var res [][]int
    var path []int
    var backtrack func()
    backtrack = func() {
        if len(path) == len(nums) {
            res = append(res, append([]int{}, path...))
            return
        }
        for _, v := range  nums {
            if contains(path, v) {
                continue
            }
            path = append(path, v)
            backtrack()
            path = path[:len(path)-1]
        }
    }
    backtrack()
    return res
}

func contains(s []int, x int) bool {
    for _, v := range s {
        if v == x {
            return true
        }
    }
    return false
}

递归函数里边用了一个contains方法,非常低效,可以通过备忘来优化:

func permute(nums []int) [][]int {
    var res [][]int
    var path []int
    // 备忘录,记录某次回溯过程中元素是否被访问
    seen := make([]bool, len(nums)) 
    var backtrack func()
    backtrack = func() {
        if len(path) == len(nums) {
            res = append(res, append([]int{}, path...))
            return
        }
        for i, v := range  nums {
            if seen[i] {
                continue
            }
            seen[i] = true
            path = append(path, v)
            backtrack()
            seen[i] = false
            path = path[:len(path)-1]
        }
    }
    backtrack()
    return res
}

DFS: 指定递归起始位置

深度优先搜索,先固定前边几个元素,然后开始尝试排列后边的,这样能逐步降低问题规模。

排列可以通过交换元素实现,参见dfs函数:

func permute(nums []int) [][]int {
    n := len(nums)
    var result [][]int
    // 保持start之前的元素固定不变,将其及其之后的元素全排列
    var dfs func(start int)
    dfs = func(start int) {
        if start == n {
            result = append(result, append([]int{}, nums...))
            return
        }
        for i := start; i < n; i++ { // 将i及其i之后的元素全排列,注意不能漏了i
            nums[start], nums[i] = nums[i], nums[start] // 通过交换做排列
            dfs(start + 1)
            nums[start], nums[i] = nums[i], nums[start]
        }
    }
    dfs(0)
    return result
}

47. 全排列 II

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]
输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

分析

问题与46相似,只是加了元素可能重复的情况,结果不能有重复;

如果用46的第一种解法,需要对结果去重。而46的后两种解法可以事先去重。

DFS: 填空

用上一问题的填空法,可以事先对nums排序,递归过程中去重。

func permuteUnique(nums []int) [][]int {
    var res [][]int
    sort.Ints(nums) // 事先排序
    n := len(nums)
    cur := []int{}
    seen := make([]bool, n)
    var dfs func()
    dfs = func() {
        if len(cur) == n {
            res = append(res, append([]int{}, cur...))
            return
        }
        for i, v := range nums {
            if seen[i] || i > 0 && v == nums[i-1] && !seen[i-1] { // 注意这里的 !seen[i-1]
                continue
            }
            seen[i] = true
            cur = append(cur, v)
            dfs()
            seen[i] = false
            cur = cur[:len(cur)-1]
        }
    }
    dfs()
    return res
}

DFS: 指定递归起始位置

递归时用set去重, 具体在交换 start 处元素与后边元素的时候,看看是否已有相同的元素参与过交换,已经参与过的跳过。

func permuteUnique(nums []int) [][]int {
    n := len(nums)
    var res [][]int
    // 保持start之前的元素固定不变,将其及其之后的元素全排列
    var dfs func(int)
    dfs = func(start int) {
        if start == n {
            res = append(res, append([]int{}, nums...))
            return
        }
        visited := make(map[int]bool, n-start)
        for i := start; i < n; i++ { // 将start及其之后的元素全排列,注意不能漏了start
            if visited[nums[i]] {
                continue
            }
            visited[nums[i]] = true
            nums[start], nums[i] = nums[i], nums[start] // 通过交换做排列
            dfs(start + 1)
            nums[start], nums[i] = nums[i], nums[start]
        }
    }
    dfs(0)
    return res
}