← 返回题目列表

火柴拼正方形为什么是回溯装桶问题?如何剪枝?

高频 中等 第 6 / 30 题 更新于 2026/07/30
回溯装桶剪枝子集划分

简化版

火柴拼正方形等价于把火柴分到 4 条边,每条边长度都等于总长度的四分之一。先判断总和能否被 4 整除,再降序排序,回溯把每根火柴放到 4 条边之一,边长不能超过目标,并用相同边长跳过和空边失败停止来剪枝。

详细版

这题是“划分为 K 个等和子集”的特例,k = 4。状态可以是 dfs(index, sides):处理第 index 根火柴,尝试放进 4 条边。若所有火柴都放完,说明之前每条边都没有超过目标,且总和正好是 4 * target,所以可返回 true。

剪枝重点是:总和不能整除 4 直接失败;最长火柴超过目标失败;降序排序让长火柴先放;同一层中边长相同的边只尝试一次;把火柴放进空边后失败,就没必要试其他空边。

复杂度最坏接近 O(4^n),但 n 通常有限,强剪枝后可以通过面试常见规模。

完整版教学

一、把正方形看成 4 个容量相同的桶

输入火柴长度 [1,1,2,2,2],总和是 8,每条边目标长度是 2。可行划分为:

边1:2
边2:2
边3:1 + 1
边4:2

这和装桶问题完全一致:4 个桶容量都是 target = sum / 4,每根火柴必须放入某个桶,且不能折断、不能重复使用。

火柴:2 2 2 1 1
边长:0 0 0 0
目标:2

二、必要条件先挡掉不可能情况

回溯前先判断:

条件不满足时
火柴数量至少 4不可能组成四条边
总长度能被 4 整除无法得到整数边长
最长火柴不超过目标边长那根火柴放不进任何边

例如 [3,3,3,3,4] 总和是 16,目标边长是 4,但四根 3 无法两两组合出四条 4,这说明必要条件不充分,仍需要回溯搜索。

记忆钩子:正方形题先算周长除以 4,再把四条边当成四个无名桶。

三、为什么要先放长火柴

长火柴约束更强。先放短火柴容易形成很多暂时合法的边长,最后才发现长火柴放不下;先放长火柴能更早剪掉失败分支。

升序:1,1,2,2,2  -> 小火柴选择多
降序:2,2,2,1,1  -> 2 必须单独占边,结构立刻清楚

排序只影响搜索顺序,不影响最终是否能拼成正方形。

四、边之间没有名字,所以要剪对称状态

四条边没有“第一边、第二边”的实际区别。当前边长是 [1,1,0,0],把长度 1 的火柴放入第一条边得到 [2,1,0,0];放入第二条边得到 [1,2,0,0],本质上只是边的顺序不同。

所以同一层中如果某个边长已经尝试过,再遇到相同边长可以跳过。

sides = [1,1,0,0], stick = 1
尝试 side=1 一次即可
尝试 side=0 一次即可

五、代码模板

boolean makesquare(int[] matchsticks) {
    if (matchsticks.length < 4) return false;
    int sum = 0;
    for (int x : matchsticks) sum += x;
    if (sum % 4 != 0) return false;
    int target = sum / 4;

    Arrays.sort(matchsticks);
    reverse(matchsticks);
    if (matchsticks[0] > target) return false;

    return dfs(matchsticks, 0, new int[4], target);
}

boolean dfs(int[] sticks, int index, int[] sides, int target) {
    if (index == sticks.length) return true;
    int stick = sticks[index];
    Set<Integer> tried = new HashSet<>();
    for (int i = 0; i < 4; i++) {
        if (sides[i] + stick > target) continue;
        if (tried.contains(sides[i])) continue;
        tried.add(sides[i]);

        sides[i] += stick;
        if (dfs(sticks, index + 1, sides, target)) return true;
        sides[i] -= stick;

        if (sides[i] == 0) break;
    }
    return false;
}

因为总和已经等于 4 * target,并且递归过程中不允许任一边超过 target,所有火柴放完时每条边自然都等于 target

六、和 K 个等和子集的关系

火柴拼正方形就是 k = 4 的等和子集划分:

题目桶数量桶容量元素
火柴拼正方形4sum / 4火柴
K 等和子集ksum / k数组元素

区别在于火柴题桶数固定为 4,叙事更具体;K 等和子集更通用。两者共享“降序排序、同和桶剪枝、空桶剪枝”的核心套路。

七、常见误区与追问

  • 误区:只判断总和能被 4 整除。 这只是必要条件,仍可能无法组合出四条等长边。
  • 误区:最后还要逐条检查边长。 如果总和正确且每条边从不超过 target,全部火柴放完时四条边必然都等于 target。
  • 误区:四条边有固定顺序。 边是无名的,边长相同的尝试属于对称重复。
  • 追问:为什么降序排序有效? 长火柴约束更强,先放能更早发现超容量分支。
  • 追问:空边失败为什么可以停止? 任意空边完全等价,换另一个空边不会改变剩余问题。
  • 追问:复杂度是多少? 最坏接近 O(4^n),但排序和对称剪枝能显著减少实际搜索。

八、加强记忆

火柴拼正方形不要被几何外衣迷惑,它就是 4 个等容量桶的回溯题。答题时先说总和、目标边长、最长火柴三个前置判断,再说降序放火柴,最后强调边无名带来的对称剪枝。代码里每根火柴只处理一次,每次试 4 条边,放入、递归、撤销;能解释“为什么放完就成功”和“为什么空边失败可 break”,这题就讲透了。