You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效解决Kakurasu谜题?Java实现及动态规划应用问询

高效解决Kakurasu谜题及Java动态规划实现指南

Hey there! Let's break down how to tackle Kakurasu puzzles efficiently, then dive into implementing a dynamic programming (DP) solution in Java—since that's what you're stuck on. First, a quick recap for context: Kakurasu is an n×n grid where each row/column has a target sum. You mark cells such that the sum of column numbers in a marked row equals the row's target, and the sum of row numbers in a marked column equals the column's target.

一、通用高效解法思路

Before jumping into code, let's cover the core strategies to solve Kakurasu fast:

  • Constraint Propagation: Start with rows/columns that have small target sums or limited possible combinations. For example, if a row's target is 3, only {1,2} or {3} are valid—this instantly narrows down your options and eliminates impossible paths early.
  • Pruned Backtracking: When exploring possible choices, if a current selection makes a row/column's remaining sum unreachable (e.g., remaining sum is negative, or smaller than the smallest possible remaining cell value), backtrack immediately instead of wasting time on dead ends.
  • Dynamic Programming: Great for smaller puzzles (n ≤ 10 roughly). It avoids redundant calculations by storing valid intermediate states, which we'll focus on next.

二、Java动态规划实现详解

The key challenge with DP for Kakurasu is defining a manageable state. Since we process rows one by one, our state needs to track the current sum of each column (since column sums depend on the rows we've marked so far). Here's a step-by-step implementation:

1. Precompute Row Candidates

First, for each row, generate all valid column subsets that add up to the row's target sum. This saves us from recalculating these subsets during the DP phase.

2. DP State Definition

We'll use a HashMap where:

  • Key: An immutable list representing the current sum of each column (so we can track unique states)
  • Value: The path of row selections that led to this state (to reconstruct the solution later)

3. State Transition

  • Initialize the DP with all valid first-row selections and their corresponding column sum states.
  • For each subsequent row, iterate over all previous valid states, apply each possible row selection, compute the new column sum state, and keep only states where no column sum exceeds its target.
  • After processing all rows, check if any state matches the exact column target sums—that's our solution.

Full Java Code Example

import java.util.*;

public class KakurasuSolver {
    private int gridSize;
    private int[] rowTargets;
    private int[] colTargets;
    private List<List<Set<Integer>>> rowCandidateSubsets;

    public KakurasuSolver(int n, int[] rowSums, int[] colSums) {
        this.gridSize = n;
        this.rowTargets = rowSums;
        this.colTargets = colSums;
        this.rowCandidateSubsets = precomputeRowCandidates();
    }

    // Precompute all valid column subsets for each row
    private List<List<Set<Integer>>> precomputeRowCandidates() {
        List<List<Set<Integer>>> allCandidates = new ArrayList<>();
        for (int rowIdx = 0; rowIdx < gridSize; rowIdx++) {
            int target = rowTargets[rowIdx];
            List<Set<Integer>> rowSubsets = new ArrayList<>();
            backtrackColumnSubsets(1, target, new HashSet<>(), rowSubsets);
            allCandidates.add(rowSubsets);
        }
        return allCandidates;
    }

    private void backtrackColumnSubsets(int currentCol, int remainingSum, Set<Integer> currentSelection, List<Set<Integer>> result) {
        if (remainingSum == 0) {
            result.add(new HashSet<>(currentSelection));
            return;
        }
        if (currentCol > gridSize || remainingSum < 0) {
            return;
        }
        // Include current column
        currentSelection.add(currentCol);
        backtrackColumnSubsets(currentCol + 1, remainingSum - currentCol, currentSelection, result);
        currentSelection.remove(currentCol);
        // Exclude current column
        backtrackColumnSubsets(currentCol + 1, remainingSum, currentSelection, result);
    }

    // DP-based solver
    public List<Set<Integer>> findSolution() {
        HashMap<List<Integer>, List<Set<Integer>>> dpState = new HashMap<>();

        // Initialize DP with first row candidates
        for (Set<Integer> firstRowChoice : rowCandidateSubsets.get(0)) {
            List<Integer> initialColSums = new ArrayList<>(Collections.nCopies(gridSize, 0));
            boolean isValid = true;
            int firstRowNum = 1; // Rows are 1-indexed for sum calculations

            for (int col : firstRowChoice) {
                int newColSum = initialColSums.get(col - 1) + firstRowNum;
                if (newColSum > colTargets[col - 1]) {
                    isValid = false;
                    break;
                }
                initialColSums.set(col - 1, newColSum);
            }

            if (isValid) {
                List<Set<Integer>> path = new ArrayList<>();
                path.add(firstRowChoice);
                dpState.put(Collections.unmodifiableList(initialColSums), path);
            }
        }

        // Process remaining rows
        for (int rowIdx = 1; rowIdx < gridSize; rowIdx++) {
            HashMap<List<Integer>, List<Set<Integer>>> newDpState = new HashMap<>();
            int currentRowNum = rowIdx + 1;

            for (Map.Entry<List<Integer>, List<Set<Integer>>> entry : dpState.entrySet()) {
                List<Integer> prevColSums = entry.getKey();
                List<Set<Integer>> prevPath = entry.getValue();

                for (Set<Integer> currentRowChoice : rowCandidateSubsets.get(rowIdx)) {
                    List<Integer> newColSums = new ArrayList<>(prevColSums);
                    boolean isValid = true;

                    for (int col : currentRowChoice) {
                        int updatedSum = newColSums.get(col - 1) + currentRowNum;
                        if (updatedSum > colTargets[col - 1]) {
                            isValid = false;
                            break;
                        }
                        newColSums.set(col - 1, updatedSum);
                    }

                    if (!isValid) continue;

                    List<Integer> immutableState = Collections.unmodifiableList(newColSums);
                    if (!newDpState.containsKey(immutableState)) {
                        List<Set<Integer>> newPath = new ArrayList<>(prevPath);
                        newPath.add(currentRowChoice);
                        newDpState.put(immutableState, newPath);
                    }
                }
            }

            dpState = newDpState;
            if (dpState.isEmpty()) return null; // No valid paths left
        }

        // Check for the final valid state (all columns meet their targets)
        for (Map.Entry<List<Integer>, List<Set<Integer>>> entry : dpState.entrySet()) {
            List<Integer> finalColSums = entry.getKey();
            boolean isSolution = true;
            for (int colIdx = 0; colIdx < gridSize; colIdx++) {
                if (!finalColSums.get(colIdx).equals(colTargets[colIdx])) {
                    isSolution = false;
                    break;
                }
            }
            if (isSolution) return entry.getValue();
        }

        return null; // No solution exists
    }

    public static void main(String[] args) {
        // Example 3x3 Kakurasu puzzle
        int n = 3;
        int[] rowSums = {3, 4, 3};
        int[] colSums = {2, 4, 4};

        KakurasuSolver solver = new KakurasuSolver(n, rowSums, colSums);
        List<Set<Integer>> solution = solver.findSolution();

        if (solution != null) {
            System.out.println("Found a solution:");
            for (int i = 0; i < solution.size(); i++) {
                System.out.printf("Row %d: Selected columns %s%n", i+1, solution.get(i));
            }
        } else {
            System.out.println("No valid solution exists for this puzzle.");
        }
    }
}

Key Notes on the Code

  • Candidate Precomputation: The precomputeRowCandidates method uses backtracking to generate all valid column subsets for each row—this avoids redundant work during DP.
  • State Management: Using an immutable list for column sums ensures we can use it as a key in the HashMap, tracking unique states efficiently.
  • Early Pruning: We discard any state where a column sum exceeds its target immediately, keeping the DP state space as small as possible.

When to Use DP vs. Backtracking

  • DP Works Best: For small grids (n ≤ 10) where the state space is manageable. It's great if you need to solve multiple similar puzzles, as you can reuse state logic.
  • Backtracking is Better: For larger grids, since DP state space grows exponentially with grid size. Pair it with constraint propagation to prune dead ends quickly.

内容的提问来源于stack exchange,提问作者Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:10:05