回溯算法part6(高阶!) | ● 332.重新安排行程 ● 51. N皇后 ● 37. 解数独


332.重新安排行程

332.重新安排行程

思路

递归回溯解决。
由于同地点可能出发两次,并且需要根据字典排序选择小的先出发。
那么使用map记录每个出发点和到达点集合的映射,并且已经使用的票不能再次使用,所以还要记录到达点结合中是否已经有到达过的,自定义pair结构体,维护visited字段。
由于只需要找到一次符合条件的路径就是答案,不需要遍历所有路径,所以递归需要返回值bool。
递归根据传入的出发点,得到到达点数组,从中选取新的出发点,如果新的出发点能够到达,那么就return true,负责继续迭代选取。如果都不能到达,那么return false。
递归终止条件是路径上的节点数目等于票的数目+1,很容易理解,一张票能到达两个地方,两张票三个地方。

思路代码

func findItinerary(tickets [][]string) []string {
    res:=[]string{}
    imap:=make(map[string]pairs)
    for _,v:=range tickets{
        if imap[v[0]]==nil{
            imap[v[0]]=pairs{}
        }
        pair:=&pair{v[1],false}
        imap[v[0]]=append(imap[v[0]],pair)
    }
    for _,v:=range imap{
        sort.Sort(v)
    }
    var backtrack func(name string)bool
    backtrack  = func(name string)bool{
        if len(res)==len(tickets)+1{
            return true
        }
        pairs:=imap[name]
        for i:=0;i<len(pairs);i++{
            if !pairs[i].visited{
                pairs[i].visited=true
                res=append(res,pairs[i].name)
                if backtrack(pairs[i].name){
                    return true
                }                 
                res=res[:len(res)-1]
                pairs[i].visited=false
            }
        }
        return false
    }
    res=append(res,"JFK")
    backtrack("JFK")
    return res
}

type pair struct{
    name string
    visited bool
}

type pairs []*pair

func (p pairs) Len() int{
    return len(p)
}

func (p pairs) Swap(i,j int){
    p[i],p[j]=p[j],p[i]
}

func (p pairs) Less(i,j int) bool{
    return p[i].name<p[j].name
}

困难

选取路径问题可以使用回溯解决。
回溯只需要一条路径,所以递归需要返回值。
需要记录目标点是否被访问过,自定义结构体,维护visited字段。
需要根据字典序选取,所以集合需要实现sort接口,对每个出发点的集合排序。


51. N皇后

51.N皇后

思路

棋盘问题递归回溯
回溯需要所有结果,所以不需要返回值。
递归参数是棋盘的行数,path用[]int数组来记录,并根据此判断是否满足八皇后条件,由于是数字,所以很好判断,最后再将数字结果转换成字符串结果即可。

思路代码

func solveNQueens(n int) [][]string {
    res:=[][]int{}
    path:=[]int{}
    str:=[][]string{}
    var backtrack func(row int)
    backtrack = func(row int){
        if len(path)==n{
            temp:=make([]int,len(path))
            copy(temp,path)
            res=append(res,temp)
            return
        }
        for i:=0;i<n;i++{
            if check(path,i){
                path = append(path,i)
                backtrack(row+1)
                path = path[:len(path)-1]
            }
        }
    }
    backtrack(0)
    for _,v:=range res{
        s:=[]string{}
        for _,v2:=range v{
            s2:=""
            for i:=0;i<n;i++{
                if i==v2{
                    s2=s2+"Q"
                }else{
                    s2=s2+"."
                }
            }
            s=append(s,s2)
        }
        str=append(str,s)
    }
    return str
}

func check(path []int,i int)bool{
    if len(path)==0{
        return true
    }
    for k,v:=range path{
        if v==i{
            return false
        }
        if i-v==len(path)-k||v-i==len(path)-k{
            return false
        }
    }
    return true
}

困难

递归参数是下棋的行数。
check是否满足皇后条件,使用数字数组模拟下棋的路径,check就变得简单了:

func check(path []int,i int)bool{
    if len(path)==0{
        return true
    }
    for k,v:=range path{
        if v==i{
            return false
        }
        if i-v==len(path)-k||v-i==len(path)-k{
            return false
        }
    }
    return true
}

37. 解数独

37.解数独

思路

二维递归,不同于皇后问题,每一行每一个位置都有多个填法。
同样需要返回值bool,用来终止递归。
check函数判断填的数字是否正确。
递归不需要参数了,每次都for循环到能填的第一个位置,然后从1到9不断尝试回溯,直到外层循环走完。

思路代码

func solveSudoku(board [][]byte)  {
    var backtrack func() bool
    backtrack = func() bool{
        for i:=0;i<9;i++{
            for j:=0;j<9;j++{
                if board[i][j]!='.'{
                    continue
                }
                for n:=1;n<=9;n++{
                    if check(i,j,byte('0'+n),board){
                        board[i][j]=byte('0'+n)
                        if backtrack(){
                            return true
                        }
                        board[i][j]='.'
                    }
                }
                return false
            }
        }
        return true
    }
    backtrack()
}

func check(row, col int, k byte, board [][]byte) bool {
	for i := 0; i < 9; i++ {
		if board[row][i] == k {
			return false
		}
	}
	for i := 0; i < 9; i++ {
		if board[i][col] == k {
			return false
		}
	}

	startrow := (row / 3) * 3
	startcol := (col / 3) * 3
	for i := startrow; i < startrow+3; i++ {
		for j := startcol; j < startcol+3; j++ {
			if board[i][j] == k {
				return false
			}
		}
	}
	return true
}

困难

二维递归回溯方法。
其实还有优化空间,比如从哪一行开始填数独,比如从更能确定数字的一行(行列数字最多的一行)开始填数字,大大缩小范围。


今日收获

路径问题,棋盘问题用递归回溯解决。
数独问题二维递归。
重要的是check函数,将其抽取出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值