本篇文章题目来源于leetcode与代码随想录
哈喽,大家好,我是小雨,今天做了代码随想录的回溯部分,其中连续三个困难题非常有价值,本篇文章就来聊聊这三个困难题:
51. N 皇后 - 力扣(Leetcode)
37. 解数独 - 力扣(Leetcode)
332. 重新安排行程 - 力扣(Leetcode)
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:'Q'表示皇后位置,'.'表示空位置,如图,4 皇后问题存在两个不同的解法。
提示:
1 <= n <= 9
此题是很经典的回溯问题,我们从第一行开始放置皇后,尝试在每个位置放置皇后,然后递归到下一行,继续尝试放置皇后,直到所有行都被填满或无法继续放置皇后为止。如果在某一行无法找到合适的位置放置皇后,则需要回溯到上一行,并尝试在上一行的其他位置放置皇后。在实现回溯算法时,需要使用一个temp数组来记录每一行中皇后的位置,以及一个visited数组来记录当前位置是否可以走到。实现的具体代码如下:
func getResult(temp []int)[]string{
var result []string
n:=len(temp)
for i := 0; i < len(temp); i++ {
var s []byte
for j := 0; j < n; j++ {
if j == temp[i]{
s = append(s,'Q')
}else{
s = append(s,'.')
}
}
result =append(result,string(s))
}
return result
}
func solveNQueens(n int) [][]string {
var temp []int
var result [][]string
visited := make([][]int,n)
for i := 0; i < len(visited); i++ {
visited[i] = make([]int,n)
}
var dfs func(index int)
dfs = func(index int) {
if len(temp) == n {
fmt.Println(temp)
result = append(result,getResult(temp))
return
}
for i := 0; i < n; i++ {
if visited[index][i] > 0 {
continue
}
//修改visited数组
for j := 0; j < n; j++ {
visited[index][j]++
}
for j := 0; j < n; j++ {
visited[j][i]++
}
//斜右下
x,y:=index+1,i+1
for x < n && y < n {
visited[x][y]++
x++
y++
}
//斜左下
x,y = index+1,i-1
for x < n && y >= 0 {
visited[x][y]++
x++
y--
}
//放入temp中
temp = append(temp,i)
dfs(index+1)
//还原visited数组
//修改visited数组
for j := 0; j < n; j++ {
visited[index][j]--
}
for j := 0; j < n; j++ {
visited[j][i]--
}
//斜右下
x,y = index+1,i+1
for x < n && y < n {
visited[x][y]--
x++
y++
}
//斜左下
x,y = index+1,i-1
for x < n && y >= 0 {
visited[x][y]--
x++
y--
}
//还原temp
temp = temp[:len(temp)-1]
}
}
dfs(0)
return result
}
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
1-9
在每一行只能出现一次。1-9
在每一列只能出现一次。1-9
在每一个以粗实线分隔的 3x3
宫内只能出现一次。(请参考示例图)数独部分空格内已填入了数字,空白格用 '.'
表示。
示例 1:
输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
提示:
board.length == 9
board[i].length == 9
board[i][j]
是一位数字或者 '.'
解决数独问题也可以使用回溯算法,类似于 N 皇后问题。从第一个空格开始,尝试在该位置填入数字 1-9,然后递归到下一个空格,继续尝试填入数字,直到所有空格都被填满或无法继续填入数字为止。如果在某一格无法找到合适的数字填入,则需要回溯到上一个空格,并尝试填入其他数字。实现时,我们需要额外定义三个数组,来记录横向竖向和网格的数字出现情况,具体实现如下:
func solveSudoku(board [][]byte) {
hvisited := make([][]int,9)
svisited := make([][]int,9)
xvisited := make([][]int,9)
for i := 0; i < 9; i++ {
hvisited[i] = make([]int,10)
svisited[i] = make([]int,10)
xvisited[i] = make([]int,10)
}
//初始化visited数组
for i := 0; i < len(board); i++ {
for j := 0; j < len(board[i]); j++ {
temp:=board[i][j]
if temp != '.'{
hvisited[i][temp-'0']++
svisited[j][temp-'0']++
xvisited[(i/3)*3+(j/3)][temp-'0']++
}
}
}
var help func(x,y int)(int,int) = func(x, y int) (int, int) {
x += y/9
y %= 9
return x,y
}
var visited func(x,y,i int)bool = func(x,y,i int)bool{
if hvisited[x][i] >0 || svisited[y][i] > 0 || xvisited[(x/3)*3+(y/3)][i]>0{
return true
}else {
return false
}
}
var dfs func(x,y int)bool
dfs = func(x, y int) bool{
if x == 9 {
return true
}
if board[x][y] != '.'{
if dfs(help(x,y+1)){
return true
}
}else{
for i := 1; i <= 9; i++ {
if visited(x,y,i){
continue
}
hvisited[x][i]++
svisited[y][i]++
xvisited[(x/3)*3+(y/3)][i]++
board[x][y] = byte('0'+i)
if dfs(help(x,y+1)){
return true
}
board[x][y] = byte('.')
hvisited[x][i]--
svisited[y][i]--
xvisited[(x/3)*3+(y/3)][i]--
}
}
return false
}
dfs(0,0)
}
给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
["JFK", "LGA"]
与 ["JFK", "LGB"]
相比就更小,排序更靠前。假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
示例 1:
输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出:["JFK","MUC","LHR","SFO","SJC"]
提示:
1 <= tickets.length <= 300
tickets[i].length == 2
fromi.length == 3
toi.length == 3
fromi
和 toi
由大写英文字母组成fromi != toi
在这题中,定义一个hashmap,存储车站与到达车站的映射。首先遍历车票,将车票中出发站和到达站的映射进行存储,从起点开始,依次尝试每一张机票,检查它是否符合要求(即当前机场为起点,且这张机票没有被使用过),如果符合要求,则将其加入当前路径中,并继续搜索下一个机场;如果不符合要求,则回溯到上一个机场,并继续尝试其他机票。当路径长度等于机场数量时,说明我们已经找到了一条合法的路径(注意,题目要求按字典排序返回最小的行程组合,因此如果提前对映射中的到达站进行排序,那么第一个遍历到的结果就是字典序最小的结果),返回结果即可。
func findItinerary(tickets [][]string) []string {
m := make(map[string][]string)
var result []string
var temp []string
var dfs func(s string)
var flag bool
for i := 0; i < len(tickets); i++ {
m[tickets[i][0]] = append(m[tickets[i][0]],tickets[i][1])
}
for _,v:=range m {
sort.Slice(v, func(i, j int) bool {
if v[i] < v[j]{
return true
}else{
return false
}
})
}
temp = append(temp,"JFK")
dfs = func(s string) {
if flag {
return
}
if len(temp) == len(tickets)+1{
result = make([]string,len(temp))
copy(result,temp)
flag = true
return
}
for i,v:=range m[s]{
if v =="-1"{
continue
}
temp = append(temp,v)
m[s][i] = "-1"
dfs(v)
temp = temp[:len(temp)-1]
m[s][i] = v
}
}
dfs("JFK")
return result
}
在做回溯类题目时,最好能够画出树状图,确定回溯路线,对整个题目做个大概的模拟,如果模拟成功,即可开始实现。实现时,定义一些状态信息,随后编写回溯函数,注意,横向的遍历由for循环控制,竖向的递归由dfs函数控制,最后再针对每种类型的题目进行减枝操作即可!