开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第38天,点击查看活动详情
你将得到一个整数数组 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 <= 151 <= matchsticks[i] <= 10^8
思路
本题可以使用动态规划求解。由于要使用所有的火柴,且不能折断,设所有火柴的长度和为total,side = total / 4,要能拼成正方形,必须满足以下两个条件:
side必须为正数,即total能被4整除,如果不能被整除,就无法做到正方形四条边相等。- 火柴可以分成
4份,每份的长度和为side。
条件1好判断,我们主要说一下条件2怎么判断。由于火柴最多15根,我们可以用一个二进制正数n的第i位是否等1表示第i根火柴是否已使用,用dp[n]来表示火柴使用状态是n时的正方形当前边的长度,初始化dp[0] = 0。如果n的第i位等于1,dp[n] = (dp[n - (1<<i)] + matchsticks[i]) (dp[n - (1<<i)] !== -1),
- 如果
dp[n] > side,表明不能把第i根火柴加入当前边,超长了,dp[n] = -1。 - 如果
dp[n] == side,表明可以把第i根火柴加入当前边,且当前边完整,dp[n]=0。 - 如果
dp[n] < side, 表明可以把第i根火柴加入当前边,边不完整。
最后求得dp[1<<(matchsticks.length)]的值,如果等于0,说明可以拼成正方形,否则无法拼成。
解题
/**
* @param {number[]} matchsticks
* @return {boolean}
*/
var makesquare = function (matchsticks) {
if (matchsticks.length < 4) return false;
matchsticks.sort((a, b) => b - a);
const sum = matchsticks.reduce((s, n) => s + n);
if (sum % 4 !== 0) return false;
const n = matchsticks.length;
const side = sum / 4;
const total = 1 << n;
const dp = new Array(total).fill(-1);
dp[0] = 0;
for (let s = 1; s < total; s++) {
for (let k = 0; k < n; k++) {
if ((s & (1 << k)) === 0) {
continue;
}
const s1 = s & ~(1 << k);
if (dp[s1] >= 0 && dp[s1] + matchsticks[k] <= side) {
dp[s] = (dp[s1] + matchsticks[k]) % side;
break;
}
}
}
return dp[total - 1] === 0;
};