每日刷题(5)——回溯问题:N皇后、解数独、重新安排行程

本篇文章题目来源于leetcode与代码随想录

前言

哈喽,大家好,我是小雨,今天做了代码随想录的回溯部分,其中连续三个困难题非常有价值,本篇文章就来聊聊这三个困难题:

51. N 皇后 - 力扣(Leetcode)

37. 解数独 - 力扣(Leetcode)

332. 重新安排行程 - 力扣(Leetcode)

Question 51. N 皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。

示例 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:'Q'表示皇后位置,'.'表示空位置,如图,4 皇后问题存在两个不同的解法。

提示:

  • 1 <= n <= 9

Solution

此题是很经典的回溯问题,我们从第一行开始放置皇后,尝试在每个位置放置皇后,然后递归到下一行,继续尝试放置皇后,直到所有行都被填满或无法继续放置皇后为止。如果在某一行无法找到合适的位置放置皇后,则需要回溯到上一行,并尝试在上一行的其他位置放置皇后。在实现回溯算法时,需要使用一个temp数组来记录每一行中皇后的位置,以及一个visited数组来记录当前位置是否可以走到。实现的具体代码如下:

Code

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
	
}

Question 37. 解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 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] 是一位数字或者 '.'
  • 题目数据 保证 输入数独仅有一个解

Solution

解决数独问题也可以使用回溯算法,类似于 N 皇后问题。从第一个空格开始,尝试在该位置填入数字 1-9,然后递归到下一个空格,继续尝试填入数字,直到所有空格都被填满或无法继续填入数字为止。如果在某一格无法找到合适的数字填入,则需要回溯到上一个空格,并尝试填入其他数字。实现时,我们需要额外定义三个数组,来记录横向竖向和网格的数字出现情况,具体实现如下:

Code

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)
	
}

Question 332. 重新安排行程

给你一份航线列表 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
  • fromitoi 由大写英文字母组成
  • fromi != toi

Solution

在这题中,定义一个hashmap,存储车站与到达车站的映射。首先遍历车票,将车票中出发站和到达站的映射进行存储,从起点开始,依次尝试每一张机票,检查它是否符合要求(即当前机场为起点,且这张机票没有被使用过),如果符合要求,则将其加入当前路径中,并继续搜索下一个机场;如果不符合要求,则回溯到上一个机场,并继续尝试其他机票。当路径长度等于机场数量时,说明我们已经找到了一条合法的路径(注意,题目要求按字典排序返回最小的行程组合,因此如果提前对映射中的到达站进行排序,那么第一个遍历到的结果就是字典序最小的结果),返回结果即可。

Code

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函数控制,最后再针对每种类型的题目进行减枝操作即可!

你可能感兴趣的:(每日刷题,leetcode,算法,深度优先)