LeetCode 37.解数独

解数独

LeetCode 37.解数独

解法一

纯暴力方法,未做任何优化

class Solution {
private:
    bool is_valid_place(vector<vector<char>>& board, int x, int y, char ch) {
        //行或者列放入字符ch是否有效
        for (int i = 0; i < 9; i++) {
            if (board[x][i] == ch || board[i][y] == ch) {
                return false;
            }
        }
        // 3x3 box放入字符ch是否有效
        int xstart = x / 3 * 3;
        int ystart = y / 3 * 3;
        for (int i = xstart; i < xstart + 3; i++) {
            for (int j = ystart; j < ystart + 3; j++) {
                if (board[i][j] == ch) {
                    return false;
                }
            }
        }
        return true;
    }
    bool back_track(vector<vector<char>>& board) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                // 找到空位置,开始从1到9,测试哪个数字有效
                if (board[i][j] == '.') {
                    for (int k = 0; k <= 8; k++) {
                        if (is_valid_place(board, i, j, k + '1')) {
                            board[i][j] = k + '1';
                            if (back_track(board) == true) {
                                return true;
                            }
                            board[i][j] = '.';
                        }
                    }
                    //该位置填入任何数字都无效,返回上一层,改变上一层(也就是前一个数)或更上层的
                    //来使这一层填入有效数字
                    return false;
                }
            }
        }
        //程序能运行到这里,说明一个空位置都没有了,上面的程序又可以确定空位置放入的是正确的数字,且题目保证数独有唯一解,所以返回true,回到第31行
        return true;
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        back_track(board);
    }
};

解法二

解法一纯暴力搜索,只要是’.‘的位置,都会从’1’ - '9’挨个放进去判断,直到放入正确的为止。

解法二用三个数组

row数组记录每一行中,数字1-9的使用情况,被使用为false,反之未被使用为true

col数组记录每一列。

box数组记录每一个3x3box。

bool row[9][9] = { false };
bool col[9][9] = { false };
bool box[3][3][9] = { false };

一个重要的数组就是tab,记录哪一个点可以使用,因此直接遍历tab就行。

vector<pair<int, int>>tab;

在这里插入图片描述

  列号-1			1		2		3		4		5		6		7		8		9
  row[4]     	    F       T		F		F		T		T		T		F		T     
  col[4]			F		F		T		T		T		F		F		F		F	
  box[3][3]     	T		F		F		T		T		F		T		F		T
class Solution {
private:
    bool row[9][9] = { false };
    bool col[9][9] = { false };
    bool box[3][3][9] = { false };
    vector<pair<int, int>>tab;
    bool flag = false;

    void back_track(vector<vector<char>>& board, int point) {
        if (point >= tab.size()) {
            flag = true;
            return;//返回到上一层函数的调用,即第23行,这一层结束
        }
        int x = tab[point].first;
        int y = tab[point].second;
        for (char ch = 0; ch <= 8; ch++) {
            if (row[x][ch] && col[y][ch] && box[x / 3][y / 3][ch]) {
                board[x][y] = ch + '1';
                //这一数字使已经在这一行出现,标记为false,不能再出现
                row[x][ch] = false;
                //这一数字使已经在这一列出现,标记为false,不能再出现
                col[y][ch] = false;
                //这一数字使已经在这一3x3的box出现,标记为false,不能再出现
                box[x / 3][y / 3][ch] = false; 
                back_track(board, point + 1);
                if (flag) {
                    //如果能进入这个if,那么会返回上一层back_track()函数的调用,即上一句
                    //此后会一直返回
                    return;
                }
                //运行到这,说明填入的数字不对,数组都要回到填入该数字之前的状态
                board[x][y] = '.';
                //让该行的其他列使用该数字
                row[x][ch] = true;
                //让该列的其他行使用该数字
                col[y][ch] = true;
                //让该3x3的box的其他位置使用该数字
                box[x / 3][y / 3][ch] = true;
            }
        }
    }

public:
    void solveSudoku(vector<vector<char>>& board) {
        memset(row, true, sizeof(row));
        memset(col, true, sizeof(row));
        memset(box, true, sizeof(row));

        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] == '.') {
                    //这个点没有数字,保存它的横纵坐标
                    tab.emplace_back(i, j);
                }
                else {
                    //数字num已经被使用,它所在的行,列,box都要作个标记
                    int num = board[i][j] - '1';
                    row[i][num] = false;
                    col[j][num] = false;
                    box[i / 3][j / 3][num] = false;
                }
            }
        }
        //从第一个空位置开始遍历
        back_track(board, 0);
    }
};

解法三

位运算

方法二中,一个数据是否使用,用一个bool来记录,其实可以用一个大小为9个位的变量val,足够记录1-9的使用情况

在这里插入图片描述

  					1		2		3		4		5		6		7		8		9
  row[4]     	    1       0		1		1		0		0		0		1		0     
  col[4]			1		1		0		0		0		1		1		1		1	
  box[3][3]    		0		0		1		0		0		1		0		1		0
  or				1  		1		1		1		0		1		1		1		1

一个数字字符,例如’3’,减去’1’后,为2,将1左移两位,二进制表示为pos = 0 0000 0100这样用一个位就能表示被使用了。用这个方法处理三个数组,和它所在的行、列、box数组对应的数字进行逻辑异或。

pos & -pos的含义是,取出最低位的位置来用,例如上面的3,如果4,5,6都没有被使用,那pos = 0 0011 1100,进行pos & -pos这个操作后等于num = 0 0000 0100,这个数刚是2k=4,k = 2,字符’1’ + 2 = ‘3’。log2(num) = 2。

pos & pos - 1的含义是,删掉最后一个最低位,还用上面的例子,如果4,5,6都没有被使用,那pos = 0 0011 1100,进行pos & pos - 1操作后,等于 0 0011 1000

这两个操作还可以看我的这篇文章,都有解释

N皇后II C++ 集合 位运算

class Solution {
private:
    short row[9] = { 0 };
    short col[9] = { 0 };
    short box[9][9] = { 0 };
    vector<pair<int, int>>point;
    bool flag = false;
public:

    void lgxor(int x, int y, int num) {
        row[x] ^= num;
        col[y] ^= num;
        box[x / 3][y / 3] ^= num;
    }

    void back_track(vector<vector<char>>& board, int p) {
        if (p >= point.size()) {
            flag = true;//同方法二
            return;
        }
        int x = point[p].first;
        int y = point[p].second;
        // 0x1ff = 1 1111 1111 九个位
        int pos = 0x01ff & ~(row[x] | col[y] | box[x / 3][y / 3]);
        while (0 != pos) {
            int num = pos & (-pos);
            pos &= pos - 1;
            char ch = log2(num);
            board[x][y] = '1' + ch;
            //与num异或,该数字所在行和列和box不能再次出现
            lgxor(x, y, num);
            back_track(board, p + 1);
            if (true == flag) {//同方法二
                return;
            }
            //与num异或,该数字所在行和列和box可以再次出现
            lgxor(x, y, num);
        }
    }

    void solveSudoku(vector<vector<char>>& board) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if ('.' == board[i][j]) {
                    point.emplace_back(i, j);
                }
                else {
                    int num = board[i][j] - '1';
                    lgxor(i, j, 1 << num);
                }
            }
        }
        back_track(board, 0);
    }
};

解法三优化

解法二中,有一些点的数字可以直接确定下来,首先将这些点填好,会省下很多时间。

  					1		2		3		4		5		6		7		8		9
  row[4]     	   	1       0		1		1		0		0		0		1		0     
  col[4]			1		1		0		0		0		1		1		1		1	
  box[3][3]    		0		0		1		0		0		1		0		1		0
  or				1  		1		1		1		0		1		1		1		1

这些点的性质就是,所在行、列、box的数字相或之后,有且仅有一个能放的数字,也就是说进行pos & pos - 1操作,移除最后一个位置后,pos为0。

class Solution {
private:
    short row[9] = { 0 };
    short col[9] = { 0 };
    short box[9][9] = { 0 };
    vector<pair<int, int>>point;
    bool flag = false;
public:

    void lgxor(int x, int y, int num) {
        row[x] ^= num;
        col[y] ^= num;
        box[x / 3][y / 3] ^= num;
    }

    void back_track(vector<vector<char>>& board, int p) {
        if (p >= point.size()) {
            flag = true;
            return;
        }
        int x = point[p].first;
        int y = point[p].second;
        int pos = 0x01ff & ~(row[x] | col[y] | box[x / 3][y / 3]);
        while (0 != pos) {
            int num = pos & (-pos);
            pos &= pos - 1;
            char ch = log2(num);
            board[x][y] = '1' + ch;
            lgxor(x, y, num);
            back_track(board, p + 1);
            if (true == flag) {
                return;
            }
            lgxor(x, y, num);
        }
    }

    void solveSudoku(vector<vector<char>>& board) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if ('.' != board[i][j]) {
                    int num = board[i][j] - '1';
                    lgxor(i, j, 1 << num);
                }
            }
        }

        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if ('.' == board[i][j]) {
                    int pos = 0x01ff & ~(row[i] | col[j] | box[i / 3][j / 3]);
                    if (0 == (pos & (pos - 1))) {
                        int num = log2(pos);
                        lgxor(i, j, 1 << num);
                        board[i][j] = '1' + num;
                    }
                    else {
                        point.emplace_back(i, j);
                    }

                }
            }
        }
        back_track(board, 0);
    }
};

性能对比,用同一个解法解同一个数独500次,看它所耗费的时间,测试很粗糙,不具代表性。

单位,毫秒。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值