【leetcode】37. Sudoku Solver 解数独

1. 题目

Write a program to solve a Sudoku puzzle by filling the empty cells.

Empty cells are indicated by the character ‘.’.

You may assume that there will be only one unique solution.
《【leetcode】37. Sudoku Solver 解数独》

A sudoku puzzle…

…and its solution numbers marked in red.

https://leetcode.com/problems…

2. 思路

每次找到最合适填入点进行试探。
每次选择行、列、方三厢里已填充字符最多的空白点上试探。
试探时从1-9试探在当前点是否可行,如果可行,则填入并递归继续。递归成功则完全成功。递归失败则回退试探下一个字符。
如果全部填满了就直接成功。

3. 代码

耗时:49ms

#include <utility>

struct Sudoku {
    // 行 列 九宫格 的一共3*9个9维向量
    char lines[27][9];
    char lz[27];
    int total; // 总的已填充数量
};

struct Pos {
    int k1, k2;
    int i[2];
    int j[2];
};

//int R = 0;

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
        Sudoku sdk;
        init(sdk);
        trans(board, sdk);
        if (trySudoku(sdk)) {
            for (int i = 0; i < 9; i++) {
                vector<char>& v = board[i];
                for (int j = 0; j < 9; j++) {
                    v[j] = sdk.lines[i][j];
                }
            }
        }
        //cout << "R=" << R << endl;
        return;
    }
    
    void init(Sudoku& sdk) {
        sdk.total = 0;
        for (int i = 0; i < 27; i++) {
            sdk.lz[i] = 0;
            for (int j = 0; j < 9; j++) {
                sdk.lines[i][j] = '.';
            }
        }
    }
    
    bool trySudoku(Sudoku& sdk) {
        //R++;
        if (sdk.total == 81) {
            return true;
        }
        
        {
            pair<int, int> ij = optimal9_2(sdk);
            if (ij.first == -1 || ij.second == -1 || sdk.lines[ij.first][ij.second] != '.') { return false; }
            int i = ij.first;
            int j = ij.second;
            
            Pos p; p.k1 = i; p.k2 = j;
            calc(p);
            /*cout << "[POS Try] total=" << sdk.total << " R="<< R << " [" 
                << p.k1 << ", " << p.k2 << "], [" 
                << p.i[0] << ", " << p.j[0] << "], [" 
                << p.i[1] << ", " << p.j[1] << "]\n";
            */
            char ch = '1';
            for (; ch <= '9'; ch++) {
                int jc = 0;
                for (; jc < 9; jc++) {
                    if (sdk.lines[p.k1][jc] == ch
                        || sdk.lines[p.i[0]][jc] == ch
                        || sdk.lines[p.i[1]][jc] == ch) {
                        break;
                    }
                }
                if (jc < 9) { continue; }
                //cout << "[NUM Try] i=" << i << " j=" << j << " ch=" << ch << endl;
                
                sdk.lines[p.k1][p.k2] = ch;
                sdk.lines[p.i[0]][p.j[0]] = ch;
                sdk.lines[p.i[1]][p.j[1]] = ch;
                sdk.total++;
                sdk.lz[p.k1]++;
                sdk.lz[p.i[0]]++;
                sdk.lz[p.i[1]]++;
                
                if (!trySudoku(sdk)) {
                    //cout << "[RBK try] i=" << i << " j=" << j << " ch=" << ch << endl;
                    sdk.lines[p.k1][p.k2] = '.';
                    sdk.lines[p.i[0]][p.j[0]] = '.';
                    sdk.lines[p.i[1]][p.j[1]] = '.';
                    sdk.total--;
                    sdk.lz[p.k1]--;
                    sdk.lz[p.i[0]]--;
                    sdk.lz[p.i[1]]--;
                    continue;
                } else {
                    return true;
                }
            }
            if (ch > '9') { return false; }
        }
        return false;
    }
    
    // 根据p的已有的一个(k1, k2)坐标点,计算出另外两个坐标
    void calc(Pos& p) {
        if (p.k1 < 9) {
        // 行 -》 列和方
            p.i[0] = 9 + p.k2;
            p.j[0] = p.k1;
            p.i[1] = 18 + (p.k1/3)*3 + p.k2/3;
            p.j[1] = (p.k1%3)*3 + p.k2%3;
        } else if (p.k1 < 18) {
        // 列 -> 行和方
            p.i[0] = p.k2;
            p.j[0] = p.k1 - 9;
            p.i[1] = (p.i[0]/3)*3 + p.j[0]/3;
            p.j[1] = 18 + (p.i[0]%3)*3 + p.j[0]%3;
        } else {
        // 方 -》 行和列
            p.i[0] = (p.k1/3)*3 + p.k2/3;
            p.j[0] = (p.k1%3)*3 + p.k2%3;
            p.i[1] = p.j[0];
            p.j[1] = 9 + p.i[0];
        }
    }
    
    // 找到已填充过最长的line进行下一步填充
    int optimal9(Sudoku& sdk) {
        int f = -1;
        for (int i = 0; i < 27; i++) {
            if (sdk.lz[i] != 9 && sdk.lz[i] > f) {
                f = sdk.lz[i];
            }
        }
        return f;
    }
    
    pair<int, int> optimal9_2(Sudoku& sdk) {
        int f = -1;
        pair<int, int> p(-1, -1);
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (sdk.lines[i][j] != '.') { continue; }
                int n1 = sdk.lz[i];
                int n2 = sdk.lz[9+j];
                int n3 = sdk.lz[18+(i/3)*3+j/3];
                
                if (n1 == 8 || n2 == 8 || n3 == 8) {
                    return make_pair(i, j);
                }
                int cf =  n1*n1 + n2*n2 + n3*n3; 
                //int cf = max3(n1, n2, n3);
                //int cf = n1 + n2 + n3;
                if (cf > f) {
                    cf = f;
                    p = make_pair(i, j);
                }
            }
        }
        return p;
    }
    
    int max3(int i, int j , int k) {
        return max(max(i, j), k);
    }
    
    void trans(vector<vector<char>>& board, Sudoku& sdk) {
        for (int i = 0; i < board.size(); i++) {
            vector<char>& v = board[i];
            for (int j = 0; j < v.size(); j++) {
                char ch = v[j];
                bool dot = (ch == '.');
                sdk.lines[i][j] = ch;
                sdk.lines[9+j][i] = ch;
                int k1 = (i/3)*3 + j/3;
                int k2 = (i%3)*3 + j%3;
                sdk.lines[18+k1][k2] = ch;
                if (!dot) {
                    sdk.lz[i]++;
                    sdk.lz[9+j]++;
                    sdk.lz[18+k1]++;
                    sdk.total++;
                }
            }
        }
    }
};

4. 代码2

耗时:66ms, 加入exist表

#include <utility>

struct Sudoku {
    // 行 列 九宫格 的一共3*9个9维向量
    char lines[27][9];
    char lz[27];
    bool exist[27][9];
    int total; // 总的已填充数量
};

struct Pos {
    int k1, k2;
    int i[2];
    int j[2];
};

//int R = 0;

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
        Sudoku sdk;
        init(sdk);
        trans(board, sdk);
        if (trySudoku(sdk)) {
            for (int i = 0; i < 9; i++) {
                vector<char>& v = board[i];
                for (int j = 0; j < 9; j++) {
                    v[j] = sdk.lines[i][j];
                }
            }
        }
        //cout << "R=" << R << endl;
        return;
    }
    
    void init(Sudoku& sdk) {
        sdk.total = 0;
        for (int i = 0; i < 27; i++) {
            sdk.lz[i] = 0;
            for (int j = 0; j < 9; j++) {
                sdk.lines[i][j] = '.';
                sdk.exist[i][j] = false;
            }
        }
    }
    
    bool trySudoku(Sudoku& sdk) {
        //R++;
        if (sdk.total == 81) {
            return true;
        }
        
        {
            pair<int, int> ij = optimal9_2(sdk);
            if (ij.first == -1 || ij.second == -1 || sdk.lines[ij.first][ij.second] != '.') { return false; }
            int i = ij.first;
            int j = ij.second;
            
            Pos p; p.k1 = i; p.k2 = j;
            calc(p);
            /*cout << "[POS Try] total=" << sdk.total << " R="<< R << " [" 
                << p.k1 << ", " << p.k2 << "], [" 
                << p.i[0] << ", " << p.j[0] << "], [" 
                << p.i[1] << ", " << p.j[1] << "]\n";
            */
            char ch = '1';
            for (; ch <= '9'; ch++) {
                /*int jc = 0;
                for (; jc < 9; jc++) {
                    if (sdk.lines[p.k1][jc] == ch
                        || sdk.lines[p.i[0]][jc] == ch
                        || sdk.lines[p.i[1]][jc] == ch) {
                        break;
                    }
                }
                if (jc < 9) { continue; }
                */
                int cv = ch - '1';
                if (sdk.exist[p.k1][cv] || sdk.exist[p.i[0]][cv] || sdk.exist[p.i[1]][cv]) { continue; }
                //cout << "[NUM Try] i=" << i << " j=" << j << " ch=" << ch << endl;
                
                sdk.lines[p.k1][p.k2] = ch;
                sdk.lines[p.i[0]][p.j[0]] = ch;
                sdk.lines[p.i[1]][p.j[1]] = ch;
                sdk.total++;
                sdk.lz[p.k1]++;
                sdk.lz[p.i[0]]++;
                sdk.lz[p.i[1]]++;
                sdk.exist[p.k1][cv] = sdk.exist[p.i[0]][cv] = sdk.exist[p.i[1]][cv] = true;
                
                if (!trySudoku(sdk)) {
                    //cout << "[RBK try] i=" << i << " j=" << j << " ch=" << ch << endl;
                    sdk.lines[p.k1][p.k2] = '.';
                    sdk.lines[p.i[0]][p.j[0]] = '.';
                    sdk.lines[p.i[1]][p.j[1]] = '.';
                    sdk.total--;
                    sdk.lz[p.k1]--;
                    sdk.lz[p.i[0]]--;
                    sdk.lz[p.i[1]]--;
                    sdk.exist[p.k1][cv] = sdk.exist[p.i[0]][cv] = sdk.exist[p.i[1]][cv] = false;
                    continue;
                } else {
                    return true;
                }
            }
            if (ch > '9') { return false; }
        }
        return false;
    }
    
    // 根据p的已有的一个(k1, k2)坐标点,计算出另外两个坐标
    void calc(Pos& p) {
        if (p.k1 < 9) {
        // 行 -》 列和方
            p.i[0] = 9 + p.k2;
            p.j[0] = p.k1;
            p.i[1] = 18 + (p.k1/3)*3 + p.k2/3;
            p.j[1] = (p.k1%3)*3 + p.k2%3;
        } else if (p.k1 < 18) {
        // 列 -> 行和方
            p.i[0] = p.k2;
            p.j[0] = p.k1 - 9;
            p.i[1] = (p.i[0]/3)*3 + p.j[0]/3;
            p.j[1] = 18 + (p.i[0]%3)*3 + p.j[0]%3;
        } else {
        // 方 -》 行和列
            p.i[0] = (p.k1/3)*3 + p.k2/3;
            p.j[0] = (p.k1%3)*3 + p.k2%3;
            p.i[1] = p.j[0];
            p.j[1] = 9 + p.i[0];
        }
    }
    
    // 找到已填充过最长的line进行下一步填充
    int optimal9(Sudoku& sdk) {
        int f = -1;
        for (int i = 0; i < 27; i++) {
            if (sdk.lz[i] != 9 && sdk.lz[i] > f) {
                f = sdk.lz[i];
            }
        }
        return f;
    }
    
    pair<int, int> optimal9_2(Sudoku& sdk) {
        int f = -1;
        pair<int, int> p(-1, -1);
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (sdk.lines[i][j] != '.') { continue; }
                int n1 = sdk.lz[i];
                int n2 = sdk.lz[9+j];
                int n3 = sdk.lz[18+(i/3)*3+j/3];
                
                if (n1 == 8 || n2 == 8 || n3 == 8) {
                    return make_pair(i, j);
                }
                int cf =  n1*n1 + n2*n2 + n3*n3; 
                //int cf = max3(n1, n2, n3);
                //int cf = n1 + n2 + n3;
                if (cf > f) {
                    cf = f;
                    p = make_pair(i, j);
                }
            }
        }
        return p;
    }
    
    int max3(int i, int j , int k) {
        return max(max(i, j), k);
    }
    
    void trans(vector<vector<char>>& board, Sudoku& sdk) {
        for (int i = 0; i < board.size(); i++) {
            vector<char>& v = board[i];
            for (int j = 0; j < v.size(); j++) {
                char ch = v[j];
                bool dot = (ch == '.');
                sdk.lines[i][j] = ch;
                sdk.lines[9+j][i] = ch;
                int k1 = (i/3)*3 + j/3;
                int k2 = (i%3)*3 + j%3;
                sdk.lines[18+k1][k2] = ch;
                if (!dot) {
                    sdk.lz[i]++;
                    sdk.lz[9+j]++;
                    sdk.lz[18+k1]++;
                    sdk.total++;
                    int cv = ch - '1';
                    sdk.exist[i][cv] = sdk.exist[9+j][cv] = sdk.exist[18+k1][cv] = true;
                }
            }
        }
    }
};
    原文作者:knzeus
    原文地址: https://segmentfault.com/a/1190000007298819
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞