JavaScript for循环let声明致数独求解数组修改行为异常问题
问题描述
编写数独谜题求解算法时遇到异常行为:
- 第一版代码运行时无报错,但会直接修改传入的原始数组
- 给for循环的
row、col变量添加let关键字声明后,代码不再修改传入的puzzle形参数组,无法理解let关键字导致该行为差异的原因
复现代码如下:
test = [ [8, 0, 6, 0, 1, 0, 0, 0, 0], [0, 0, 3, 0, 6, 4, 0, 9, 0], [9, 0, 0, 0, 0, 0, 8, 1, 6], [0, 8, 0, 3, 9, 6, 0, 0, 0], [7, 0, 2, 0, 4, 0, 3, 0, 9], [0, 0, 0, 5, 7, 2, 0, 8, 0], [5, 2, 1, 0, 0, 0, 0, 0, 4], [0, 3, 0, 7, 5, 0, 2, 0, 0], [0, 0, 0, 0, 2, 0, 1, 0, 5] ] let result = solve(test) console.log(result) printPuzzle(test) function solve(puzzle) { for (row = 0; row < 9; row++) { for (col = 0; col < 9; col++) { if (puzzle[row][col] === 0) { for (number = 1; number < 9; number++) { if (isValidPlacement(puzzle, number, row, col)) { puzzle[row][col] = number if (solve(puzzle)) { return true } else { puzzle[row][col] = 0 } } } return false } } } return true }
问题原因
不加let/const声明直接赋值的变量,会自动成为全局作用域共享变量,这是两种行为差异的核心原因:
- 无变量声明的版本中,所有递归调用的
solve函数共用同一套全局的row/col/number变量。递归进入下一层求解时,会直接覆盖全局的循环索引值,等回溯返回上一层时,row/col已经不是当初填数的位置坐标,回溯逻辑puzzle[row][col] = 0无法命中之前填入的数字,不能完成状态回退,最终原始数组会残留递归过程中填入的所有临时值,表现为“传入的数组被修改”。 - 加
let声明循环变量的版本中,let会为每一层循环、每一次函数调用生成独立的块级作用域变量,各层递归的row/col/number互不干扰。回溯时能准确定位到之前填数的格子,执行回退操作把临时填入的数字改回初始的0,整个递归流程跑完后,数组状态和传入时完全一致,看起来就像“没有修改传入的形参数组”,本质是所有临时修改都被正确回滚了。
另外代码还存在一个逻辑bug:最内层枚举填数的循环条件写的是number < 9,只会尝试1-8共8个数字,漏掉了数字9,需要改成number <= 9才能正常求解数独。
修复后的核心逻辑
function solve(puzzle) { // 所有循环变量用let声明,绑定独立块级作用域 for (let row = 0; row < 9; row++) { for (let col = 0; col < 9; col++) { if (puzzle[row][col] === 0) { // 修正循环边界,覆盖1-9所有可填数字 for (let number = 1; number <= 9; number++) { if (isValidPlacement(puzzle, number, row, col)) { puzzle[row][col] = number if (solve(puzzle)) { return true } else { // 回溯时用当前作用域的独立坐标,准确回退状态 puzzle[row][col] = 0 } } } return false } } } return true }
内容的提问来源于stack exchange,提问作者eleethesontai
相关产品推荐
相关产品推荐

