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皇后
思路
棋盘问题递归回溯
回溯需要所有结果,所以不需要返回值。
递归参数是棋盘的行数,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. 解数独
思路
二维递归,不同于皇后问题,每一行每一个位置都有多个填法。
同样需要返回值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函数,将其抽取出来。