火柴拼正方形

262 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第38天,点击查看活动详情

473. 火柴拼正方形 - 力扣(Leetcode)

你将得到一个整数数组 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] <= 10^8

思路

本题可以使用动态规划求解。由于要使用所有的火柴,且不能折断,设所有火柴的长度和为totalside = total / 4,要能拼成正方形,必须满足以下两个条件:

  1. side必须为正数,即total能被4整除,如果不能被整除,就无法做到正方形四条边相等。
  2. 火柴可以分成4份,每份的长度和为side

条件1好判断,我们主要说一下条件2怎么判断。由于火柴最多15根,我们可以用一个二进制正数n的第i位是否等1表示第i根火柴是否已使用,用dp[n]来表示火柴使用状态是n时的正方形当前边的长度,初始化dp[0] = 0。如果n的第i位等于1dp[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;
};