[Leetcode] 473. Matchsticks to Square 解题报告

探讨如何使用深度优先搜索(DFS)解决火柴拼成正方形的问题,介绍三种优化策略提高搜索效率,并给出C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

Remember the story of Little Match Girl? By now, you know exactly what matchsticks the little match girl has, please find out a way you can make one square by using up all those matchsticks. You should not break any stick, but you can link them up, and each matchstick must be used exactly one time.

Your input will be several matchsticks the girl has, represented with their stick length. Your output will either be true or false, to represent whether you could make one square using all the matchsticks the little match girl has.

Example 1:

Input: [1,1,2,2,2]
Output: true

Explanation: You can form a square with length 2, one side of the square came two sticks with length 1.

Example 2:

Input: [3,3,3,3,4]
Output: false

Explanation: You cannot find a way to form a square with all the matchsticks.

Note:

  1. The length sum of the given matchsticks is in the range of 0 to 10^9.
  2. The length of the given matchstick array will not exceed 15.

思路

看讨论说这道题目是NP难度的,所以就只能用深度优先搜索试了,时间复杂度高达O(4^n)。最简单的方法是暴力搜索,把每个火柴都试着在4个边轮流放一遍。不过这肯定过不了大数据。通过加入三个优化,DFS方法可以在6ms内通过所有的测试数据,我们看看这几个神奇的优化吧:

1)我们知道火柴的总长度,那么正方形的每个边的长度就确定了。于是一旦某个边的长度已经超过了正方形的边长,就可以直接进行剪枝。

2)如果火柴长度是无序的,那么很可能我们是试了很多短火柴之后才试长火柴的,而一旦加入长火柴可能就超过边长了。所以如果我们首先试长火柴,则可以提前结束无效的搜索,这样就再次进行了剪枝。因此在搜索之前,我们可以先对火柴按照长度由长到短排序。

3)对于当前的边i,如果我们发现边j和它的长度一样,那么说明在试边j的时候已经搜索过了整个数组,所以此时就不用再试了,这是另外一个有效的剪枝。

加了这三个有效的剪枝,就可以大大提高DFS的执行效率。我在下面的代码片段中注释了三个优化的地方,读者可以仔细体味其中的精妙之处。

当然这道题目还可以用DFS +DP来解决。具体方法是:假设整个火柴的长度是s,那么我们首先用DFS方法来获得所有长度之和为s / 2的子集。每个子集其实就对应了对所有火柴的一个划分。对于每个划分,我们分别用DP来计算其两个子集是否存在和为s / 4的一个子集,如果都存在,就返回true。如果所有的划分都无法再次被划分为和相等的两部分,那么就返回false。有兴趣的读者可以按照这个思路实现(其中的DFS可以仿照下面的代码进行实现,而DP就是0-1背包问题的照搬)。

代码

class Solution {
public:
    bool makesquare(vector<int>& nums) {
        if (nums.size() < 4) {
            return false;
        }
        int sum = 0;
        for (const int val: nums) {
            sum += val;
        }
        if (sum % 4 != 0) {
            return false;
        }
        sort(nums.begin(), nums.end(), [](const int &l, const int &r){return l > r;});  // second optimization
        vector<int> sidesLength(4, 0);
        return dfs(sidesLength, nums, 0, sum / 4);
    }
private:
    bool dfs(vector<int> &sidesLength,const vector<int> &matches, int index, const int target) {
        if (index == matches.size()) {
            return sidesLength[0] == sidesLength[1] && sidesLength[1] == sidesLength[2] && sidesLength[2] == sidesLength[3];
        }
        for (int i = 0; i < 4; ++i) {
            if (sidesLength[i] + matches[index] > target) {                             // first optimization
                continue;
            }
            int j = i;
            while (--j >= 0) {                                                          // third optimization
                if (sidesLength[i] == sidesLength[j]) 
                    break;
            }
            if (j != -1) {      // already checked in sidesLength[j]
                continue;
            }
            sidesLength[i] += matches[index];
            if (dfs(sidesLength, matches, index + 1, target)) {
                return true;
            }
            sidesLength[i] -= matches[index];
        }
        return false;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值