Java扫雷递归移除空白区域触发StackOverflowError问题求助
解决扫雷递归移除空白区域的StackOverflowError问题
嘿,我当初写扫雷的递归开空白功能时也踩过一模一样的坑!这个StackOverflowError几乎都是因为递归没有正确的终止条件,或者重复访问同一个格子导致无限递归循环了,咱们一步步来排查和解决:
常见问题原因
- 👉 没有标记已访问的格子:递归遍历周围8个方向时,会反复调用同一个(x,y),比如A格子调用B,B又调用A,无限循环直到栈溢出
- 👉 边界判断有误:你代码里的
leng...看起来像是length的拼写错误?如果边界条件写错(比如y > leng而不是y > length-1),会导致越界后又触发递归 - 👉 没有过滤非空白格子:如果当前格子是雷或者已经显示数字(不是空白),还继续递归的话,也会导致不必要的调用甚至错误
修正后的递归实现方案
首先要给每个格子加一个isVisited标记(或者用格子的状态来判断是否已经打开),确保每个格子只被递归处理一次:
// 假设你有这些成员变量或方法: // int width, length; // 棋盘的宽和高 // boolean[][] isMine; // 标记是否是地雷 // boolean[][] isVisited; // 标记是否已经访问过 // int[][] adjacentMineCount; // 记录相邻地雷数量 // void openCell(int x, int y); // 打开格子的UI逻辑(比如显示数字或空白) public void removeEmptyRegion(int x, int y) { // 1. 第一步:边界检查,超出棋盘范围直接返回 if (x < 0 || x >= width || y < 0 || y >= length) { return; } // 2. 终止条件:如果是地雷、已访问、不是空白(有相邻地雷),直接返回 if (isMine[x][y] || isVisited[x][y] || adjacentMineCount[x][y] > 0) { // 如果是有数字的未访问格子,也要打开它,但不递归 if (!isMine[x][y] && !isVisited[x][y]) { isVisited[x][y] = true; openCell(x, y); } return; } // 3. 标记当前格子为已访问,避免重复递归 isVisited[x][y] = true; // 4. 打开当前空白格子 openCell(x, y); // 5. 递归遍历周围8个方向 removeEmptyRegion(x-1, y-1); // 左上 removeEmptyRegion(x-1, y); // 上 removeEmptyRegion(x-1, y+1); // 右上 removeEmptyRegion(x, y-1); // 左 removeEmptyRegion(x, y+1); // 右 removeEmptyRegion(x+1, y-1); // 左下 removeEmptyRegion(x+1, y); // 下 removeEmptyRegion(x+1, y+1); // 右下 }
更稳妥的迭代方案(避免栈溢出)
如果空白区域特别大(比如整个棋盘都是空白),递归深度会达到几十甚至上百,Java的调用栈很容易爆掉。这种情况下建议用**BFS(广度优先搜索)**的迭代方式来实现:
public void removeEmptyRegion(int startX, int startY) { Queue<int[]> queue = new LinkedList<>(); int[][] directions = {{-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1}}; // 先检查起点是否合法 if (startX < 0 || startX >= width || startY < 0 || startY >= length || isMine[startX][startY] || isVisited[startX][startY]) { return; } // 处理起点 isVisited[startX][startY] = true; queue.add(new int[]{startX, startY}); while (!queue.isEmpty()) { int[] current = queue.poll(); int x = current[0]; int y = current[1]; // 打开当前格子 openCell(x, y); // 遍历所有方向 for (int[] dir : directions) { int newX = x + dir[0]; int newY = y + dir[1]; // 检查新坐标是否合法、未访问、不是地雷 if (newX >= 0 && newX < width && newY >=0 && newY < length && !isVisited[newX][newY] && !isMine[newX][newY]) { isVisited[newX][newY] = true; // 如果是空白格子,加入队列继续遍历 if (adjacentMineCount[newX][newY] == 0) { queue.add(new int[]{newX, newY}); } // 如果是有数字的格子,直接打开,不用加入队列 else { openCell(newX, newY); } } } } }
最后检查点
- 确认你的边界判断里的
length拼写正确(你代码里的leng...可能是笔误) - 确保每次递归前都标记了
isVisited,避免重复调用 - 如果还是有问题,可以打印每次递归的(x,y)坐标,看看是不是进入了循环
内容的提问来源于stack exchange,提问作者Charlotte Cheng
相关产品推荐
相关产品推荐

