和字母组合差不多的逻辑,但是多了一个k长度的组合,有了这个组合就不能用多重循环了,如果使用多重循环要循环K层,当k很大的时候复杂度就不够了。因此想到另一种暴力的解法回溯。
还是回溯三部曲:
# 1、画图找递归点
图依然有点丑,哈哈
图中可以看到,for横向处理的逻辑应该从1到n,为什么是从1到n呢从测试用例可以看到组合的起点应该是1,所以其实索引startIndex应该是等于1的。
# 2、确定递归出口
递归出口时什么呢?很容易能想到当paths的长度达到k的长度就可以可以处理逻辑了。贴上代码:
if len(paths) == k {
temp := make([]int, len(paths))
copy(temp, paths)
*res = append(*res, temp)
return
}
这里有个坑,初始的时候我是声明了一个临时切片temp,让temp=paths了,输出(1,2)、(1,3)会被(1,4)覆盖掉,这是因为slice是引用类型,在回溯过程中,path 会不断地被修改和回溯,赋值给temp会直接影响temp的值。所以应该直接copy创建新得切片。
# 3、处理for循环内的单层逻辑
单层逻辑只需要把i逐层得加到paths就好了,但是递归得时候不应该是startIndex+1了,如果是startIndex+1得话就会造成字符重复使用,所以应该是i+1。后续就是paths滞空回溯就好。贴代码:
for i := startIndex; i <= n; i++ {
//单层逻辑
paths = append(paths, i)
backtracking(n, k, i+1, paths, res)
paths = paths[:len(paths)-1]
}
最后贴上整段代码:
package main
import "fmt"
func combine(n int, k int) (res [][]int) {
startIndex := 1
backtracking(n, k, startIndex, []int{}, &res)
return
}
func backtracking(n int, k int, startIndex int, paths []int, res *[][]int) {
if len(paths) == k {
temp := make([]int, len(paths))
copy(temp, paths)
*res = append(*res, temp)
return
}
for i := startIndex; i <= n; i++ {
//单层逻辑
paths = append(paths, i)
backtracking(n, k, i+1, paths, res)
paths = paths[:len(paths)-1]
}
}
func main() {
c := combine(4, 2)
fmt.Println(c)
}
但是回溯时间复杂度不理想,后续还想能不能用自顶向下得记忆化搜索或者自底向上得动态规划重构一下。