编程题 | 解数独问题 (Leetcode 37题)

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

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

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 '.' 表示。

         一个数独                       答案标记为红色

Note:

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


解法一 回溯法

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

    bool backtrack(vector<vector<char>>& board, int row, int col){
        if(col == 9)
            return backtrack(board,row+1,0);

        if(row == 9)
            return true;

        for(int i = row; i < 9; i++){
            for(int j = col; 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))
                        continue;
                    board[i][j] = ch;
                    if(backtrack(board, i, j + 1))
                        return true;
                    board[i][j] = '.';
                }
                return false;

            }
        }
        return false;
    }

    bool isValid(vector<vector<char>>& board, int row, int col, char ch){
        for(int i = 0; i < 9; i++){
            if(board[row][i] == ch) return false;
            if(board[i][col] == ch) return false;
            if(board[(row/3)*3 + i/3][(col/3)*3 + i%3] == ch) return false;
        }
        return true;
    }
};

 


解法二 深度优先搜索

直观来讲,数独问题就是在空中填入数, 并且满足约束条件.
如果说在当前空中没有一个数满足约束条件, 肯定是之前的空填错了,那么就需要回溯.
整个思路按照回溯算法进行, 也就是说在深度搜索的过程中, 需要记录每一步填入数的过程.

所以算法如下:

  1. 找到当前棋盘下的第一个空, 填入满足约束条件的数,入栈
  2. 迭代第1步, 直到出现当前空中没有一个数满足约束条件的情况, 弹栈
  3. 在上一步填入下一个数, 循环第1,2步
  4. 没有空可以填入数,则结束
void print(vector<pair<int, int>> vec) {
    for (auto a : vec) {
        cout << a.first << ' ' << a.second<<" | ";
    }
    cout << endl;
}

template<typename T>
void print(vector<T> vec) {
    for (auto a : vec) {
        cout << a << ' ';
    }
    cout << endl;
}

template<typename T>
void print(vector<vector<T>> vec) {
    for (const auto &v: vec) {
        print(v);
    }
    cout << endl;
}

class Solution {
public:
    using Point = pair<int, int>;

    bool check(int row, int col, char value, vector<vector<char>> &board) {
        int grid_row = row / 3;
        int grid_col = col / 3;
        for (int i = grid_row * 3; i < (grid_row + 1) * 3; i++) {
            for (int j = grid_col * 3; j < (grid_col + 1) * 3; j++) {
                if (value == board[i][j])
                    return false;
            }
        }

        for (int i = 0; i < 9; i++) {
            if (value == board[i][col])
                return false;
        }

        for (int j = 0; j < 9; j++) {
            if (value == board[row][j])
                return false;
        }
        return true;
    }

    Point getNext(int row, int col, vector<vector<char>> &board) {
        Point next = {-1, -1};
        if (row != 9 && col != 9) {
            for (int j = col; j < 9; j++) {
                if (board[row][j] == '.') return make_pair(row, j);
            }
            for (int i = row + 1; i < 9; i++)
                for (int j = 0; j < 9; j++) {
                    if (board[i][j] == '.') return make_pair(i, j);
                }
        }
        return next;
    }

    bool dfs(int row, int col, vector<vector<char>> &board, vector<Point> &stack) {
        auto p = getNext(row, col, board);
        if (p.first == -1) {
            return true;
        }
        bool is_check = false;
        for (char c = '1'; c <= '9'; c++) {
            if (check(p.first, p.second, c, board)) {
                board[p.first][p.second] = c;
                stack.emplace_back(p.first, p.second);
                print(stack);
                print<char>(board);
                if (p.second + 1 == 9) {
                    is_check = dfs(p.first + 1, 0, board, stack);
                } else {
                    is_check = dfs(p.first, p.second + 1, board, stack);
                }
            }
        }
        // 运行到这里, 就会出现没有一个数可以填入当前空, 弹栈
        if (!is_check) {
            auto g = stack[stack.size() - 1];
            stack.pop_back();
            board[g.first][g.second] = '.';
        }
        return is_check;
    }

    vector<vector<char>> solveSudoku(vector<vector<char>> &board) {
        vector<Point> stack;
        dfs(0, 0, board, stack);
//        print<char>(board);
        return board;
    }
};

官方解法  Java 版本

class Solution {
  // box size
  int n = 3;
  // row size
  int N = n * n;

  int [][] rows = new int[N][N + 1];
  int [][] columns = new int[N][N + 1];
  int [][] boxes = new int[N][N + 1];

  char[][] board;

  boolean sudokuSolved = false;

  public boolean couldPlace(int d, int row, int col) {
    /*
    Check if one could place a number d in (row, col) cell
    */
    int idx = (row / n ) * n + col / n;
    return rows[row][d] + columns[col][d] + boxes[idx][d] == 0;
  }

  public void placeNumber(int d, int row, int col) {
    /*
    Place a number d in (row, col) cell
    */
    int idx = (row / n ) * n + col / n;

    rows[row][d]++;
    columns[col][d]++;
    boxes[idx][d]++;
    board[row][col] = (char)(d + '0');
  }

  public void removeNumber(int d, int row, int col) {
    /*
    Remove a number which didn't lead to a solution
    */
    int idx = (row / n ) * n + col / n;
    rows[row][d]--;
    columns[col][d]--;
    boxes[idx][d]--;
    board[row][col] = '.';
  }

  public void placeNextNumbers(int row, int col) {
    /*
    Call backtrack function in recursion
    to continue to place numbers
    till the moment we have a solution
    */
    // if we're in the last cell
    // that means we have the solution
    if ((col == N - 1) && (row == N - 1)) {
      sudokuSolved = true;
    }
    // if not yet
    else {
      // if we're in the end of the row
      // go to the next row
      if (col == N - 1) backtrack(row + 1, 0);
        // go to the next column
      else backtrack(row, col + 1);
    }
  }

  public void backtrack(int row, int col) {
    /*
    Backtracking
    */
    // if the cell is empty
    if (board[row][col] == '.') {
      // iterate over all numbers from 1 to 9
      for (int d = 1; d < 10; d++) {
        if (couldPlace(d, row, col)) {
          placeNumber(d, row, col);
          placeNextNumbers(row, col);
          // if sudoku is solved, there is no need to backtrack
          // since the single unique solution is promised
          if (!sudokuSolved) removeNumber(d, row, col);
        }
      }
    }
    else placeNextNumbers(row, col);
  }

  public void solveSudoku(char[][] board) {
    this.board = board;

    // init rows, columns and boxes
    for (int i = 0; i < N; i++) {
      for (int j = 0; j < N; j++) {
        char num = board[i][j];
        if (num != '.') {
          int d = Character.getNumericValue(num);
          placeNumber(d, i, j);
        }
      }
    }
    backtrack(0, 0);
  }
}

官方解法  Python 版本  

from collections import defaultdict
class Solution:
    def solveSudoku(self, board):
        """
        :type board: List[List[str]]
        :rtype: void Do not return anything, modify board in-place instead.
        """
        def could_place(d, row, col):
            """
            Check if one could place a number d in (row, col) cell
            """
            return not (d in rows[row] or d in columns[col] or \
                    d in boxes[box_index(row, col)])
        
        def place_number(d, row, col):
            """
            Place a number d in (row, col) cell
            """
            rows[row][d] += 1
            columns[col][d] += 1
            boxes[box_index(row, col)][d] += 1
            board[row][col] = str(d)
            
        def remove_number(d, row, col):
            """
            Remove a number which didn't lead 
            to a solution
            """
            del rows[row][d]
            del columns[col][d]
            del boxes[box_index(row, col)][d]
            board[row][col] = '.'    
            
        def place_next_numbers(row, col):
            """
            Call backtrack function in recursion
            to continue to place numbers
            till the moment we have a solution
            """
            # if we're in the last cell
            # that means we have the solution
            if col == N - 1 and row == N - 1:
                nonlocal sudoku_solved
                sudoku_solved = True
            #if not yet    
            else:
                # if we're in the end of the row
                # go to the next row
                if col == N - 1:
                    backtrack(row + 1, 0)
                # go to the next column
                else:
                    backtrack(row, col + 1)
                
                
        def backtrack(row = 0, col = 0):
            """
            Backtracking
            """
            # if the cell is empty
            if board[row][col] == '.':
                # iterate over all numbers from 1 to 9
                for d in range(1, 10):
                    if could_place(d, row, col):
                        place_number(d, row, col)
                        place_next_numbers(row, col)
                        # if sudoku is solved, there is no need to backtrack
                        # since the single unique solution is promised
                        if not sudoku_solved:
                            remove_number(d, row, col)
            else:
                place_next_numbers(row, col)
                    
        # box size
        n = 3
        # row size
        N = n * n
        # lambda function to compute box index
        box_index = lambda row, col: (row // n ) * n + col // n
        
        # init rows, columns and boxes
        rows = [defaultdict(int) for i in range(N)]
        columns = [defaultdict(int) for i in range(N)]
        boxes = [defaultdict(int) for i in range(N)]
        for i in range(N):
            for j in range(N):
                if board[i][j] != '.': 
                    d = int(board[i][j])
                    place_number(d, i, j)
        
        sudoku_solved = False
        backtrack()

 


复杂度分析

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值