leetcode
leetcode 401 ~ 450
火柴拼正方形

火柴拼正方形

难度:

标签:

题目描述

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

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

 

示例 1:

输入: matchsticks = [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。

示例 2:

输入: matchsticks = [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。

 

提示:

  • 1 <= matchsticks.length <= 15
  • 1 <= matchsticks[i] <= 108

代码结果

运行时间: 89 ms, 内存: 16.1 MB


/*
 * 思路:
 * 1. 计算所有火柴的总长度,如果不是4的倍数则返回false。
 * 2. 计算每条边的目标长度。
 * 3. 使用回溯法将火柴分成4组,每组的长度等于目标长度。
 * 4. 使用Java Stream简化数组的处理。
 */
import java.util.stream.IntStream;
import java.util.Arrays;
 
public class Solution {
    public boolean makesquare(int[] matchsticks) {
        int totalLength = IntStream.of(matchsticks).sum();
        if (totalLength % 4 != 0) return false;
        int targetLength = totalLength / 4;
        matchsticks = IntStream.of(matchsticks).boxed().sorted((a, b) -> b - a).mapToInt(i -> i).toArray();
        int[] sides = new int[4];
        return backtrack(matchsticks, sides, targetLength, 0);
    }
 
    private boolean backtrack(int[] matchsticks, int[] sides, int targetLength, int index) {
        if (index == matchsticks.length) {
            return Arrays.stream(sides).allMatch(side -> side == targetLength);
        }
        for (int i = 0; i < 4; i++) {
            if (sides[i] + matchsticks[index] <= targetLength) {
                sides[i] += matchsticks[index];
                if (backtrack(matchsticks, sides, targetLength, index + 1)) return true;
                sides[i] -= matchsticks[index];
            }
        }
        return false;
    }
}

解释

方法:

这个题解使用了回溯算法和分支限界法的思想。首先将火柴根据长度从大到小排序,然后从第一根火柴开始递归搜索。在搜索过程中,通过剪枝策略(如果当前边的长度超过目标长度,就直接返回false)来减少搜索空间。同时使用Counter来统计每种长度火柴的数量,避免重复搜索。搜索过程中,如果四条边都拼接成功,则返回true;如果所有可能的搜索都无法拼成正方形,则返回false。

时间复杂度:

O(4^n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在递归函数中,将火柴长度从大到小排序后再进行搜索,这样做的目的是什么?
将火柴长度从大到小排序后再进行搜索的目的主要有两个:一是可以更快地发现不可能的情况并进行剪枝,因为较大的火柴更容易使得边的长度超过目标长度,从而提前结束无效的搜索路径;二是大的火柴更具有决定性,使用大火柴先进行拼接,可以更快地填满边的长度,这有助于减少搜索的深度和复杂度。
🦆
在题解中提到了剪枝策略,具体有哪些剪枝条件?这些条件是如何减少无效搜索的?
题解中使用的剪枝策略主要包括:1) 如果当前边的长度超过目标长度,则直接返回false,因为这表示当前的拼接方式已经无法成功组成正方形的一边;2) 如果当前边的长度恰好等于目标长度,则直接跳转到下一边的拼接,避免在当前边继续增加长度;3) 在递归过程中,如果某根火柴的长度大于上一根火柴的长度或已经用完,跳过该火柴,避免重复和无效的搜索。这些剪枝条件通过减少搜索空间和避免无效的搜索路径,提高算法的效率。
🦆
题解中使用了Counter来统计每种长度火柴的数量,这种方法相比直接使用数组有什么优势?
使用Counter来统计每种长度火柴的数量相比直接使用数组有几个优势:1) Counter可以自动处理各种不同的火柴长度,不需要预先知道火柴长度的范围,提供了更好的灵活性;2) 通过Counter可以更方便地增减火柴的数量,以及快速查找特定长度火柴的剩余数量;3) 当火柴的长度种类较多时,Counter比数组更节省空间,因为它只记录存在的火柴长度。
🦆
题解提到,如果当前边的长度超过目标长度就直接返回false,能否详细解释这种情况是如何发生的?即在什么情况下会出现当前边长度超过目标长度?
当前边的长度超过目标长度通常发生在递归拼接过程中错误地添加了过长的火柴或者多个火柴的组合导致边的总长度超出了目标长度。例如,如果目标边长是10,而递归过程中错误地连续添加了长度为6和5的火柴,那么这时候当前边的长度就变成了11,超过了目标长度。这种情况下,算法会直接返回false,并进行回溯,尝试其他可能的火柴组合。

相关问题