跳至主要內容

火柴拼正方形

程琬茵大约 3 分钟教学文档回溯与分支限界-dfs

火柴拼正方形

1. 题目描述

你将得到一个整数数组matchsticks,其中matchsticks[i]是第i个火柴棒的长度。你要用所有的火柴棍拼成一个正方形。你不能折断任何一根火柴棒,但你可以把它们连在一起,而且每根火柴棒必须使用一次

如果你能使这个正方形,则返回true,否则返回false

示例 1:

输入:matchsticks = [1,1,2,2,2]

1734357187032.jpg
1734357187032.jpg

输出:true

解释:能拼成一个边长为2的正方形,每边两根火柴。

示例 2:

输入: matchsticks = [3,3,3,3,4]

输出: false

解释:不能用所有火柴拼成一个正方形。

2. 分析

这道题目可以转化为将数组中的火柴棒分成四个子集,每个子集的和相等,即总和的四分之一。使用回溯法来探索所有可能的组合。

具体步骤:

1. 基本条件检查:

  • 如果火柴棒的总长度不能被4整除,则无法拼成正方形。

  • 如果任意一根火柴棒的长度超过边长,也无法拼成正方形。

2. 排序优化:

  • 将火柴棒按降序排序,有助于更快地达到剪枝条件,减少不必要的计算。

3. 回溯法分配火柴棒到四条边:

  • 尝试将每根火柴棒放入四条边中的任意一条。

  • 如果某条边已经达到边长,则跳过该边。

剪枝策略:

  • 如果某一条边当前长度加上当前火柴棒长度超过边长,则跳过该边。

  • 如果当前火柴棒长度等于0,则跳过。

排序:将火柴棒按降序排序,使得较长的火柴棒先被尝试分配,有助于快速发现不可能的情况。 避免重复:如果两条边尝试放入相同长度的火柴棒后未成功,避免在同一层级继续尝试相同的操作。

3. 代码实现

import java.util.Arrays;

public class MatchsticksToSquare {
    public boolean makesquare(int[] matchsticks) {
        if (matchsticks == null || matchsticks.length < 4) {
            return false;
        }

        // 计算总长度
        int totalLength = 0;
        for (int stick : matchsticks) {
            totalLength += stick;
        }

        // 总长度必须能被4整除
        if (totalLength % 4 != 0) {
            return false;
        }

        int target = totalLength / 4;
        Arrays.sort(matchsticks);

        // 将火柴棒按降序排序,优化剪枝
        for (int i = 0; i < matchsticks.length / 2; i++) {
            int temp = matchsticks[i];
            matchsticks[i] = matchsticks[matchsticks.length - 1 - i];
            matchsticks[matchsticks.length - 1 - i] = temp;
        }

        // 检查是否有火柴棒超过边长
        if (matchsticks[0] > target) {
            return false;
        }

        // 初始化四条边
        int[] sides = new int[4];
        return backtrack(matchsticks, sides, 0, target);
    }

    private boolean backtrack(int[] matchsticks, int[] sides, int index, int target) {
        // 如果所有火柴棒都被成功分配
        if (index == matchsticks.length) {
            // 检查四条边是否都等于目标长度
            return sides[0] == target && sides[1] == target &&
                   sides[2] == target && sides[3] == target;
        }

        int currentStick = matchsticks[index];

        for (int i = 0; i < 4; i++) {
            // 如果将当前火柴棒加入第i条边后超过目标长度,则跳过
            if (sides[i] + currentStick > target) {
                continue;
            }

            // 如果当前边已经与前一条边相同,跳过以避免重复
            if (i > 0 && sides[i] == sides[i - 1]) {
                continue;
            }

            // 将当前火柴棒加入第i条边
            sides[i] += currentStick;

            // 递归处理下一个火柴棒
            if (backtrack(matchsticks, sides, index + 1, target)) {
                return true;
            }

            // 回溯,移除当前火柴棒
            sides[i] -= currentStick;
        }

        // 如果当前火柴棒无法被放入任何一条边,则返回false
        return false;
    }

    public static void main(String[] args) {
        MatchsticksToSquare solver = new MatchsticksToSquare();

        int[][] testCases = {
            {1,1,2,2,2},
            {3,3,3,3,4},
            {5,5,5,5,4,4,4,4,3,3,3,3},
            {1,1,1,1,1,1,1,1,1,1,1,1,4,4,4,4}
        };

        for (int[] testCase : testCases) {
            boolean result = solver.makesquare(testCase);
            System.out.println("输入: " + Arrays.toString(testCase));
            System.out.println("是否能拼成正方形: " + result);
            System.out.println();
        }
    }
}
上次编辑于:
贡献者: ptsdicu