leetcode 37. 解数独 思考分析

193 阅读2分钟

目录

题目

编写一个程序,通过填充空格来解决数独问题。
一个数独的解法需遵循如下规则:

1、数字 1-9 在每一行只能出现一次。
2、数字 1-9 在每一列只能出现一次。
3、数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

空白格用 ‘.’ 表示。

一个数独。 答案被标成红色。

提示:

给定的数独序列只包含数字 1-9 和字符 ‘.’ 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。

核心思路的不断细化

算法的核心思路就是对每一个空着的格子穷举 1 到 9,如果遇到不合法的数字(在同一行或同一列或同一个 3×3 的区域中存在相同的数字)则跳过,如果找到一个合法的数字,则继续穷举下一个空格子。
写出核心框架:

1、核心框架

void solveSudoku(vector<vector<char>>& board) {
    backtrack(board, 0, 0);
}

void backtrack(vector<vector<char>>& board, int hang, int lie) {
    // 就是对棋盘的每个位置进行穷举
    for (int i = hang; i < 9; i++) {
        for (int j = lie; j < 9; j++) {
            // 做选择
            backtrack(board, i, j);
            // 撤销选择
        }
    }
}

2、考虑到每个位置的工作

对于每个位置有1~9的选择,这样就变成了3重嵌套循环。

void backtrack(vector<vector<char>>& board, int hang, int lie) {
    // 就是对每个位置进行穷举
    for (int i = hang; i < 9; i++) {
        for (int j = lie; j < 9; j++) {
            for (char ch = '1'; ch <= '9'; ch++) {
                // 做选择
                board[i][j] = ch;
                // 继续穷举下一列,
                backtrack(board, i, j + 1);
                // 撤销选择
                board[i][j] = '.';
            }
        }
    }
}

3、考虑到到达最后一列、该位置的数已经预置的情况

1、当lie到达超过最后一个索引时,转为增加hang开始穷举下一行。
2、如果当前位置元素不为“.”,那么跳过即可。
3、穷举的时候如果遇到符合规则的数字,填入,否则跳过。

void backtrack(vector<vector<char>>& board, int hang, int lie) {
	if(lie==9)
	{
		backtrack(board, hang+1, 0);
	}
    // 就是对每个位置进行穷举
    for (int i = hang; i < 9; i++) {
        for (int j = lie; j < 9; j++) {
        	// 如果该位置是预设的数字,不用我们操心
            if (board[i][j] != '.') {
                backtrack(board, i, j + 1);
                return;
            } 
            //如果不是预置的数字
            for (char ch = '1'; ch <= '9'; ch++) {
            	// 如果遇到符合规则的数字,填入
                if (isValid(board, i, j, ch)){
	                // 做选择
	                board[i][j] = ch;
	                // 继续穷举下一列,
	                backtrack(board, i, j + 1);
	                // 撤销选择
	                board[i][j] = '.';
                }
            }
        }
    }
}

4、判断是否符合规则的函数

规则如下:

1、数字 1-9 在每一行只能出现一次。
2、数字 1-9 在每一列只能出现一次。
3、数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

翻译成函数:
注意这里对第3个规则的描述:

// 判断 board[i][j] 是否可以填入 n
bool isValid(vector<vector<char>>& board, int hang, int lie, char n) {
    for (int i = 0; i < 9; i++) {
        // 判断行是否存在重复
        if (board[hang][i] == n) return false;
        // 判断列是否存在重复
        if (board[i][lie] == n) return false;
        // 判断 3 x 3 方框是否存在重复
        if (board[(hang/3)*3 + i/3][(lie/3)*3 + i%3] == n)
            return false;
    }
    return true;
}

下面是这个式子的含义,并且将i=0~8模拟了一下:
在这里插入图片描述

5、确定递归终止条件+确定函数返回值

如果递归完最后一行,那么我们就可以返回我们的结果了。
如果找到一个解我们的任务就结束了,本题并没有要求找到所有解。
如果一个位置遍历完所有数字,都不符合,这说明此路不通,应该及时返回false。
如果每个位置都遍历过了,结果都没有返回true,说明,这个数独棋盘没有解,返回false。
所以使用bool类型的值作为返回值:

if(hang == 9) return true;

完整的回溯函数代码应该如下:

bool backtrack(vector<vector<char>>& board, int hang, int lie) {
	//遍历完这一行最后一列,转而遍历下一行
	if(lie==9)
	{
		return backtrack(board, hang+1, 0);
	}
	//找到一个解,返回
	if(hang == 9) return true;
    // 就是对每个位置进行穷举
    for (int i = hang; i < 9; i++) {
        for (int j = lie; j < 9; j++) {
        	// 如果该位置是预设的数字,不用我们操心
            if (board[i][j] != '.') {
                return backtrack(board, i, j + 1);
            } 
            //如果不是预置的数字
            for (char ch = '1'; ch <= '9'; ch++) {
            	// 如果遇到符合规则的数字,填入
                if (isValid(board, i, j, ch)){
	                // 做选择
	                board[i][j] = ch;
	                // 继续穷举下一列,如果找到了一个解,立即结束
	                if(backtrack(board, i, j + 1) == true) return true;
	                // 撤销选择
	                board[i][j] = '.';
                }
            }
			// 穷举完 1~9,依然没有找到可行解,此路不通
            return false;
        }
    }
	return false;
}

AC代码

class Solution {
public:
    // 判断 board[i][j] 是否可以填入 n
    bool isValid(vector<vector<char>>& board, int hang, int lie, char n) {
        for (int i = 0; i < 9; i++) {
            // 判断行是否存在重复
            if (board[hang][i] == n) return false;
            // 判断列是否存在重复
            if (board[i][lie] == n) return false;
            // 判断 3 x 3 方框是否存在重复
            if (board[(hang/3)*3 + i/3][(lie/3)*3 + i%3] == n)
                return false;
        }
        return true;
    }
    bool backtrack(vector<vector<char>>& board, int hang, int lie) {
        //遍历完这一行最后一列,转而遍历下一行
        if(lie==9)
        {
            return backtrack(board, hang+1, 0);
        }
        //找到一个解,返回
        if(hang == 9) return true;
        // 就是对每个位置进行穷举
        for (int i = hang; i < 9; i++) {
            for (int j = lie; j < 9; j++) {
                // 如果该位置是预设的数字,不用我们操心
                if (board[i][j] != '.') {
                   return backtrack(board, i, j + 1);
                } 
                //如果不是预置的数字
                for (char ch = '1'; ch <= '9'; ch++) {
                    // 如果遇到符合规则的数字,填入
                    if (isValid(board, i, j, ch)){
                        // 做选择
                        board[i][j] = ch;
                        // 继续穷举下一列,如果找到了一个解,立即结束
                        if(backtrack(board, i, j + 1) == true) return true;
                        // 撤销选择
                        board[i][j] = '.';
                    }
                }
                // 穷举完 1~9,依然没有找到可行解,此路不通
                return false;
            }
        }
        return false;
    }
    void solveSudoku(vector<vector<char>>& board) {
        backtrack(board, 0, 0);
        return;
    }
};