小哆啦解题记:数独有效性判断的奇妙之旅

6 阅读5分钟

小哆啦解题记:数独有效性判断的奇妙之旅

小哆啦开始刷力扣的第二十七天

36. 有效的数独 - 力扣(LeetCode)

一、神秘任务降临

在那充满奇幻色彩的数字魔法世界里,阳光暖暖地洒在魔法广场上,小哆啦正欢快地和数字精灵们玩耍。突然,一道闪耀着神秘光芒的卷轴从天而降,稳稳地落在小哆啦面前。小哆啦好奇地凑上前,轻轻展开卷轴,只见上面写着一个艰巨的任务:判断一个 9 x 9 的数独是否有效。

小哆啦的眼睛瞬间亮了起来,它拍拍胸脯,自信满满地说:“这有什么难的,看我大展身手!” 一旁的小智微笑着提醒道:“可别小看这个任务哦,里面藏着不少学问呢。”

二、暴力解法的初次尝试

(一)灵感闪现

小哆啦找了个安静的角落,盘腿坐下,眼睛紧紧盯着数独题目,大脑飞速运转。不一会儿,它的脸上露出了得意的笑容,想到了一个最直接的办法 —— 暴力检查。它自言自语道:“我只要一行一行、一列一列、一个 3x3 宫格一个 3x3 宫格地去检查数字是否重复,肯定能搞定!”

(二)魔法代码书写

小哆啦兴奋地挥舞着魔法棒,魔法棒顶端闪烁着五彩光芒,在魔法屏幕上敲下了如下 TypeScript 代码:

function isValidSudoku(board: string[][]): boolean {
    // 检查每一行
    for (let i = 0; i < 9; i++) {
        const rowSet = new Set<string>();
        for (let j = 0; j < 9; j++) {
            const num = board[i][j];
            if (num!== '.') {
                if (rowSet.has(num)) {
                    return false;
                }
                rowSet.add(num);
            }
        }
    }

    // 检查每一列
    for (let j = 0; j < 9; j++) {
        const colSet = new Set<string>();
        for (let i = 0; i < 9; i++) {
            const num = board[i][j];
            if (num!== '.') {
                if (colSet.has(num)) {
                    return false;
                }
                colSet.add(num);
            }
        }
    }

    // 检查每一个 3x3 宫格
    for (let boxRow = 0; boxRow < 9; boxRow += 3) {
        for (let boxCol = 0; boxCol < 9; boxCol += 3) {
            const boxSet = new Set<string>();
            for (let i = boxRow; i < boxRow + 3; i++) {
                for (let j = boxCol; j < boxCol + 3; j++) {
                    const num = board[i][j];
                    if (num!== '.') {
                        if (boxSet.has(num)) {
                            return false;
                        }
                        boxSet.add(num);
                    }
                }
            }
        }
    }

    return true;
}

(三)遭遇瓶颈

小哆啦满心期待地运行代码,代码运行时,魔法屏幕上数字如流星般闪烁。可当小哆啦看到运行结果虽然正确,但速度却很慢时,它的眉头皱成了一个 “川” 字。它焦急地跺着脚,嘟囔着:“怎么这么慢呀,这要是遇到更大的数独,不得等到花儿都谢了!”

三、小智的智慧引导

(一)耐心剖析

就在小哆啦急得像热锅上的蚂蚁时,小智慢悠悠地走了过来。他轻轻拍了拍小哆啦的肩膀,说:“小哆啦,别着急。你看你这个方法虽然能解决问题,但是要对整个数独进行三次遍历,就好像你要跑三趟才能完成一件事,自然就慢啦。我们能不能想办法在一次遍历中就完成所有的检查呢?”

小哆啦听了,眼睛里满是疑惑,它挠挠头,问道:“一次遍历?这怎么可能呢?”

(二)巧妙点拨

小智笑着拿出一根魔法粉笔,在地上画起了数独的结构。他指着图说:“我们可以同时记录每一行、每一列和每一个 3x3 宫格中数字的出现情况。用三个数组分别存储行、列和宫格的信息,每个数组元素是一个 Map,用来记录对应位置上数字的出现次数。这样在一次遍历过程中就能完成所有的检查。”

小哆啦仔细地听着,眼睛里渐渐有了光亮,它兴奋地说:“我好像明白了,就像我们可以同时记住很多事情一样,让程序也同时记住行、列和宫格的数字情况!”

四、算法优化的成功逆袭

(一)优化代码诞生

小哆啦迫不及待地拿起魔法棒,根据小智的提示,修改起了代码:

function isValidSudokuOptimized(board: string[][]): boolean {
    const rows: Map<string, number>[] = Array.from({ length: 9 }, () => new Map());
    const cols: Map<string, number>[] = Array.from({ length: 9 }, () => new Map());
    const boxes: Map<string, number>[] = Array.from({ length: 9 }, () => new Map());

    for (let i = 0; i < 9; i++) {
        for (let j = 0; j < 9; j++) {
            const num = board[i][j];
            if (num!== '.') {
                const boxIndex = Math.floor(i / 3) * 3 + Math.floor(j / 3);

                rows[i].set(num, (rows[i].get(num) || 0) + 1);
                cols[j].set(num, (cols[j].get(num) || 0) + 1);
                boxes[boxIndex].set(num, (boxes[boxIndex].get(num) || 0) + 1);

                if (
                    rows[i].get(num)! > 1 ||
                    cols[j].get(num)! > 1 ||
                    boxes[boxIndex].get(num)! > 1
                ) {
                    return false;
                }
            }
        }
    }

    return true;
}

(二)惊喜效果呈现

小哆啦紧张地按下运行按钮,只见魔法屏幕上数字闪烁的速度明显加快,不一会儿就得出了结果。小哆啦兴奋得跳了起来,它抱住小智,大喊道:“成功啦!速度变快好多,太神奇了!”

小智笑着解释道:“现在我们只需要对整个数独进行一次遍历,时间复杂度从  降低到了 ,效率提升了不少呢。在处理大规模数据时,这种优化就会更加明显。”

五、收获与成长

小哆啦看着优化后的代码,心中感慨万千。它明白了,在解决问题时,不能只满足于能得到答案,还要思考如何让方法更加高效。这次的经历就像一把神奇的钥匙,为小哆啦打开了算法优化的大门。

从此,小哆啦在算法探索的道路上更加自信满满,它期待着迎接更多的挑战,用智慧和魔法书写更多精彩的解题故事。