说明
数对:两个单元格,两个数字。两个数字在当前区域(行、列、宫)中,只能在这两个单元格内出现,则形成数对。 作用:如果形成数对,则能够确定这两个单元格只能填写这两个数字,其他数字不能出现,都可以剔除,并且同区域内,其他单元格不能填写这两个数字。
图片
如上图所示,第1宫内,有两个空白格只能填入8和9,则这两个单元格与89形成数对,其余单元格内不能填入89。则可以排除。
并且在所处的列中,也能排除。
再看一个隐藏比较深入的,如下图
上图在第3宫中,4和6只能填写在这两个单元格,故形成46数对,排除本身单元格内的其他数字。所以8只能出现在第3行第9列的位置。
算法代码
CalcEnum
在 绑定算法 中添加 属性
package com.suduku.calc.enums;
import com.suduku.calc.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
/**
* 功能描述: 算法枚举 <br/>
*
*/
@Getter
@AllArgsConstructor
public enum CalcEnum {
/***/
ONLY_NUM(OnlyNumCalc.class, "唯余法", "唯一余数法(当前单元格中,候选数字只有一个)"),
ONLY_BOX(OnlyBoxCalc.class, "摒除法", "摒除法(数字在所在行或列或宫中,只有一个空格能够填写,则确定是唯一数字)"),
GRID_XY(GridXYCalc.class, "单宫行列法", "单宫行列法()"),
SU_DUI(SuDuiCalc.class, "数对法", "数对法(一个区域内,两个数字只能在两个单元格内,则形成数对,其他值不能填入)"),
;
private static final Map<Class<? extends AbstractCalc>, CalcEnum> CE_MAP = new HashMap<>(CalcEnum.values().length);
static {
for(CalcEnum ce : CalcEnum.values()) {
CE_MAP.put(ce.getClazz(), ce);
}
}
/**
* 功能描述: 通过类,获取枚举 <br/>
*
* @param clazz 类
* @return "com.suduku.calc.enums.CalcEnum"
*/
public static CalcEnum indexOf(Class<? extends AbstractCalc> clazz) {
return CE_MAP.get(clazz);
}
private Class<? extends AbstractCalc> clazz;
private String name;
private String msg;
}
SuDuiCalc
package com.suduku.calc;
import com.suduku.entity.Box;
import com.suduku.util.SudoUtil;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 数对法(一个区域内,两个数字只能在两个单元格内,则形成数对,其他值不能填入) <br/>
*
* 测试数据:DataConstant.XING_02_23
*/
public class SuDuiCalc extends AbstractCalc {
@Override
Box solve() {
// 判断宫内数对
Box box = twoNumBox(getGMap());
if(box != null) {
return box;
}
// 判断行内数对
box = twoNumBox(getXMap());
if(box != null) {
return box;
}
// 判断列内数对
box = twoNumBox(getYMap());
if(box != null) {
return box;
}
return null;
}
/**
* 功能描述: 查找区域内的数对 <br/>
*
* @param areaMap 区域集合
* @return "com.suduku.entity.Box"
*/
private Box twoNumBox(Map<Integer, List<Box>> areaMap) {
// 遍历所有区域
Set<Map.Entry<Integer, List<Box>>> entries = areaMap.entrySet();
for (Map.Entry<Integer, List<Box>> entry : entries) {
List<Box> list = entry.getValue();
// 查找区域内候选字是两个数字,并且候选数字相同,且只有两个单元格的情况,形成数对
List<Box> nmList = SudoUtil.findSuDuiBoxList(list);
if(nmList != null && nmList.size() == 2 && SudoUtil.isEqTwoList(nmList.get(0).getCList(), nmList.get(1).getCList())) {
// 形成数对,则可以排除区域其他宫内的情况
// 获取数对的下标
List<Integer> suDuiIndexs = nmList.stream().map(Box::getI).collect(Collectors.toList());
List<Box> clearBoxList = list.stream().filter(b -> b.isBlank() && !suDuiIndexs.contains(b.getI()) && (b.getCList().contains(nmList.get(0).getCList().get(0)) || b.getCList().contains(nmList.get(0).getCList().get(1)))).collect(Collectors.toList());
for (Box box : clearBoxList) {
// 是空白格,不是数对单元格,包含数对数字
getListener().sendMsg("确定数对为【%d, %d】,移除数对区域其他单元格数对数字。\n", nmList.get(0).getCList().get(0), nmList.get(0).getCList().get(1));
box.getCList().removeAll(nmList.get(0).getCList());
return box;
}
}
// 遍历 1 ~ 9 个数字,在候选值中是否只有两个空白格
for(Integer n : Box.INIT_LIST) {
List<Box> nList = list.stream()
.filter(b -> b.isBlank() && b.getCList().contains(n))
.collect(Collectors.toList());
// 如果在该区域内,只有两个单元格
if(nList.size() == 2) {
// 遍历下一个数字,并且数字大于第一个数字(减少次数)
for(Integer m : Box.INIT_LIST) {
if(m > n) {
List<Box> mList = list.stream().filter(b -> b.isBlank() && b.getCList().contains(m)).collect(Collectors.toList());
// 如果也是只有两个单元格,并且与第一个数字的两个单元格相等。则形成数对
if(mList.size() == 2 && SudoUtil.isEqBoxList(nList, mList)) {
// 形成数对,开始剔除数据
List<Integer> suDuiList = SudoUtil.getList(n, m);
// 1、剔除本身两个单元格
for(Box box : nList) {
if(box.getCList().size() > 2) {
getListener().sendMsg("确定数对为【%d, %d】,移除数对单元格其余数字。\n", n, m);
box.setCList(suDuiList);
return box;
}
}
// 2、剔除区域内的其他单元格
// 获取数对的下标
List<Integer> suDuiIndexs = nList.stream().map(Box::getI).collect(Collectors.toList());
// 遍历当前区域
for (Box box : list) {
// 是空白格,不是数对单元格,包含数对数字
if(box.isBlank() && !suDuiIndexs.contains(box.getI()) && (box.getCList().contains(n) || box.getCList().contains(m))) {
getListener().sendMsg("确定数对为【%d, %d】,移除数对区域其他单元格数对数字。\n", n, m);
box.getCList().removeAll(suDuiList);
return box;
}
}
}
}
}
}
}
}
return null;
}
}
SudoUtil
/**
* 功能描述: 发现数对 <br/>
*
* @param towCLists 待发现的数对
* @return "java.util.List<com.suduku.entity.Box>"
*/
public static List<Box> findSuDuiBoxList(List<Box> towCLists) {
for(Box b1 : towCLists) {
for(Box b2 : towCLists) {
if(b1.isBlank() && b2.isBlank() && b1.getI() != b2.getI()) {
if(b1.getCList().size() == 2 && isEqTwoList(b1.getCList(), b2.getCList())) {
return getList(b1, b2);
}
}
}
}
return null;
}
输出结果
尝试【 数对法 】
确定数对为【4, 6】移除数对单元格其余数字。
确认位置【行:1,列:8】 值为:【】 候选值为:【4,6】
0 (12 ) 0 (12789 ) 0 (89 ) | 0 (179 ) 0 (579 ) 0 (12579 ) | 3 ( ){0}(46 ) 0 (246789 ) |
5 ( ) 0 (12379 ) 4 ( ) | 6 ( ) 8 ( ) 0 (12379 ) | 0 (129 ) 0 (1279 ) 0 (279 ) |
6 ( ) 0 (123789 ) 0 (89 ) | 0 (1379 ) 0 (79 ) 4 ( ) | 5 ( ) 0 (1279 ) 0 (2789 ) |
0 (79 ) 6 ( ) 1 ( ) | 2 ( ) 3 ( ) 8 ( ) | 0 (49 ) 5 ( ) 0 (479 ) |
8 ( ) 5 ( ) 2 ( ) | 0 (79 ) 4 ( ) 0 (79 ) | 6 ( ) 3 ( ) 1 ( ) |
0 (79 ) 4 ( ) 3 ( ) | 5 ( ) 1 ( ) 6 ( ) | 8 ( ) 0 (279 ) 0 (279 ) |
0 (12 ) 0 (1289 ) 0 (5689 ) | 0 (134789 ) 0 (5679 ) 0 (13579 ) | 0 (1249 ) 0 (12469 ) 0 (23469 ) |
3 ( ) 0 (1289 ) 7 ( ) | 0 (1489 ) 0 (69 ) 0 (19 ) | 0 (1249 ) 0 (12469 ) 5 ( ) |
4 ( ) 0 (19 ) 0 (569 ) | 0 (139 ) 2 ( ) 0 (1359 ) | 7 ( ) 8 ( ) 0 (369 ) |
尝试【 数对法 】
确定数对为【4, 6】移除数对单元格其余数字。
确认位置【行:1,列:9】 值为:【】 候选值为:【4,6】
0 (12 ) 0 (12789 ) 0 (89 ) | 0 (179 ) 0 (579 ) 0 (12579 ) | 3 ( ) 0 (46 ){0}(46 ) |
5 ( ) 0 (12379 ) 4 ( ) | 6 ( ) 8 ( ) 0 (12379 ) | 0 (129 ) 0 (1279 ) 0 (279 ) |
6 ( ) 0 (123789 ) 0 (89 ) | 0 (1379 ) 0 (79 ) 4 ( ) | 5 ( ) 0 (1279 ) 0 (2789 ) |
0 (79 ) 6 ( ) 1 ( ) | 2 ( ) 3 ( ) 8 ( ) | 0 (49 ) 5 ( ) 0 (479 ) |
8 ( ) 5 ( ) 2 ( ) | 0 (79 ) 4 ( ) 0 (79 ) | 6 ( ) 3 ( ) 1 ( ) |
0 (79 ) 4 ( ) 3 ( ) | 5 ( ) 1 ( ) 6 ( ) | 8 ( ) 0 (279 ) 0 (279 ) |
0 (12 ) 0 (1289 ) 0 (5689 ) | 0 (134789 ) 0 (5679 ) 0 (13579 ) | 0 (1249 ) 0 (12469 ) 0 (23469 ) |
3 ( ) 0 (1289 ) 7 ( ) | 0 (1489 ) 0 (69 ) 0 (19 ) | 0 (1249 ) 0 (12469 ) 5 ( ) |
4 ( ) 0 (19 ) 0 (569 ) | 0 (139 ) 2 ( ) 0 (1359 ) | 7 ( ) 8 ( ) 0 (369 ) |
最终结果
1 ( ) 7 ( ) 8 ( ) | 9 ( ) 5 ( ) 2 ( ) | 3 ( ) 4 ( ) 6 ( ) |
5 ( ) 2 ( ) 4 ( ) | 6 ( ) 8 ( ) 3 ( ) | 1 ( ) 9 ( ) 7 ( ) |
6 ( ) 3 ( ) 9 ( ) | 1 ( ) 7 ( ) 4 ( ) | 5 ( ) 2 ( ) 8 ( ) |
7 ( ) 6 ( ) 1 ( ) | 2 ( ) 3 ( ) 8 ( ) | 9 ( ) 5 ( ) 4 ( ) |
8 ( ) 5 ( ) 2 ( ) | 7 ( ) 4 ( ) 9 ( ) | 6 ( ) 3 ( ) 1 ( ) |
9 ( ) 4 ( ) 3 ( ) | 5 ( ) 1 ( ) 6 ( ) | 8 ( ) 7 ( ) 2 ( ) |
2 ( ) 9 ( ) 5 ( ) | 8 ( ) 6 ( ) 7 ( ) | 4 ( ) 1 ( ) 3 ( ) |
3 ( ) 8 ( ) 7 ( ) | 4 ( ) 9 ( ) 1 ( ) | 2 ( ){6}( ) 5 ( ) |
4 ( ) 1 ( ) 6 ( ) | 3 ( ) 2 ( ) 5 ( ) | 7 ( ) 8 ( ) 9 ( ) |
============数独解题完成,尝试次数为:82============
总结
数对属于数独解法中的中级用法,熟练掌握可以快速解决大部分数独。在Sudo Cool 这个app的 2星难度,只需要掌握数对,就能全部通关。