トップページに戻る    次のC#のサンプルへ    前のC#のサンプルへ

24-04 カックロ

問題

ニコリのカックロを解きます。

例題と答え
    

1 すべての白マスに1から9までの数字のどれかを1つずつ入れましょう。0(ゼロ)は使いません。

2 ナナメの線の右上の数字は、その右に連続した白マスに入る数字の合計を表し、
  左下の数字は、その下に連続した白マスに入る数字の合計を表します。

3 タテヨコへの1つの白マスのつながりには、同じ数字を入れてはいけません。
  124は○ですが、232は×です。


ソース

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    struct PointDef
    {
        internal int X;
        internal int Y;
    }

    //数値情報の構造体
    struct NumInfoDef
    {
        internal PointDef Point; //数値の座標
        internal int YokoSum; //横方向の合計
        internal int TateSum; //縦方向の合計
        internal PointDef[] RenketuYokoPointArr; //連結した横座標の配列
        internal PointDef[] RenketuTatePointArr; //連結した縦座標の配列
    }

    //カックロ分解表の1要素の定義
    struct BunkaiDef
    {
        internal int MasuCnt; //マス目の数
        internal int SumVal; //合計値
        internal int[] KouhoArr; //候補の数値の配列
    }

    //カックロ分解表
    static BunkaiDef[] mBunkaiArr;

    static int[,] mQuestionYokoSum;
    static int[,] mQuestionTateSum;
    static int UB_X;
    static int UB_Y;

    static NumInfoDef[] mNumInfoArr; //数値情報の配列

    static void Main()
    {
        //カックロ分解表を作成
        CreateBunkaiHyou();

        int[,] Q01_YokoSum = {{ 0, 5, 5, 0, 0},
                              {17,17,17,17, 0},
                              { 6, 6, 0, 4, 4},
                              { 0,10,10,10,10},
                              { 0, 0, 3, 3, 0}};

        int[,] Q01_TateSum = {{ 0,11, 4, 0, 0},
                              {14,11, 4,10, 0},
                              {14,11, 0,10, 3},
                              { 0,11, 3,10, 3},
                              { 0, 0, 3,10, 0}};

        mQuestionYokoSum = XYRev(Q01_YokoSum);
        mQuestionTateSum = XYRev(Q01_TateSum);
        UB_X = mQuestionYokoSum.GetUpperBound(0);
        UB_Y = mQuestionYokoSum.GetUpperBound(1);

        //第1フェーズ 数値情報を作成する
        CreateNumInfoArr();

        //デバッグ用の数値情報を出力
        //DebugPrintNumInfo();

        //第2フェーズ 確定探索
        int[,] FillNumArr = new int[UB_X + 1, UB_Y + 1];
        ExecKakuteiTansaku(FillNumArr);

        //第3フェーズ 深さ優先探索
        ExecDFS(FillNumArr);
    }

    //カックロ分解表を作成
    static void CreateBunkaiHyou()
    {
        var BunkaiList = new List<BunkaiDef>();

        Action<int, int, int[]> AddAct = (pMasuCnt, pSumVal, pKouhoArr) =>
        {
            BunkaiDef WillAdd;
            WillAdd.MasuCnt = pMasuCnt;
            WillAdd.SumVal = pSumVal;
            WillAdd.KouhoArr = pKouhoArr;
            BunkaiList.Add(WillAdd);
        };

        AddAct(2, 3, new int[] { 1, 2 });
        AddAct(2, 4, new int[] { 1, 3 });
        AddAct(2, 16, new int[] { 7, 9 });
        AddAct(2, 17, new int[] { 8, 9 });
        AddAct(3, 6, new int[] { 1, 2, 3 });
        AddAct(3, 7, new int[] { 1, 2, 4 });
        AddAct(3, 23, new int[] { 6, 8, 9 });
        AddAct(3, 24, new int[] { 7, 8, 9 });
        AddAct(4, 10, new int[] { 1, 2, 3, 4 });
        AddAct(4, 11, new int[] { 1, 2, 3, 5 });
        AddAct(4, 29, new int[] { 5, 7, 8, 9 });
        AddAct(4, 30, new int[] { 6, 7, 8, 9 });
        AddAct(5, 15, new int[] { 1, 2, 3, 4, 5 });
        AddAct(5, 16, new int[] { 1, 2, 3, 4, 6 });
        AddAct(5, 34, new int[] { 4, 6, 7, 8, 9 });
        AddAct(5, 35, new int[] { 5, 6, 7, 8, 9 });
        AddAct(6, 21, new int[] { 1, 2, 3, 4, 5, 6 });
        AddAct(6, 22, new int[] { 1, 2, 3, 4, 5, 7 });
        AddAct(6, 38, new int[] { 3, 5, 6, 7, 8, 9 });
        AddAct(6, 39, new int[] { 4, 5, 6, 7, 8, 9 });
        AddAct(7, 28, new int[] { 1, 2, 3, 4, 5, 6, 7 });
        AddAct(7, 29, new int[] { 1, 2, 3, 4, 5, 6, 8 });
        AddAct(7, 41, new int[] { 2, 4, 5, 6, 7, 8, 9 });
        AddAct(7, 42, new int[] { 3, 4, 5, 6, 7, 8, 9 });

        for (int I = 1; I <= 9; I++) {
            var wkList = Enumerable.Range(1, 9).ToList();
            wkList.Remove(I);
            AddAct(8, 45 - I, wkList.ToArray());
        }

        AddAct(9, 45, Enumerable.Range(1, 9).ToArray());

        mBunkaiArr = BunkaiList.ToArray();
    }

    //X座標とY座標の入れ替え
    static int[,] XYRev(int[,] pBaseArr)
    {
        int RevArrUB_X = pBaseArr.GetUpperBound(1);
        int RevArrUB_Y = pBaseArr.GetUpperBound(0);
        int[,] WillReturnArr = new int[RevArrUB_X + 1, RevArrUB_Y + 1];
        for (int X = 0; X <= RevArrUB_X; X++) {
            for (int Y = 0; Y <= RevArrUB_Y; Y++) {
                WillReturnArr[X, Y] = pBaseArr[Y, X];
            }
        }
        return WillReturnArr;
    }

    //第1フェーズ 数値情報を作成する
    static void CreateNumInfoArr()
    {
        var NumInfoList = new List<NumInfoDef>();

        for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
            for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                //黒マスの場合
                if (mQuestionYokoSum[LoopX, LoopY] == 0)
                    continue;

                NumInfoDef WillAdd;
                PointDef CurrPoint = new PointDef() { X = LoopX, Y = LoopY };
                WillAdd.Point = CurrPoint;
                WillAdd.YokoSum = mQuestionYokoSum[LoopX, LoopY];
                WillAdd.TateSum = mQuestionTateSum[LoopX, LoopY];

                var wkYokoPointList = new List<PointDef>();
                wkYokoPointList.AddRange(DeriveRenketuPointList(CurrPoint, -1, 0));
                wkYokoPointList.AddRange(DeriveRenketuPointList(CurrPoint, 1, 0));
                WillAdd.RenketuYokoPointArr =
                    wkYokoPointList.OrderBy(A => A.X).ThenBy(A => A.Y).ToArray();

                var wkTatePointList = new List<PointDef>();
                wkTatePointList.AddRange(DeriveRenketuPointList(CurrPoint, 0, -1));
                wkTatePointList.AddRange(DeriveRenketuPointList(CurrPoint, 0, 1));
                WillAdd.RenketuTatePointArr =
                    wkTatePointList.OrderBy(A => A.X).ThenBy(A => A.Y).ToArray();

                NumInfoList.Add(WillAdd);
            }
        }
        mNumInfoArr = NumInfoList.ToArray();
    }

    //変位ベクトルを引数として、連結した座標のListを返す
    static List<PointDef> DeriveRenketuPointList(PointDef pCurrPoint, int pHeniX, int pHeniY)
    {
        var WillReturnList = new List<PointDef>();

        int LoopX = pCurrPoint.X, LoopY = pCurrPoint.Y;

        while (true) {
            //次のマスからが対象となる
            LoopX += pHeniX; LoopY += pHeniY;

            //範囲外の場合
            if (LoopX < 0 || UB_X < LoopX) break;
            if (LoopY < 0 || UB_Y < LoopY) break;

            //黒マスに到達した場合
            if (mQuestionYokoSum[LoopX, LoopY] == 0) break;

            WillReturnList.Add(new PointDef() { X = LoopX, Y = LoopY });
        }
        return WillReturnList;
    }

    //デバッグ用の数値情報を出力
    static void DebugPrintNumInfo()
    {
        for (int I = 0; I <= mNumInfoArr.GetUpperBound(0); I++) {
            Console.WriteLine("■■■■■■■■■■■■■");
            Console.WriteLine("{0}個目の数値情報", I + 1);
            Console.WriteLine("Point=({0},{1})", mNumInfoArr[I].Point.X, mNumInfoArr[I].Point.Y);
            Console.WriteLine("横方向の合計={0}", mNumInfoArr[I].YokoSum);
            Console.WriteLine("縦方向の合計={0}", mNumInfoArr[I].TateSum);

            Console.Write("連結した横座標の配列 ");
            foreach (var EachPoint in mNumInfoArr[I].RenketuYokoPointArr) {
                Console.Write("({0},{1}),", EachPoint.X, EachPoint.Y);
            }

            Console.WriteLine();
            Console.Write("連結した縦座標の配列 ");
            foreach (var EachPoint in mNumInfoArr[I].RenketuTatePointArr) {
                Console.Write("({0},{1}),", EachPoint.X, EachPoint.Y);
            }
            Console.WriteLine();
        }
    }

    //第2フェーズ 確定探索を行い、有効な盤面かを返す
    static bool ExecKakuteiTansaku(int[,] pFillNumArr)
    {
        while (true) {
            int[,] PrevNumArr = (int[,])pFillNumArr.Clone();

            for (int I = 0; I <= mNumInfoArr.GetUpperBound(0); I++) {
                int CurrX = mNumInfoArr[I].Point.X;
                int CurrY = mNumInfoArr[I].Point.Y;
                if (pFillNumArr[CurrX, CurrY] != 0) continue;

                //状態を調査して、数値の候補を求める
                int[] KouhoNumArr = DeriveKouhoNumArr(pFillNumArr, I, CurrX, CurrY);

                if (KouhoNumArr.Length == 0) {
                    return false;
                }
                else if (KouhoNumArr.Length == 1) {
                    pFillNumArr[CurrX, CurrY] = KouhoNumArr[0];
                }
            }

            //確定探索で確定するマスがなくなったらBreak
            if (pFillNumArr.Cast<int>().SequenceEqual(PrevNumArr.Cast<int>()))
                break;
        }
        return true;
    }

    //状態を調査して、数値の候補を求める
    static int[] DeriveKouhoNumArr(int[,] pFillNumArr, int pInd, int pX, int pY)
    {
        //横方向の合計値を求める
        int Sum_Yoko = mNumInfoArr[pInd].YokoSum;

        //縦方向の合計値を求める
        int Sum_Tate = mNumInfoArr[pInd].TateSum;

        //横方向の使用済の数値を求める
        var UsedNumSet_Yoko = new HashSet<int>();
        foreach (PointDef EachPoint in mNumInfoArr[pInd].RenketuYokoPointArr) {
            int wkVal = pFillNumArr[EachPoint.X, EachPoint.Y];
            if (wkVal != 0) UsedNumSet_Yoko.Add(wkVal);
        }

        //縦方向の使用済の数値を求める
        var UsedNumSet_Tate = new HashSet<int>();
        foreach (PointDef EachPoint in mNumInfoArr[pInd].RenketuTatePointArr) {
            int wkVal = pFillNumArr[EachPoint.X, EachPoint.Y];
            if (wkVal != 0) UsedNumSet_Tate.Add(wkVal);
        }

        //横方向の残マスを求める
        int RestMasuCnt_Yoko = 1;
        RestMasuCnt_Yoko += mNumInfoArr[pInd].RenketuYokoPointArr.Length;
        RestMasuCnt_Yoko -= UsedNumSet_Yoko.Count;

        //縦方向の残マスを求める
        int RestMasuCnt_Tate = 1;
        RestMasuCnt_Tate += mNumInfoArr[pInd].RenketuTatePointArr.Length;
        RestMasuCnt_Tate -= UsedNumSet_Tate.Count;

        HashSet<int> wk1 = DeriveKouhoNumSet_OneLine(Sum_Yoko, UsedNumSet_Yoko, RestMasuCnt_Yoko);
        HashSet<int> wk2 = DeriveKouhoNumSet_OneLine(Sum_Tate, UsedNumSet_Tate, RestMasuCnt_Tate);
        return wk1.Intersect(wk2).ToArray();
    }

    //横方向か縦方向の数値の候補を求める
    static HashSet<int> DeriveKouhoNumSet_OneLine(int pLineSum, HashSet<int> pUsedNumSet, int pRestMasuCnt)
    {
        var KouhoSet = new HashSet<int>();
        int pRestSum = pLineSum - pUsedNumSet.Sum();

        //対応するカックロ分解表があるかを調べる
        int TaiouInd = Array.FindIndex(mBunkaiArr, X => X.SumVal == pRestSum && X.MasuCnt == pRestMasuCnt);

        if (TaiouInd >= 0) { //対応するカックロ分解表がある場合
            KouhoSet.UnionWith(mBunkaiArr[TaiouInd].KouhoArr);
        }
        //残りマスが1マスの場合
        else if (pRestMasuCnt == 1) {
            if (pRestSum <= 9) KouhoSet.Add(pRestSum);
        }
        else { //対応するカックロ分解表がない場合
            for (int I = 1; I <= 9; I++) {
                //残りマス数-1を項数とする
                int Kousuu = pRestMasuCnt - 1;

                var MinOtherNumList = new List<int>();
                var MaxOtherNumList = new List<int>();

                for (int J = 1; J <= 9; J++) {
                    if (J == I || pUsedNumSet.Contains(J)) continue;
                    MinOtherNumList.Add(J);
                    if (MinOtherNumList.Count == Kousuu) break;
                }
                for (int J = 9; 0 <= J; J--) {
                    if (J == I || pUsedNumSet.Contains(J)) continue;
                    MaxOtherNumList.Add(J);
                    if (MaxOtherNumList.Count == Kousuu) break;
                }

                if (pRestSum < I + MinOtherNumList.Sum()) continue;
                if (pRestSum > I + MaxOtherNumList.Sum()) continue;
                KouhoSet.Add(I);
            }

            //残りマスが2マスで、合計が偶数なら、2で割った値は使用不可
            if (pRestMasuCnt == 2 && pRestSum % 2 == 0)
                KouhoSet.Remove(pRestSum / 2);
        }

        //使用済の数値は使用不可
        KouhoSet.ExceptWith(pUsedNumSet);

        return KouhoSet;
    }

    //第3フェーズ 深さ優先探索
    static void ExecDFS(int[,] pFillNumArr)
    {
        var Stk = new Stack<int[,]>();
        int[,] WillPushFillNumArr = pFillNumArr;
        Stk.Push(WillPushFillNumArr);

        while (Stk.Count > 0) {
            int[,] PoppedFillNumArr = Stk.Pop();

            //クリア判定
            int Ind;
            int NeedFillX, NeedFillY;
            if (IsClear(PoppedFillNumArr, out Ind, out NeedFillX, out NeedFillY)) {
                Console.WriteLine("解を発見");
                PrintAnswer(PoppedFillNumArr);
                return;
            }

            //状態を調査して、数値の候補を求める
            int[] KouhoNumArr = DeriveKouhoNumArr(PoppedFillNumArr, Ind, NeedFillX, NeedFillY);

            //Push処理
            foreach (int EachInt in KouhoNumArr) {
                WillPushFillNumArr = (int[,])PoppedFillNumArr.Clone();
                WillPushFillNumArr[NeedFillX, NeedFillY] = EachInt;

                //確定探索を行い、有効な盤面ならPush処理
                if (ExecKakuteiTansaku(WillPushFillNumArr))
                    Stk.Push(WillPushFillNumArr);
            }
        }
    }

    //クリア判定
    static bool IsClear(int[,] pFillNumArr, out int pInd, out int pNeedFillX, out int pNeedFillY)
    {
        pInd = -1;
        pNeedFillX = pNeedFillY = -1;
        for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
            for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
                if (pFillNumArr[LoopX, LoopY] != 0) continue;

                int wkInd = Array.FindIndex(mNumInfoArr,
                    A => A.Point.X == LoopX && A.Point.Y == LoopY);

                if (wkInd >= 0) {
                    pInd = wkInd; pNeedFillX = LoopX; pNeedFillY = LoopY;
                    return false;
                }
            }
        }
        return true;
    }

    //解を出力
    static void PrintAnswer(int[,] pFillNumArr)
    {
        //半角数字を全角数字に変換
        Func<int, char> SingleToWideFunc = (pStr) => (char)('0' + pStr);

        var sb = new System.Text.StringBuilder();
        for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
            for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
                int wkInt = pFillNumArr[LoopX, LoopY];
                if (wkInt != 0) {
                    sb.Append(SingleToWideFunc(wkInt));
                }
                else if (Array.Exists(mNumInfoArr,
                         A => A.Point.X == LoopX && A.Point.Y == LoopY)) {
                    sb.Append('□');
                }
                else sb.Append('■');
            }
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }
}


実行結果

解を発見
■23■■
9512■
51■31
■3142
■■21■


追加問題02 ニコリビヨリのサンプル問題

int[,] Q02_YokoSum = {{ 0, 3, 3, 0, 0},
                      {16,16,16,16,16},
                      {11,11, 0, 9, 9},
                      {35,35,35,35,35},
                      { 0, 0,17,17, 0}};

int[,] Q02_TateSum = {{ 0,11, 4, 0, 0},
                      {16,11, 4,30,14},
                      {16,11, 0,30,14},
                      {16,11,16,30,14},
                      { 0, 0,16,30, 0}};


追加問題03 ニコリビヨリの問題

int[,] Q03_YokoSum = {{ 4, 4, 0, 3, 3, 0, 0, 0,18,18,18},
                      { 3, 3, 0, 6, 6, 6, 0,10,10,10,10},
                      {10,10,10,10, 0,11,11,11,11, 0, 0},
                      { 0, 7, 7, 0, 7, 7, 7, 0,11,11,11},
                      { 0, 0, 8, 8, 8, 0, 7, 7, 0,16,16},
                      { 0, 9, 9, 9, 0, 0, 0,21,21,21, 0},
                      { 6, 6, 0,10,10, 0,22,22,22, 0, 0},
                      { 6, 6, 6, 0,23,23,23, 0,17,17, 0},
                      { 0, 0,29,29,29,29, 0,26,26,26,26},
                      {15,15,15,15, 0,24,24,24, 0, 6, 6},
                      { 7, 7, 7, 0, 0, 0,16,16, 0,12,12}};

int[,] Q03_TateSum = {{ 7,10, 0, 6, 4, 0, 0, 0,11,10,13},
                      { 7,10, 0, 6, 4, 7, 0, 3,11,10,13},
                      { 7,10,11, 6, 0, 7, 6, 3,11, 0, 0},
                      { 0,10,11, 0, 3, 7, 6, 0,11,23, 8},
                      { 0, 0,11, 7, 3, 0, 6,23, 0,23, 8},
                      { 0, 9,11, 7, 0, 0, 0,23,30,23, 0},
                      { 4, 9, 0, 7,24, 0,13,23,30, 0, 0},
                      { 4, 9,11, 0,24,24,13, 0,30,29, 0},
                      { 0, 0,11,10,24,24, 0,24,30,29, 7},
                      {13, 3,11,10, 0,24,17,24, 0,29, 7},
                      {13, 3,11, 0, 0, 0,17,24, 0,29, 7}};


追加問題04 ペンシルパズル入門の例題

int[,] Q04_YokoSum = {{ 0, 8, 8, 8, 0},
                      {10,10,10,10, 0},
                      { 3, 3, 0, 8, 8},
                      { 0,29,29,29,29},
                      { 0,18,18,18, 0}};

int[,] Q04_TateSum = {{ 0,15, 4,20, 0},
                      { 4,15, 4,20, 0},
                      { 4,15, 0,20,16},
                      { 0,15,17,20,16},
                      { 0,15,17,20, 0}};


追加問題05 ペンシルパズル入門の7問目

int[,] Q05_YokoSum = {{23,23,23, 0, 0,17,17, 0, 0, 4, 4},
                      {30,30,30,30, 0,24,24,24, 0, 6, 6},
                      { 0,35,35,35,35,35, 0,10,10,10,10},
                      { 0, 0,24,24,24, 0, 0, 7, 7, 0, 0},
                      { 7, 7, 7, 0,14,14, 0,30,30,30,30},
                      { 4, 4, 0,35,35,35,35,35, 0,16,16},
                      {11,11,11,11, 0,17,17, 0,20,20,20},
                      { 0, 0, 4, 4, 0, 0,13,13,13, 0, 0},
                      {10,10,10,10, 0,15,15,15,15,15, 0},
                      { 3, 3, 0, 8, 8, 8, 0,10,10,10,10},
                      { 6, 6, 0, 0, 3, 3, 0, 0, 7, 7, 7}};

int[,] Q05_TateSum = {{16,24,34, 0, 0,23,16, 0, 0, 7, 6},
                      {16,24,34,23, 0,23,16,34, 0, 7, 6},
                      { 0,24,34,23,29,23, 0,34,13, 7, 6},
                      { 0, 0,34,23,29, 0, 0,34,13, 0, 0},
                      { 9, 6,34, 0,29,19, 0,34,13,23,24},
                      { 9, 6, 0,15,29,19,29,34, 0,23,24},
                      { 9, 6, 7,15, 0,19,29, 0,15,23,24},
                      { 0, 0, 7,15, 0, 0,29, 7,15, 0, 0},
                      { 6, 7, 7,15, 0, 7,29, 7,15, 6, 0},
                      { 6, 7, 0,15, 4, 7, 0, 7,15, 6, 4},
                      { 6, 7, 0, 0, 4, 7, 0, 0,15, 6, 4}};


追加問題06 パズルBox10の4問目

int[,] Q06_YokoSum = {{ 0, 6, 6, 6, 0, 0,16,16, 0, 6, 6, 6, 0,11,11},
                      {29,29,29,29, 0,23,23,23, 0,22,22,22,22,22,22},
                      {12,12, 0,28,28,28,28,28,28,28, 0,10,10,10, 0},
                      { 0,15,15,15,15,15, 0,21,21,21,21,21, 0,14,14},
                      { 7, 7, 7, 0,29,29,29,29, 0,13,13, 0,24,24,24},
                      {10,10, 0,16,16, 0, 7, 7, 7, 0,29,29,29,29, 0},
                      {41,41,41,41,41,41,41, 0,15,15,15,15,15, 0, 0},
                      { 0,10,10, 0, 0,10,10, 0, 8, 8, 0, 0,10,10, 0},
                      { 0, 0,30,30,30,30,30, 0,29,29,29,29,29,29,29},
                      { 0,30,30,30,30, 0, 6, 6, 6, 0, 4, 4, 0,10,10},
                      { 6, 6, 6, 0, 4, 4, 0,10,10,10,10, 0,23,23,23},
                      {17,17, 0,15,15,15,15,15, 0,34,34,34,34,34, 0},
                      { 0,13,13,13, 0,42,42,42,42,42,42,42, 0, 7, 7},
                      {38,38,38,38,38,38, 0, 7, 7, 7, 0,10,10,10,10},
                      {16,16, 0, 6, 6, 6, 0, 6, 6, 0, 0,24,24,24, 0}};

int[,] Q06_TateSum = {{ 0,43,10,11, 0, 0,24,39, 0,15, 3,10, 0,39, 4},
                      {13,43,10,11, 0,21,24,39, 0,15, 3,10,10,39, 4},
                      {13,43, 0,11,35,21,24,39, 6,15, 0,10,10,39, 0},
                      { 0,43, 4,11,35,21, 0,39, 6,15,23,10, 0,39,16},
                      { 7,43, 4, 0,35,21,21,39, 0,15,23, 0,34,39,16},
                      { 7,43, 0,17,35, 0,21,39,21, 0,23,10,34,39, 0},
                      { 7,43,16,17,35,24,21, 0,21, 7,23,10,34, 0, 0},
                      { 0,43,16, 0, 0,24,21, 0,21, 7, 0, 0,34,43, 0},
                      { 0, 0,16,17,17,24,21, 0,21, 7,15, 3,34,43,23},
                      { 0,38,16,17,17, 0,21,22,21, 0,15, 3, 0,43,23},
                      {10,38,16, 0,17,20, 0,22,21,18,15, 0,16,43,23},
                      {10,38, 0,11,17,20,13,22, 0,18,15,21,16,43, 0},
                      { 0,38,16,11, 0,20,13,22, 7,18,15,21, 0,43, 4},
                      {14,38,16,11, 8,20, 0,22, 7,18, 0,21,10,43, 4},
                      {14,38, 0,11, 8,20, 0,22, 7, 0, 0,21,10,43, 0}};


解説

カックロ分解表を最初に作成しておくことにより、計算量を削減してます。

HashSetジェネリックで、
マスに埋める候補となる数値の集合に対して、
和集合演算や差集合演算や積集合演算を行ってます。