毎日の歯磨きの質問 (5) - 過去の質問: N 女王、数独を解く、旅程を再調整する

この記事のタイトルは、leetcode と code のランダムな考えから来ています。

序文

こんにちは、Xiaoyu です。今日はコードのランダム録音のバックトラック部分を行いました。3 つの連続した難問は非常に貴重です。この記事では、これら 3 つの難問について説明します。

51. クイーンN - リートコード

37. 数独を解く - Leetcode

332. 旅程の再調整 - Leetcode

質問51. クイーンN

チェスのルールによれば、クイーンは、クイーンと同じ行または列、または同じ対角線上にある駒を攻撃できます。

n クイーン問題は、クイーンが互いに攻撃できないようにチェス盤上にnn 個のクイーンを配置する方法を研究します。n×n

整数を指定すると、 n-queens 問題に対する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
	
}

質問 37. 数独を解く

空白を埋めて数独の問題を解くプログラムを作成してください。

Sudoku の解法は次のルールに従う

  1. 1-9数字は1 行に 1 回だけ表示できます。
  2. 数字は各列1-9に。
  3. 1-9数字は、3x3太い実線で区切られた各宮殿に 1 回だけ表示されます。(サンプル画像をご参照ください)

数独部分の空欄には数字が埋められており、空欄は で'.'示されて。

例 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]一桁であるか、'.'
  • 質問データ入力された数独の解が 1 つだけであることを保証する

解決

数独の問題を解く場合、N クイーン問題と同様に、バックトラッキング アルゴリズムを使用することもできます。最初のスペースから開始して、その位置に 1 ~ 9 の数字を入力してから、次のスペースに再帰的に進み、すべてのスペースが埋まるか、それ以上数字を入力できなくなるまで、数字の入力を続けます。特定のスペースに入力するのに適切な数字が見つからない場合は、前のスペースに戻って他の数字を入力してみる必要があります。実装する場合、水平、垂直、グリッド内の数値の出現を記録するために 3 つの追加の配列を定義する必要があります。具体的な実装は次のとおりです。

コード

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

質問332. スケジュールを変更する

飛行ルートのリストが表示されtickets、飛行機が出発および着陸する空港はどこtickets[i] = [fromi, toi]ですか。旅程を再計画してください。

これらのチケットはすべて (JFK) から出発する 1 人の氏に属しているJFKため、旅行は からJFK開始する。有効な旅程が複数ある場合は、辞書に従って最小の旅程の組み合わせを返してください。

  • たとえば、["JFK", "LGA"]旅行["JFK", "LGB"]より小さく、 よりも上位に並べ替えられます。

すべての航空券には、少なくとも 1 つの適切な旅程が含まれていると想定されます。また、すべてのチケットは 1 回だけ使用する必要があります。

例 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

解決

この質問では、駅と到着駅間のマッピングを保存するハッシュマップを定義します。まずチケットを調べ、出発駅と到着駅のマッピングをチケットに保存し、出発点から開始し、各チケットを順番に試して、要件を満たしているかどうかを確認します (つまり、現在の空港が出発点であり、条件を満たしている場合は現在のルートに追加され、次の空港の検索を続けます。条件を満たしていない場合は、前の空港に戻って検索を続けます。他のチケット。パスの長さが空港の数と等しい場合、それは正当なパスが見つかったことを意味します (タイトルでは辞書ソートによって最小の旅程の組み合わせを返す必要があることに注意してください。そのため、マップ内の到着駅が次のようにソートされている場合)先に進み、最初に通過した結果が辞書編集順で最も小さい結果になります)、結果を返すだけです。

コード

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 関数によって制御されることに注意してください。最後に、分岐削減操作は、各タイプに対して実行できます。トピック!

おすすめ

転載: blog.csdn.net/doreen211/article/details/129496586