递归,回溯,剪枝
对递归的理解很浅,经常困在底层递归中,有推荐看sicp前两章的
记录下题目,斐波那契就跳过了
- 递归函数像是一个黑盒,不要在脑子中思考每一步递归,只需要第一步和最后的return条件,其他中间过程是重复
递归汉诺塔
- 递归
func hanota(A []int, B []int, C []int) []int {
n:=len(A)
dfs(&A,&B,&C,n)
return C
}
//递归函数,a通过b向c移动n个数据
func dfs(a,b,c *[]int,n int){
if n==1{
*c = append(*c,(*a)[len(*a)-1])
*a = (*a)[:len(*a)-1] //截断不包含后面的一个
}
if n>1{
//n-1 通过c放b,1个底层放c,n-1通过a放c
dfs(a,c,b,n-1)
dfs(a,b,c,1)
dfs(b,a,c,n-1)
}
}
39 组合总和
- dfs+回溯剪枝
var res [][]int
var path []int
func combinationSum(candidates []int, target int) [][]int {
res = make([][]int,0)
path = make([]int,0)
dfs(candidates,target,0,0)
return res
}
func dfs(candidates []int,target int,index int,sum int){
if sum>target{
return
}
if sum==target{
tmp:=make([]int,len(path)) //拷贝,直接放path在res,之后的path会改变,导致1最终结果错误
copy(tmp,path)
res = append(res,tmp)
return
}
for i:=index;i<len(candidates);i++{
sum+=candidates[i]
path = append(path,candidates[i])
dfs(candidates,target,i,sum) //注意index为i
path = path[:len(path)-1]
sum-=candidates[i]
}
}
40 组合总和2
题目链接
包含重复元素,可以使用重复,例如
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
需要1排序并设置used数组标识i位置是否已经使用,重复元素前面使用的情况下可以继续使用,如果前面false,则说明已经重复,不再使用
说的有点绕,可以参考
- 回溯+剪枝
import "sort"
var res [][]int
var path []int
func combinationSum2(candidates []int, target int) [][]int {
//只使用一次,相同元素第一个使用的情况可以继续使用,否则不能重复使用,排序+flag
used:=make([]bool,len(candidates))
res=make([][]int,0)
path = make([]int,0)
sort.Ints(candidates)
dfs(candidates,target,0,0,used)
return res
}
func dfs(candidates []int,target int,index int,sum int,used []bool){
if sum>target{
return
}
if sum==target{
tmp:=make([]int,len(path))
copy(tmp,path)
res = append(res,tmp)
}
for i:=index;i<len(candidates);i++{
if i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false{ //此种方式是在同层进行剪枝,前一个使用过则继续使用新的重复元素
continue
}
sum+=candidates[i]
path = append(path,candidates[i])
used[i]=true
dfs(candidates,target,i+1,sum,used)
used[i] = false
path = path[:len(path)-1]
sum-=candidates[i]
}
}
216 组和总和3
和39类似,只是不同重复数字,并且范围到9
- 回溯+剪枝
var res[][]int
var path []int
func combinationSum3(k int, n int) [][]int {
res = make([][]int,0)
path = make([]int,0)
dfs(1,0,k,n)
return res
}
func dfs(index int,sum int,k int,target int){
if sum>target{
return
}
if sum==target&&len(path)==k{
res = append(res,append([]int{},path...))
return
}
for i:=index;i<10;i++{
sum+=i
path = append(path,i)
dfs(i+1,sum,k,target)
path = path[:len(path)-1]
sum-=i
}
}
77 组合
- 类似上题
var res [][]int
var path []int
func combine(n int, k int) [][]int {
//全排列 需要index
res = make([][]int,0)
path = make([]int,0)
dfs(1,n,k)
return res
}
func dfs(index int,n int,k int){
if len(path)==k{
tmp:=make([]int,len(path))
copy(tmp,path)
res = append(res,tmp)
return
}
for i:=index;i<=n;i++{
path = append(path,i)
dfs(i+1,n,k)
path = path[:len(path)-1]
}
}
46 全排列
- 不含重复数字
var res [][]int
var path []int
func permute(nums []int) [][]int {
//不需要index,used记录即可,因为排列1选完后面还可以选前面
used:=make([]bool,len(nums))
res = make([][]int,0)
path = make([]int,0)
dfs(nums,used)
return res
}
func dfs(nums []int,used []bool){
if len(path)==len(nums){
tmp := make([]int,len(path))
copy(tmp,path)
res = append(res,tmp)
return
}
for i:=0;i<len(nums);i++{
if used[i]{
continue
}
path = append(path,nums[i])
used[i] = true
dfs(nums,used)
used[i] = false
path = path[:len(path)-1]
}
}
47 全排列2
- 排序+used标记,前面使用过不再使用.
因为没有index,需要对此次used[i]进行判断,避免重复选取
import "sort"
var res [][]int
var path []int
func permuteUnique(nums []int) [][]int {
//包含重复元素,需要排序并且对前面的剪枝
sort.Ints(nums)
used:=make([]bool,len(nums))
res = make([][]int,0)
path = make([]int,0)
dfs(nums,used)
return res
}
func dfs(nums []int,used []bool){
if len(path)==len(nums){
tmp := make([]int,len(path))
copy(tmp,path)
res = append(res,tmp)
return
}
for i:=0;i<len(nums);i++{
if i>0&&nums[i]==nums[i-1]&&used[i-1]==false{ //前一个没用则不能用此次的,否则重复,[1,1,2],前面1false,则第二个1开头就不用再次计算
continue
}
//此次元素已经使用过则不再使用
if used[i]==true{
continue
}
path = append(path,nums[i])
used[i] = true
dfs(nums,used)
used[i] = false
path = path[:len(path)-1]
}
}
784 字母大小写全排列
- index递归,大小写转换,ascii,a-32=A
var res []string
func letterCasePermutation(s string) []string {
//不算全排列,转换之后进行判断
path:=[]byte(s)
res = make([]string,0)
dfs(path,0)
return res
}
func dfs(path []byte,index int){
//每次更改都放入res
res = append(res,string(path))
for i:=index;i<len(path);i++{
//数字不需要转换
if isNumber(path[i]){
continue
}
//字母转换,之后恢复
if isChar(path[i]){
path[i]-=32 //大小写相差32,小写大
}else{
path[i]+=32
}
dfs(path,i+1)
if isChar(path[i]){
path[i]-=32
}else{
path[i]+=32
}
}
}
//判断是否是数字
func isNumber(str byte)bool{
if str>='0'&&str<='9'{
return true
}else{
return false
}
}
//判断是否是字母,进行大小写转换
func isChar(str byte)bool{
if str>='a'&&str<='z'{
return true
}else{
return false
}
}
78 子集
题目链接
不包含重复元素的子集
- index 递归回溯
var res [][]int
var path []int
func subsets(nums []int) [][]int {
//index递归,包含空集
res = make([][]int,0)
path = make([]int,0)
dfs(nums,0)
return res
}
func dfs(nums []int,index int){
res = append(res,append([]int{},path...))
for i:=index;i<len(nums);i++{
path = append(path,nums[i])
dfs(nums,i+1)
path = path[:len(path)-1]
}
}
90 子集II
包含重复元素,不能重复子集,
- index+排序+used数组去重
import "sort"
var res [][]int
var path []int
func subsetsWithDup(nums []int) [][]int {
//index递归,包含重复元素,需要排序+used数组去除
res = make([][]int,0)
path = make([]int,0)
used:=make([]bool,len(nums))
sort.Ints(nums)
dfs(nums,0,used)
return res
}
func dfs(nums []int,index int,used []bool){
res = append(res,append([]int{},path...))
for i:=index;i<len(nums);i++{
if i>0&&nums[i-1]==nums[i]&&used[i-1]==false{
continue
}
used[i]=true
path = append(path,nums[i])
dfs(nums,i+1,used)
path = path[:len(path)-1]
used[i]=false
}
}
139 单词拆分(*)
判断是否单词是否能分解
- dfs+记忆化
func wordBreak(s string, wordDict []string) bool {
//dfs + 记忆化
// index 每次+1
used := make(map[int]bool,len(s))
hash:=make(map[string]bool)
for _,v:=range wordDict{
hash[v]=true
}
return dfs(s,0,hash,used)
}
//dfs 细节很多
func dfs(s string,index int,hash map[string]bool,used map[int]bool)bool{
if index==len(s){
return true
}
if value,ok:=used[index];ok{ //有值则直接返回
return value
}
for i:=index+1;i<=len(s);i++{ //==len(s) 不包含,
str:=s[index:i] //取字符串
if hash[str]&&dfs(s,i,hash,used){ //i会在下次循环+1
used[i] = true //记录方便复用
return true
}
}
used[index] = false//不存在也要标示
return false
}
- dp
dp[i] 代表0到i-1字符串是否符合,dp[0]=true
func wordBreak(s string, wordDict []string) bool {
//dp
//word放map
hash:=make(map[string]bool,0)
for _,v:=range wordDict{
hash[v]=true
}
dp:=make([]bool,len(s)+1)
dp[0]=true
for i:=1;i<=len(s);i++{ //还可以条件优化 dp[i]==true break. dp[j]==false continue
for j:=i-1;j>=0;j--{
str:=s[j:i] //前段字符,从下表j到i-1
if hash[str]&&dp[j]{ //dp[j] 为0到j-1,刚好覆盖
dp[i] = true
}
}
}
return dp[len(s)]
}
140 单词拆分2 (**)
- hash+dfs
func wordBreak(s string, wordDict []string) []string {
// hash+dfs
hash:=make(map[string]bool)
for _,v:=range wordDict{
hash[v]=true
}
var res []string
var path []string
var dfs func(index int)
dfs = func(index int){
if index==len(s){
res = append(res,strings.Join(path," "))
return
}
for i:=index+1;i<=len(s);i++{
if hash[s[index:i]]{ //符合才继续寻找,否则剪枝
path = append(path,s[index:i])
dfs(i)
path = path[:len(path)-1]
}
}
}
dfs(0)
return res
}
131 分割回文串(*)
dp和dfs结合的经典题目
- dp+dfs
var path []string
var res [][]string
func partition(s string) [][]string {
//dp+fds
path =make([]string,0)
res = make([][]string,0)
dp:=make([][]bool,len(s))
for i:=0;i<len(s);i++{
dp[i] = make([]bool,len(s))
}
//判断是否回文,并且存储
for r:=0;r<len(s);r++{
for l:=0;l<=r;l++{
if s[l]==s[r]&&(r-l<=2||dp[l+1][r-1]){
dp[l][r] = true
}
}
}
dfs(s,0,dp)
return res
}
func dfs(s string,index int,dp [][]bool){
if index==len(s){
tmp:=make([]string,len(path))
copy(tmp,path)
res = append(res,tmp)
return
}
for i:=index;i<len(s);i++{
if dp[index][i]{ //回文则继续
path = append(path,s[index:i+1]) //i不包含则应该+1才是正确范围
dfs(s,i+1,dp)
path = path[:len(path)-1]
}
}
}