力扣每日一题,37. 解数独

做了不少回溯题了,渐渐不看题解也能靠自己的思路通过,今天的数独题自己也能独立通过,虽然速度不快,不过也说明自己对回溯的理解是正确到位的。
力扣每日一题,37. 解数独_第1张图片

文章目录

  • 题目描述
  • 思路
  • 提交代码

题目描述

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

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

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

空白格用 '.' 表示。

一个数独。

答案被标成红色。

Note:

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

思路

用回溯法,和N皇后问题差不多。
回溯法介绍

  • 回溯就是把问题的解空间转化成了图或者树的结构表示,然后使用深度优先遍历,遍历过程中记录和寻找所有可行解或者最优解。
  • 如果完成一件事情有很多种方法,并且每一种方法分成若干步骤,那多半就可以使用回溯算法完成。
  • 基本思想是“尝试搜索”,一条路如果走不通(不能得到想要的结果),就回到上一个“路口”,尝试走另一条路。

回溯法的模板

void backtrace(int i, int n, other parameters) {
    if (i == n) {
        //获取一个答案
        return;
    }
       //一般子结点有多少个就循环多少次
    for (){
        backtrack(i + 1, n, other parameters);
    }
}

回溯法思路其实很"暴力",对这道题,我具体思路是,把数独的所有空格取出来,然后1将放进第一个空格里,若1已存在(在它所在行或所在列或所在的3x3宫内已经有了),就放2,若2也有了就放3…直到这个数放进第一个空格,
接着第二个空格也从1开始放进去,直到放进去的数符合要求。。。同理再放第三个空格、第四个空格。。。
从这个思路可见,这真的很黄很暴力
个人觉得想理解好回溯法,去刷力扣全排列(传送门)那道题挺好。

提交代码

我将空格抽象为一个Blank类,属性有空格的值,在数独中的横纵坐标。

package 解数独;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

public class Solution {
	@Test
	public void testSolveSudoku() throws Exception {
		char nums[][] = { { '5', '3', '.', '.', '7', '.', '.', '.', '.' },
				{ '6', '.', '.', '1', '9', '5', '.', '.', '.' },
				{ '.', '9', '8', '.', '.', '.', '.', '6', '.' },
				{ '8', '.', '.', '.', '6', '.', '.', '.', '3' },
				{ '4', '.', '.', '8', '.', '3', '.', '.', '1' },
				{ '7', '.', '.', '.', '2', '.', '.', '.', '6' },
				{ '.', '6', '.', '.', '.', '.', '2', '8', '.' },
				{ '.', '.', '.', '4', '1', '9', '.', '.', '5' },
				{ '.', '.', '.', '.', '8', '.', '.', '7', '9' } };
		solveSudoku(nums);
		for (int j = 0; j < nums.length; j++) {
			System.out.println(Arrays.toString(nums[j]));
		}
	}

	public void solveSudoku(char[][] board) {
		char chars[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
		List<Blank> blanks = new ArrayList<Solution.Blank>();
		for (int i = 0; i < board.length; i++) {
			for (int j = 0; j < board[0].length; j++) {
				if (board[i][j] == '.') {
					blanks.add(new Blank('.', i, j));
				}
			}
		}
		char res[][] = new char[9][9];
		backtrace(0, blanks.size(), chars, blanks, board, res);
		for (int j = 0; j < board.length; j++) {
			board[j] = Arrays.copyOf(res[j], res[j].length);
		}
	}

	private void backtrace(int i, int n, char chars[], List<Blank> blanks,
			char nums[][], char res[][]) {
		if (i == n) {
			for (int j = 0; j < nums.length; j++) {
				res[j] = Arrays.copyOf(nums[j], nums[j].length);
			}
			return;
		}
		for (int j = 0; j < chars.length; j++) {
			Blank temp = blanks.get(i);
			temp.val = chars[j];
			if (!isUsed(temp.val, temp.x, temp.y, nums)) {
				nums[temp.x][temp.y] = temp.val;
				backtrace(i + 1, n, chars, blanks, nums, res);
				nums[temp.x][temp.y] = '.';
				temp.val = '.';
			}
		}
	}

	class Blank {
		char val;
		int x;
		int y;
		public Blank(char val, int x, int y) {
			this.val = val;
			this.x = x;
			this.y = y;
		}
	}

	private boolean isUsed(char a, int x, int y, char nums[][]) {
		for (int i = 0; i < 9; i++) {
			if (i == x)
				continue;
			if (nums[i][y] == a) {
				// System.out.println(i+" "+y);
				return true;
			}

		}
		for (int j = 0; j < 9; j++) {
			if (j == y)
				continue;
			if (nums[x][j] == a) {
				// System.out.println(x+" "+j);
				return true;
			}
		}
		int istart = 0, jstart = 0, iend = 3, jend = 3;
		if (x > 2 && x < 6) {
			istart = 3;
			iend = 6;
		} else if (x > 5) {
			istart = 6;
			iend = 9;
		}
		if (y > 2 && y < 6) {
			jstart = 3;
			jend = 6;
		} else if (y > 5) {
			jstart = 6;
			jend = 9;
		}
		for (int i = istart; i < iend; i++) {
			for (int j = jstart; j < jend; j++) {
				if (nums[i][j] == a) {
					// System.out.println(i+" "+j);
					return true;
				}
			}
		}
		return false;
	}
}

分享本
算法竞赛入门经典
https://o8.cn/ztnkDg
密码:rdu3
(本地高速是下载器)

你可能感兴趣的:(编程练习题,数独,Java,回溯,算法,力扣)