【回溯 状态压缩 深度优先】37. 解数独

本文涉及知识点

回溯 状态压缩 深度优先

LeetCode37. 解数独

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

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

数字 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] 是一位数字或者 '.'

题目数据 保证 输入数独仅有一个解

回溯

vRow[i] 记录第i行可以选择那些数,vCol[i]和vCell类型。

vRow[i] & ( 1 << j) 表示第i行,可以选择数组j。

直接将选择结果修改到board上。

vector<tuple<int,int,int>> vSel。 i1,记录可以选择的数量,i2记录行号,i3记录列号。注意:只需要记录能修改的数组。 初始化结束后,对vSel排序。理论上:只有一种选择的先选快点。实际上几乎无影响。

用深度优先实现。Fill 函数填写某行某列,UnFill 恢复某行某列原装。
时间复杂度:不好计算。

代码

核心代码

cpp 复制代码
class CBitCounts
{
public:
	CBitCounts(int iMaskCount)
	{
		for (int i = 0; i < iMaskCount; i++)
		{
			m_vCnt.emplace_back(bitcount(i));
		}
	}
	template<class T>
	static int bitcount(T x) {
		int countx = 0;
		while (x) {
			countx++;
			x &= (x - 1);
		}
		return countx;
	}
	vector<int> m_vCnt;
};

class Solution {
public:
	void solveSudoku(vector<vector<char>>& board) {
		m_board = board;
		int mask = 0;
		for (int i = 1; i <= 9; i++) {
			mask |= (1 << i);
		}
		for (int i = 0; i < 9; i++) {
			m_rows[i] = m_cols[i] = m_cells[i] = mask;
		}
		
		for (int r = 0; r < 9; r++) {
			for (int c = 0; c < 9; c++) {
				if ('.' == board[r][c]) { continue; }
				Fill(r, c, board[r][c] - '0');
			}
		}
		vector<tuple<int, int, int,int>> vSel;
		for (int r = 0; r < 9; r++) {
			for (int c = 0; c < 9; c++) {
				if ('.' != board[r][c]) { continue; }
				int iCell = r / 3 * 3 + c / 3;
				int mask = m_rows[r] & m_cols[c] & m_cells[iCell];
				vSel.emplace_back(CBitCounts::bitcount(mask), r, c,iCell);
			}
		}
		sort(vSel.begin(), vSel.end());
		DFS(vSel, 0);
		board = m_board;
	}
	bool DFS(const vector<tuple<int, int, int,int>> vSel, int leve) {
		if (vSel.size() == leve) { return true; }
		const auto& [tmp, r, c, iCell] = vSel[leve];
		int mask = m_rows[r] & m_cols[c] & m_cells[iCell];
		for (int i = 1; i <= 9; i++) {
			if (mask & (1 << i)) {
				Fill(r, c, i);
				if (DFS(vSel, leve + 1)) { return true; }
				UnFill(r, c, i);
			}
		}
		return false;
	}
	void Fill (int r, int c, int val) {
		m_board[r][c] = val + '0';
		m_rows[r] &= ~(1 << val);
		m_cols[c] &= ~(1 << val);
		int iCell = r / 3 * 3 + c / 3;
		m_cells[iCell] &= ~(1 << val);
	};
	void UnFill(int r, int c, int val) {
		m_board[r][c] = '.';
		m_rows[r] |= (1 << val);
		m_cols[c] |= (1 << val);
		int iCell = r / 3 * 3 + c / 3;
		m_cells[iCell] |= (1 << val);
	};
	vector<vector<char>> m_board;
	int m_rows[9], m_cols[9], m_cells[9];
};

测试用例

cpp 复制代码
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
	if (v1.size() != v2.size())
	{
		assert(false);
		return;
	}
	for (int i = 0; i < v1.size(); i++)
	{
		assert(v1[i] == v2[i]);
	}
}

template<class T>
void Assert(const T& t1, const T& t2)
{
	assert(t1 == t2);
}

int main()
{
	vector<vector<char>> board;
	{
		Solution slu;
		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' }};
		 slu.solveSudoku(board);
		 vector<vector<char>> board1={ {'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' }};
		Assert(board1, board);
	}	
}

2023年5月代码

记录已经选择的数,这样初始化简单。用二维数组记录3 × \times × 3 网格的情况,减少计算网格号。

cpp 复制代码
class Solution {
public:
	void solveSudoku(vector<vector<char>>& board) {
		memset(m_aRows, 0, sizeof(m_aRows));
		memset(m_aCols, 0, sizeof(m_aCols));
		memset(m_aBlock, 0, sizeof(m_aBlock));
		
		for (int r = 0; r < 9; r++)
		{
			for (int c = 0; c < 9; c++)
			{
				const char& ch = board[r][c];
				if ('.' == ch)
				{
					m_vNeedDoRowCols.emplace_back(r, c);
					continue;
				}
				Add(r, c, ch - '1');
			}
		}
		dfs(board, 0);
	}
	bool dfs(vector<vector<char>>& board,int iLeve)
	{
		if (m_vNeedDoRowCols.size() == iLeve)
		{
			return true;
		}
		const int r = m_vNeedDoRowCols[iLeve].first;
		const int c = m_vNeedDoRowCols[iLeve].second;
		int iMask = m_aRows[r] | m_aCols[c] | m_aBlock[r/3][c/3];
		for (int i = 0; i < 9; i++)
		{
			if (iMask & (1 << i))
			{
				continue;
			}
			Add(r, c, i);
			board[r][c] = '1' + i;
			if (dfs(board, iLeve + 1))
			{
				return true;
			}
			board[r][c] = '.';
			Erase(r, c, i);
		}
		return false;
	}
	void Add(int r, int c, int iNum)
	{
		const int iMask = 1 << iNum;
		m_aRows[r] |= iMask;
		m_aCols[c] |= iMask;
		m_aBlock[r / 3][c / 3] |= iMask;
	}
	void Erase(int r, int c, int iNum)
	{
		const int iMask = 1 << iNum;
		m_aRows[r] -= iMask;
		m_aCols[c] -= iMask;
		m_aBlock[r / 3][c / 3] -= iMask;
	}
	int m_aRows[9],m_aCols[9];
	int m_aBlock[3][3];
	vector<std::pair<int, int>> m_vNeedDoRowCols;
};
相关推荐
诚丞成35 分钟前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
清梦202038 分钟前
经典问题---跳跃游戏II(贪心算法)
算法·游戏·贪心算法
Dream_Snowar1 小时前
速通Python 第四节——函数
开发语言·python·算法
Altair澳汰尔1 小时前
数据分析和AI丨知识图谱,AI革命中数据集成和模型构建的关键推动者
人工智能·算法·机器学习·数据分析·知识图谱
东风吹柳1 小时前
观察者模式(sigslot in C++)
c++·观察者模式·信号槽·sigslot
A懿轩A2 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
Python机器学习AI2 小时前
分类模型的预测概率解读:3D概率分布可视化的直观呈现
算法·机器学习·分类
吕小明么3 小时前
OpenAI o3 “震撼” 发布后回归技术本身的审视与进一步思考
人工智能·深度学习·算法·aigc·agi
大胆飞猪3 小时前
C++9--前置++和后置++重载,const,日期类的实现(对前几篇知识点的应用)
c++
1 9 J3 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法