LeetCode探索(80):473-火柴拼正方形

180 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

题目

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

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

示例 1:

img

输入: 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

思考

本题难度中等。

首先是读懂题意。我们需要用 所有的火柴棍 拼成一个正方形。注意每根火柴都不能折断且必须使用一次。

我们首先可以计算这些火柴的和,判断是否是4的倍数(也就是初步判断能否组成正方形的四条边)。还可以判断最长的火柴是否超出正方形的边长,若超出一定不能组成正方形。

我们可以使用回溯算法来解决问题。首先是定义数组edges,作为正方形的四条边,逐一进行判断。对火柴数组从大到小排序,每次对于每条边添加一根火柴,如果不超出边长则继续添加,超出边长则舍弃当前火柴,继续添加下一根火柴。依此类推,直至所有火柴都使用到了为止,此时返回true。

解答

方法一:回溯

/**
 * @param {number[]} matchsticks
 * @return {boolean}
 */
var makesquare = function(matchsticks) {
  // 求和、计算正方形的边长
  let sum = matchsticks.reduce((prev, cur) => prev + cur, 0)
  if (sum % 4 !== 0) {
    return false
  }
  let sideLength = sum / 4
  // 从大到小排序
  matchsticks.sort((a, b) => b - a)
  // 最长火柴的长度大于边长,无法拼成
  if (matchsticks[0] > sideLength) {
    return false
  }
  const edges = new Array(4).fill(0)
  return dfs(0, matchsticks, edges, sideLength)
}
const dfs = (index, matchsticks, edges, len) => {
  if (index === matchsticks.length) {
    return true
  }
  for (let i = 0; i < edges.length; i++) {
    // 第i个边使用火柴matchsticks[index]
    edges[i] += matchsticks[index]
    // 不超出len则继续调用dfs,判断是否可以使用下一个索引的火柴
    if (edges[i] <= len && dfs(index + 1, matchsticks, edges, len)) {
      return true
    }
    // 超出len则回溯
    edges[i] -= matchsticks[index]
  }
  return false
}

// 执行用时:300 ms, 在所有 JavaScript 提交中击败了9.95%的用户
// 内存消耗:40.5 MB, 在所有 JavaScript 提交中击败了99.92%的用户
// 通过测试用例:190 / 190

复杂度分析:

  • 时间复杂度:O(4^n),其中 n 是火柴的数目。每根火柴都可以选择放在 4 条边上。
  • 空间复杂度:O(n)。递归栈需要占用 O(n) 的空间。

参考