1. 程式人生 > 其它 >方格填數(dfs)

方格填數(dfs)

技術標籤:藍橋杯遞迴與搜尋

1. 問題描述:

格填數
如下的10個格子
+--+--+--+
| | | |
+--+--+--+--+
| | | | |
+--+--+--+--+
| | | |
+--+--+--+
(如果顯示有問題,也可以參看【圖1.jpg】)填入0~9的數字。要求:連續的兩個數字不能相鄰。(左右、上下、對角都算相鄰)一共有多少種可能的填數方案?


請填寫表示方案數目的整數。
注意:你提交的應該是一個整數,不要填寫任何多餘的內容或說明性文字。

2. 思路分析:

① 分析可以知道我們需要將0-9這個數字填入到如圖所示的方格中,並且需要滿足左右/上下/對角方向上不相鄰,所以我們需要嘗試所有的可能才能夠知道最終滿足條件的數目,很明顯可以使用dfs解決,dfs能夠搜尋所有的可能性。對於這種方格填數字的問題一般使用的是在for迴圈中進行遞迴,這樣我們就可以對當前的(x, y)位置嘗試填入0-9這個數字,檢查當前填入的數字是否存在衝突如果存在衝突那麼嘗試其他的數字進行填入。因為使用的是python語言,所以在一開始的時候可以宣告長度為10的列表來記錄之前已經使用過哪些數字,對於使用過的數字我們標記為1,這樣下一次嘗試填入數字的時候就可以知道當前嘗試填入的數字是否與存在重複,如果之前使用過這個數字那麼需要嘗試其他的數字,如果沒有使用過這個數字那麼就需要檢查是否存在衝突,因為方向是相互的,所以對於當前填入數字的對應位置只需要檢查左邊/上邊/左上角/右上角的位置是否存在衝突即可,因為是需要檢查當前位置的相鄰的四個位置上的數字,所以我們在一開始的時候需要宣告一個二維列表來記錄遞迴的時候填入的數字,這樣就可以使用這個列表來檢查是否存在衝突,並且填完10個數字之後可以輸出對應的值檢視是否正確,我們可以宣告一個3 * 4的二維列表,其中第一個位置與最後一個位置是沒有用的,我們在判斷是否存在衝突的時候檢查左邊/上邊/左上角這三個位置的時候可以忽略掉這些位置

② 總的來說是在for迴圈中進行遞迴,嘗試0-9的數字填入到當前(x,y)位置,檢查當前填入的數字是否存在衝突,如果不存在衝突那麼可以填入當前的數字,這道題目其實是典型的dfs應用的題目,都是比較常規的套路,答案是1580

③ 第二種思路是使用全排列的思想,我們可以先生成1-10的全排列,然後在遞迴的出口判斷生成的全排列的對應位置是否存在衝突如果不存在衝突那麼計數加1即可

3. 程式碼如下:

from typing import List


class Solution:
    count = 0
    # 只需要判斷當前填入的數字的左邊/上邊/左上角/右上角是否相鄰即可
    def check(self, n: int, x: int, y: int, res: List[List[int]], vis: List[int]):
        # 因為第一個數字res[0][0]是不需要進行檢查的所以在判斷的時候需要篩選掉: res對應位置不等於-1表示的是當前的位置不是第一個位置
        if y - 1 >= 0 and res[x][y - 1] != -1 and abs(n - res[x][y - 1]) == 1: return False
        if x - 1 >= 0 and res[x - 1][y] != -1 and abs(n - res[x - 1][y]) == 1: return False
        if x - 1 >= 0 and y - 1 >= 0 and res[x - 1][y - 1] != -1 and abs(n - res[x - 1][y - 1]) == 1: return False
        # 右上角的位置根本不可能為x = 0 y = 0的位置所以不用加上res對應位置是否等於-1的條件
        if x - 1 >= 0 and y + 1 < len(res[0]) and abs(n - res[x - 1][y + 1]) == 1: return False
        return True

    def dfs(self, x: int, y: int, res: List[List[int]], vis: List[List[int]]):
        if x == 2 and y == 2:
            for i in range(10):
                if vis[i]: continue
                # 檢查最後一個數字
                if self.check(i, x, y, res, vis):
                    self.count += 1
                    res[x][y] = i
                    print("  ", end="")
                    print(res[0][1], res[0][2], res[0][3])
                    print(res[1][0], res[1][1], res[1][2], res[1][3])
                    print(res[2][0], res[2][1], res[2][2])
                    print()
                    return
            return
        for i in range(10):
            # 當前的數字之前已經使用過了
            if vis[i] == 1: continue
            if self.check(i, x, y, res, vis):
                vis[i] = 1
                res[x][y] = i
                # 二維方格中的遞迴經常使用下面的方式進行換行
                self.dfs(x + (y + 1) // 4, (y + 1) % 4, res, vis)
                # 回溯
                vis[i] = 0
                res[x][y] = -1

    def squareFill(self):
        # res用來記錄遞迴過程中填入的數字
        res = [[-1] * 4 for i in range(3)]
        # print(res)
        # vis用來記錄遞迴過程中使用過的數字
        vis = [0] * 10
        self.dfs(0, 1, res, vis)
        return self.count


if __name__ == '__main__':
    print(Solution().squareFill())