[leetcode] sudoku solver:暴力还是优化

2024-04-09 01:08

本文主要是介绍[leetcode] sudoku solver:暴力还是优化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. backtracking

Sudoku是典型的backtracking问题,有关backtracking的问题《The Algorithm Design Manual》 7.1章解释的最详细易懂。Backtracking的定义如下:

Backtracking is a systemic way to iterate through all the possible configurations of a search space.

简而言之,backtracking就是通过遍历所有组合,并从中找出符合条件的结果集的一种方法。因此,一种非常直观的backtracking算法可以描述如下:

// array: 保存了[0, step-1]每一步的选择
// step: 当前是第几步backtracking(array, step)
{// 判断前step步的选择是否可以构成一个结果if (is_a_solution(array, step)) {process_solution(array, step);} else {step++;// 获取下一步的所有选择condidates = construct_candidates(array, step);foreach c in condidates {// 对每一种选择,继续递归下去array[step] = c;make_move(array, step);backtracking(array, step);unmake_move(array, step);}}
}

其中重要函数有这样几个:

  • is_a_solution:判断当前组合是否是一个期望的结果
  • process_solution:处理结果,例如打印出来
  • construct_candidates:获取下一步的所有选择。注意,这里并没有说明如何选择下一步该如何进行,大多数情况下,这个函数还应当有选择下一步的功能
  • make_move:向前进一步。通常这意味着在array中填充这一步的所选择的的值
  • unmake_move:向后退一步。通常这意味着在array中将这一步的值清空

对于数独问题,套用上面的算法,大概步骤如下:

#define DIMENSION 3;
bool solve(vector<vector<char> > &board, int step) {// 1. is_a_solutionif (step == DIMENSION*DIMENSION*9) return true;// 2. get next square and its all candidatesint row = 0, col = 0;   // position to move next, start from 0;set<char> possible_values;get_next_square(board, row, col, possible_values, step);for (set<char>::iterator it = possible_values.begin(); it != possible_values.end(); ++it) {board[row][col] = *it;				// make_moveif (solve(board, step+1)) return true;board[row][col] = EMPTY;			// unmake_move}return false;
}

2. 如何选择下一步

backtracking主要有两种应用:

  • 获取所有组合。典型问题:
    • Letter Combinations of a Phone Number:给出电话号码,求电话号码对应的所有字符串
    • Generate Parentheses:求所有n对括号"()"组成的字符串
    • Combination Sum:给定一组数字和一个target值,求所有和等于target的组合(组合中每个数字可以出现多次)
    • Combination Sum2:和Combination Sum问题一样,区别是组合中每个数字只能出现一次
    • Permutations:求一组数字的全排列
    • Permutations2:和Permutations问题一样,区别是给定的一组数字有重复,并且要求结果集中不能有重复的组合
  • 从所有的组合中找出符合条件的结果集。典型问题:
    • Sudoku Solver:数独解

第一类问题需要遍历所有解,有些特殊的情况无非是结果集中需要去重,这些都可以通过精细地选择”下一步的值“来做到。例如,在每一步中,可以对”这一步可选的值“做排序,相同的值只选一次,这样可以解决绝大多数”结果集去重“问题(例如Combination Sum2和Permutations2)

第二类问题与第一类问题有着根本不同。第二类问题可以在遍历一组组合的过程中,如果发现当前的组合已经不可能满足条件,则无需遍历完,即可在中途丢提当前的组合,直接跳到下一种组合。

考虑数独问题,首先,如果我们在构造一个组合的过程中,发现某个格子填入任何值都不可能满足条件,那么当前的组合无需再计算下去,必然是之前某些步出错。无需再计算当前还没有填充的其他格子的值,直接丢弃当前解,跳到上一步尝试其他值即可。

说道这里,其实还有一个最关键的问题没有细说,那就是如何选择下一步?

例如sudoku问题,最直观的做法是随机选择一个还是空白的格子,还能再优化吗?考虑这样的情况:假设当前空白的格子中,有一个格子有5种可能的值,有一个格子只有1个可能的值,那么应当先选择哪个格子?显然,选择只有1个可能值的格子更好。填充了这个格子,能够减少其他未填充格子的可选择值,也就降低了unmake_move的次数。

但是这样一定比随机选择更快吗?细心的读者能够发现,这样的选择方式,在每次选择下一步的时候,会花费相当的时间去查找”可选择值最少“的空白格子。每一步,我们对所有空白格子,计算它们的可选择值;计算可选择值的过程是查看当前行、当前列、当前9格。其实,这和随机选择一样,最后都会得到时间代价O(n^4)的算法。

3. 更多的优化

其实这里还有继续优化的空间,这里不详细展开,只说一下大概的思路。
  • 使用数组保留每个空白格子的可选值。每当有空白格被填入了数字,重新计算受影响的空白格的可选值(当前行、当前列、当前9格)
  • 不那么严格的选择。例如,我们可以只计算每个9格中的空白格的数量,从空白格最少的9格中,随机选出一个空白格,作为下一步要填充的格子。这是一种不那么严格的选择,好处是每个9格的空白格数量可以快速地计算出,同时保证了unmake_move的次数比随机选择要大大减少。

4. 暴力破解 VS 精细选择

每一步随机选择一个空白格,这其实就是一种暴力破解的方法,这里还有另一种暴力破解的方法,经过计算就会发现,其实两者的算法复杂度基本相当。前文提到的精细选择,如果精细选择的过程没有优化,算法的复杂度其实没有变化,有兴趣的同学可以自己证明和验证。



这篇关于[leetcode] sudoku solver:暴力还是优化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/886831

相关文章

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

MySQL不使用子查询的原因及优化案例

《MySQL不使用子查询的原因及优化案例》对于mysql,不推荐使用子查询,效率太差,执行子查询时,MYSQL需要创建临时表,查询完毕后再删除这些临时表,所以,子查询的速度会受到一定的影响,本文给大家... 目录不推荐使用子查询和JOIN的原因解决方案优化案例案例1:查询所有有库存的商品信息案例2:使用EX

MySQL中my.ini文件的基础配置和优化配置方式

《MySQL中my.ini文件的基础配置和优化配置方式》文章讨论了数据库异步同步的优化思路,包括三个主要方面:幂等性、时序和延迟,作者还分享了MySQL配置文件的优化经验,并鼓励读者提供支持... 目录mysql my.ini文件的配置和优化配置优化思路MySQL配置文件优化总结MySQL my.ini文件

正则表达式高级应用与性能优化记录

《正则表达式高级应用与性能优化记录》本文介绍了正则表达式的高级应用和性能优化技巧,包括文本拆分、合并、XML/HTML解析、数据分析、以及性能优化方法,通过这些技巧,可以更高效地利用正则表达式进行复杂... 目录第6章:正则表达式的高级应用6.1 模式匹配与文本处理6.1.1 文本拆分6.1.2 文本合并6

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传