来源:力扣(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] <= 108
解题思路
仔细思考这个问题,其实就是判断给定的这堆火柴,能否拼成4条一样长的边!
首先通过将所有火柴的长度相加并除以4,如果余数不为0,则显然无法拼成正方形;如果余数为0,且商记为L,则问题就转换为:如何将给定数组分成4份,且每一份的长度之和均为L!
解决这个问题最直观的办法是:一一尝试,即回溯算法。
首先进行降序排列,目的是让数字有规律,并以最大的数开始匹配减小后续操作量。而后依次尝试分组,使得分组的和为target的。
在这个过程中,只有出现无法配对成target时才会返回false
,其他情况下会进行回撤操作,尝试另一种配对方式,直到找到为止。
详细注释及代码如下:
代码:
class Solution {
public:
bool makesquare(vector<int>& matchsticks) {
int sum=0;
for(auto num:matchsticks){
sum+=num;
}
if(sum%4!=0){
return false;
}
int length=sum/4;
//降序排列,为了从最长的火柴开始尝试
sort(matchsticks.begin(),matchsticks.end(),greater<int>());
vector<int> edge(4);//存储4条边的长度
return isValid(matchsticks,edge,length,0);
}
//dfs(回溯算法/深度优先)
bool isValid(vector<int>& nums,vector<int>& edge,int target,int curindex){
//遍历完毕
if(curindex==nums.size()){
return true;//遍历到最后一个都没出现问题,说明满足等分
}
//开始尝试搭建四条边
for(int i=0;i<edge.size();i++){
if((edge[i]+nums[curindex])>target){
continue;//说明第i条边已经满足,该查看下一条边了
}
//尝试装一下
edge[i]+=nums[curindex];
//dfs进行下一位,如果返回true,则说明算法通过
if(isValid(nums,edge,target,curindex+1)){
return true;
}
//回撤操作
edge[i]-=nums[curindex];
}
return false;//说明上述过程无论怎么尝试都无法搭建出4条相等的边
}
};
执行用时:172 ms, 在所有 C++ 提交中击败了31.29%的用户
内存消耗:9.7 MB, 在所有 C++ 提交中击败了20.99%的用户
总结
dfs(回溯算法/深度优先)算法可以简单理解为暴力尝试,虽然简单,但在很多情况下具有实用性,必须掌握。在日常刷题或面试中,应该首先掌握这些基本方法,再去尝试优化(比如剪枝)。
author : shining-stars