码迷,mamicode.com
首页 > 其他好文 > 详细

【leetcode 37】解数独

时间:2020-03-11 23:56:28      阅读:91      评论:0      收藏:0      [点我收藏+]

标签:instead   字符   pre   modify   sar   二进制   入队   总结   ===   

1. 解数独

因为年少时喜欢做数独,所以很清楚数独的解题思路,简单总结如下:

  1. 先确定每个空格可能取值
  2. 填写只剩一种可能取值的空格
  3. 更新其他同行,同列,同9宫格的空格可能取值
  4. 重复【2】【3】,出现3中情况:
  • 没有空格 -- 解题成功
  • 任一空格不存在任何可能的取值 -- 本题无解
  • 剩下的空格都存在多个可能取值
  1. 尝试假定某个空格的值,重复【2】【3】【4】

2. 算法和数据结构

有了解题思路,接下来就可以设计算法和数据结构了,注意到上述解题的过程可以简化为【符合条件?出列:重新入队】,因此考虑用队列。另外注意到会遇到步骤【5】,因此需要在检查到整个队列没有符合要求的时候设定一个假设值,当推出无解时回溯到假设前的状态,然后设定下一个假设值。用一个队列记录剩余待求解的空格索引,再用一个哈希表记录每个空格可能的取值。

/**
 * deep clone
 */
var deepClone = function(obj) {
    if (typeof obj!=='object' || obj===null) {
        return obj;
    }
    let ret = Array.isArray(obj) ? [] : {};
    for (let k in obj) {
        ret[k] = deepClone(obj[k]);
    }
    return ret;
}

/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solveSudoku = function(board) {
    let _q = [],_hash = {}, allow = ['1','2','3','4','5','6','7','8','9'];
    // 初始化
    for (let i=0;i<9;i++) {
        for(let j=0;j<9;j++) {
            if (board[i][j] == '.') {
                // 初始化空格的所有可能值
                _hash[i*9+j] = Object.assign([], allow);
            } else {
                // 已填入的值
                _hash[i*9+j] = [board[i][j]];
            }
            _q.push(i*9+j);
        }
    }
    // 只有唯一解,否则会死循环
    function slove(q, hash) {
        while (q.length > 0) {
            let len = q.length;
            // 队尾删除避免索引混乱 (顺序好像也是可以的。。)
            for (let idx = len-1; idx >=0; idx--) {
                if (hash[q[idx]].length == 0) {
                    return false; // 无任何可能取值,本次无解
                }
                if (hash[q[idx]].length == 1) {
                    // 还原索引位置
                    let i = Math.floor(q[idx]/9), j = q[idx]%9;
                    board[i][j] = hash[q[idx]][0];
                    delete hash[q[idx]];
                    q.splice(idx,1);
                    // 删除关联位置的可能取值
                    for(let a in hash) {
                        let r = Math.floor(a/9), c = a%9;
                        if (r==i || c==j || (Math.floor(i/3)==Math.floor(r/3) && Math.floor(j/3)==Math.floor(c/3))){
                            // delete
                            let exist = hash[a].indexOf(board[i][j]);
                            if (exist !== -1) {
                                hash[a].splice(exist, 1);
                            }
                        }
                    }
                } else {
                    len--;  // 计数,
                }
            }
            // 出现多个位置多个可能取值,进入步骤【5】
            if(len == 0) {
                // 出现每个位置都有多个可能值,逐个假设尝试求解
                for (let n =0; n<hash[q[0]].length; n++) {
                    // 克隆状态,循环尝试求解
                    // 之前用了Object.assign()浅拷贝导致我调试了n久
                    let cloneHash = deepClone(hash);
                    let cloneQ = deepClone(q);
                    // 预先设定一个值
                    cloneHash[cloneQ[0]] = [hash[q[0]][n]];
                    if (slove(cloneQ, cloneHash)) {
                        return true;    // 解题成功,否则回溯进入下一个循环
                    }
                }
                // 所有可能值都失败,无解
                return false;
            }
        }
        return true;
    }
    slove(_q, _hash)
    return board;
};

3. 结果与优化

执行用时 :104 ms, 在所有 JavaScript 提交中击败了63.07%的用户
内存消耗 :37.7 MB, 在所有 JavaScript 提交中击败了61.64%的用户

ac之后去看了大神们的题解,收到启发可以使用位标记代替字符串数组优化内存,也就是将[1,2,3,4,5,6,7,8,9]替换为二进制 11111111,比如[1,5,6,8]可以表示成100011010,同理删除取值可以改为位与运算:
删除5: allow & 111101111

【leetcode 37】解数独

标签:instead   字符   pre   modify   sar   二进制   入队   总结   ===   

原文地址:https://www.cnblogs.com/dapianzi/p/12466141.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!