火柴拼正方形
难度:
标签:
题目描述
你将得到一个整数数组 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)
代码细节讲解
🦆
为什么在递归函数中,将火柴长度从大到小排序后再进行搜索,这样做的目的是什么?
▷🦆
在题解中提到了剪枝策略,具体有哪些剪枝条件?这些条件是如何减少无效搜索的?
▷🦆
题解中使用了Counter来统计每种长度火柴的数量,这种方法相比直接使用数组有什么优势?
▷🦆
题解提到,如果当前边的长度超过目标长度就直接返回false,能否详细解释这种情况是如何发生的?即在什么情况下会出现当前边长度超过目标长度?
▷