还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
示例 1:
输入: [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
示例 2:
输入: [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。
注意:
给定的火柴长度和在 0 到 10^9之间。
火柴数组的长度不超过15。
通过次数9,158提交次数23,284
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/matchsticks-to-square
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
回溯法+剪枝
可以这样进行问题的抽象,有一堆数字,要将他们分别放入四个容器中,问题是最后能否使得四个容器内的数字之和全都相等。全部的搜索空间为4^n(n为数字的个数),但是可以通过如下几个方式优化递归搜索:
1. 将数组从大到小进行排序,背后的逻辑是,优先放入大的火柴棍,在每个容器的大小固定时,会减少搜索的空间,因为该容器中能放入的火柴棍会减少;
2. 如1中提到的,容器的大小固定,即在开始就计算出结果的边长,当当前遍历的一个分支已经有不满足边长条件的时候,就跳过其后的分支;
3. 排除掉大小小于4和求得的边长不为整数即和不能被4整除的用例;
注:sort可以通过rbegin()和rend()来进行逆向的排序
class Solution {
public:
bool makesquare(vector<int>& nums) {
vector<int> edge(4,0);
//计算边长
int sum = 0;
for(auto num:nums)
{
sum+=num;
}
if(sum%4!=0)return false;
int edgeLen = sum/4;
if(nums.size()<4)return false;
//从大到小排序
sort(nums.rbegin(),nums.rend());
//构建边集合
return dfs(0,edgeLen,nums,edge);
}
//进行递归搜索,尝试将每个火柴放入四个位置并向下递归遍历
bool dfs(int index,int edgeLen,vector<int> &nums,vector<int> &edge)
{
if(index == nums.size())
{
return edge[0]==edgeLen&&edge[1]==edgeLen&&edge[2]==edgeLen&&edge[3]==edgeLen;
}
if(nums[index]>edgeLen)return false;
for(int i = 0;i<4;i++)
{
if(edge[i]+nums[index]>edgeLen)
{
continue;
}
else
{
edge[i]+=nums[index];
if(dfs(index+1,edgeLen,nums,edge))return true;
edge[i]-=nums[index];
}
}
return false;
}
};
思路2:
利用位运算的思想先求出所有和为边长的子集,再在这个子集之中找到四个没有重复部分的子集即找到了一种拼正方形的方式。思路很巧妙,没有进行优化的情况下在leetcode中比回溯法要慢一些。具体的实现思路是,以二进制的位的1来表示选取对应位置的火柴,0表示不选取,遍历所有可能的子集,而在判断位置是否重叠的时候就可通过按位与来解决,而合并则可以通过按位或来解决。步骤如下:
1. 求出边长;
2. 求出所有和为边长的子集edge;
3. 遍历edge求得满足条件且没有重复的边对的集合pair
4. 在pair中搜索是否有无重复的元素,有则true,vice versa。
class Solution {
public:
bool makesquare(vector<int>& nums) {
//位运算方式
int tempSum = 0;
for(auto num:nums)
{
tempSum+=num;
}
if(tempSum%4!=0||nums.size()<4)return false;
int edgeLen = tempSum/4;
//初始化所需数据结构
int maxLen = 1<<nums.size();
vector<int> edge;
for(int i = 0;i<maxLen;i++)
{
int sum = 0;
for(int j = 0;j<nums.size();j++)
{
if((i&(1<<j))==(1<<j))
{
sum+=nums[j];
if(sum>edgeLen)break;
}
}
if(sum>edgeLen)continue;
if(sum==edgeLen)
{
edge.push_back(i);
}
}
int edgeSize = edge.size();
vector<int> pair;
for(int i = 0;i<edgeSize;i++)
{
for(int j = i+1;j<edgeSize;j++)
{
if((edge[i]&edge[j])==0)
{
pair.push_back(edge[i]|edge[j]);
}
}
}
int pairSize = pair.size();
for(int i = 0;i<pairSize;i++)
{
for(int j = i+1;j<pairSize;j++)
{
if((pair[i]&pair[j])==0)return true;
}
}
return false;
}
};