473. 火柴拼正方形(Java)
题目链接:https://leetcode-cn.com/problems/matchsticks-to-square/
难度:中等
1.题目
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
2.示例
输入: [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
3.题解
思路:先判断所有火柴的总长度是否为4的倍数。如果不是则直接返回false。如果是4的倍数就尝试将火柴放入四个边看能否组成正方形。
class Solution {
public boolean makesquare(int[] matchsticks) {
int sum = 0;
for(int matchstick: matchsticks) sum += matchstick;
if(sum==0||(sum & 3)!=0) return false;
return backtrack(matchsticks,0,sum >>2,new int[4]);
}
//index表示当前对应的arr[index],target则是正方体的边长,size数组存储各边的长度
//该函数是对火柴数组arr中的第index根进行相应的操作
public boolean backtrack(int[] arr,int index,int target,int[] size){
//当数组arr的最后一根火柴已经全部加入size数组中
if(index == arr.length){
//判断size中的边长是否相等
if(size[0]==size[1]&&size[1]==size[2]&&size[2]==size[3]) return true;
//不相等则表示没有对应的解
return false;
}
//当数组arr还有火柴没有用完时
for(int i=0;i<4;i++){
//如果size[i]的长度加上arr[index]超过了target,则应该结束此次循环
if(size[i]+arr[index]>target) continue;
//如果相加和不超过target,就可以进行下一步
//i边加上相对应的长度,然后对下一个火柴长度进行判断
size[i] += arr[index];
if(backtrack(arr,index+1,target,size)) return true;
//如果在接下来的火柴长度判断中不满足题目要求,则将此时的火柴长度减去,对下一个边长进行判断
size[i] -= arr[index];
}
return false;
}
}
复杂度分析
时间复杂度:O(4N),N是火柴的数量
空间复杂度:O(N)。
代码优化
如果数组的前面元素数值较小,则可能导致递归的深度较高。于是我们可以先对火柴数组进行排序,从大的火柴进行填入判断。
class Solution {
public boolean makesquare(int[] matchsticks) {
int sum = 0;
for(int matchstick: matchsticks) sum += matchstick;
if(sum==0||(sum & 3)!=0) return false;
Arrays.sort(matchsticks);
return backtrack(matchsticks,matchsticks.length-1,sum >>2,new int[4]);
}
//index表示当前对应的arr[index],target则是正方体的边长,size数组存储各边的长度
//该函数是对火柴数组arr中的第index根进行相应的操作
public boolean backtrack(int[] arr,int index,int target,int[] size){
//当数组arr的最后一根火柴已经全部加入size数组中
if(index == -1){
//判断size中的边长是否相等
if(size[0]==size[1]&&size[1]==size[2]&&size[2]==size[3]) return true;
//不相等则表示没有对应的解
return false;
}
//当数组arr还有火柴没有用完时
for(int i=0;i<4;i++){
//如果size[i]的长度加上arr[index]超过了target,则应该结束此次循环
//如果此时的size[i]跟前面的size[i-1]相等,
//就说明在上一个size[i-1]的循环中已经证明这个arr[index]不适合目前的size[i]
//则对于此时的index也不存在解
if(size[i]+arr[index]>target||(i>0&&size[i]==size[i-1])) continue;
//如果相加和不超过target,就可以进行下一步
//i边加上相对应的长度,然后对下一个火柴长度进行判断
size[i] += arr[index];
if(backtrack(arr,index-1,target,size)) return true;
//如果在接下来的火柴长度判断中不满足题目要求,则将此时的火柴长度减去,对下一个边长进行判断
size[i] -= arr[index];
}
return false;
}
}
4.总结
回溯算法的经典模板
private void backtrack("原始参数") {
//终止条件(递归必须要有终止条件)
if ("终止条件") {
//一些逻辑操作(可有可无,视情况而定)
return;
}
for (int i = "for循环开始的参数"; i < "for循环结束的参数"; i++) {
//一些逻辑操作(可有可无,视情况而定)
//做出选择
//递归
backtrack("新的参数");
//一些逻辑操作(可有可无,视情况而定)
//撤销选择
}
}