【2.5】回溯算法-解火柴拼正方形

一、题目

        还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能 使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴 都要用到 输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼 成正方形。

注意 :
1.  给定的火柴长度和在 0 到 10^9之间。
2.  火柴数组的长度不超过15。

二、解题思路

        这道题目要求我们判断是否可以使用所有的火柴拼接成一个正方形,且不允许折断火柴。所有火柴都必须被使用。

        首先,我们需要计算所有火柴的总长度,并检查这个总长度是否是4的倍数。如果不是4的倍数,那么我们可以立即返回 `false`,因为这意味着无法将火柴均匀分配到四条边上,从而不可能拼成正方形。如果总长度是4的倍数,我们继续进行下一步。

        接下来,我们将每一根火柴尝试放置在四条边中的任意一条上。通过这种方式,我们尝试找到一种排列,使得每条边的长度相等,从而构成一个正方形。如果最终我们能够成功地将所有火柴分配到四条边上并形成正方形,我们就返回 `true`。下面看看思路图

        这就是回溯算法,他不断的尝试,一旦成功也就成功了。如果不成功,在回到上一步继续
尝试,其实他有一个经典的模板
void backtrack(/*原始参数*/) {
    // 终止条件(递归必须要有终止条件)
    if (/*终止条件*/) {
        // 一些逻辑操作(可有可无,视情况而定)
        return;
    }

    for (int i = /*for循环开始的参数*/; i < /*for循环结束的参数*/; i++) {
        // 一些逻辑操作(可有可无,视情况而定)

        // 做出选择

        // 递归
        backtrack(/*新的参数*/);

        // 一些逻辑操作(可有可无,视情况而定)

        // 撤销选择
    }
}

三、代码实现

让我们来看看代码实现:

#include <vector>

using namespace std;

bool backtrack(const vector<int>& nums, int index, int target, vector<int>& size);

bool makesquare(vector<int>& nums) {
    int total = 0;
    // 统计所有火柴的长度
    for (int num : nums) {
        total += num;
    }
    // 如果所有火柴的长度不是4的倍数,直接返回false
    if (total == 0 || (total & 3) != 0)
        return false;
    // 回溯
    vector<int> size(4, 0); // 初始化长度为4的数组,初始值为0
    return backtrack(nums, 0, total >> 2, size);
}

// index表示访问到当前火柴的位置,target表示正方形的边长,size是长度为4的数组,
// 分别保存正方形4个边的长度
bool backtrack(const vector<int>& nums, int index, int target, vector<int>& size) {
    if (index == nums.size()) {
        // 如果火柴都访问完了,并且size的4个边的长度都相等,说明是正方形,直接返回true,
        // 否则返回false
        if (size[0] == size[1] && size[1] == size[2] && size[2] == size[3])
            return true;
        return false;
    }
    // 到这一步说明火柴还没访问完
    for (int i = 0; i < size.size(); i++) {
        // 如果把当前火柴放到size[i]这个边上,他的长度大于target,我们直接跳过
        if (size[i] + nums[index] > target)
            continue;
        // 如果当前火柴放到size[i]这个边上,长度不大于target,我们就放上面
        size[i] += nums[index];
        // 然后在放下一个火柴,如果最终能变成正方形,直接返回true
        if (backtrack(nums, index + 1, target, size))
            return true;
        // 如果当前火柴放到size[i]这个边上,最终不能构成正方形,我们就把他从
        // size[i]这个边上给移除,然后在试其他的边
        size[i] -= nums[index];
    }
    // 如果不能构成正方形,直接返回false
    return false;
}

代码优化

        如果数组前面数组比较小,这会导致递归的比较深,所以我们可以先对数组进行排序,从大的开始递归,代码如下
#include <iostream>
#include <vector>
#include <algorithm> // for std::sort

using namespace std;

bool backtrack(const vector<int>& nums, int index, int target, vector<int>& size);

bool makesquare(vector<int>& nums) {
    int total = 0;
    // 统计所有火柴的长度
    for (int num : nums) {
        total += num;
    }
    // 如果所有火柴的长度不是4的倍数,直接返回false
    if (total == 0 || (total & 3) != 0)
        return false;
    // 先排序
    sort(nums.begin(), nums.end());
    // 回溯,从最长的火柴开始
    vector<int> size(4, 0); // 初始化长度为4的数组,初始值为0
    return backtrack(nums, nums.size() - 1, total >> 2, size);
}

// index表示访问到当前火柴的位置,target表示正方形的边长,size是长度为4的数组,
// 分别保存正方形4个边的长度
bool backtrack(const vector<int>& nums, int index, int target, vector<int>& size) {
    if (index == -1) {
        // 如果火柴都访问完了,并且size的4个边的长度都相等,说明是正方形,直接返回true,
        // 否则返回false
        if (size[0] == size[1] && size[1] == size[2] && size[2] == size[3])
            return true;
        return false;
    }
    // 到这一步说明火柴还没访问完
    for (int i = 0; i < size.size(); i++) {
        // 如果把当前火柴放到size[i]这个边上,他的长度大于target,我们直接跳过。或者
        // size[i] == size[i - 1]即上一个分支的值和当前分支的一样,上一个分支没有成功,
        // 说明这个分支也不会成功,直接跳过即可。
        if (size[i] + nums[index] > target || (i > 0 && size[i] == size[i - 1]))
            continue;
        // 如果当前火柴放到size[i]这个边上,长度不大于target,我们就放上面
        size[i] += nums[index];
        // 然后在放下一个火柴,如果最终能变成正方形,直接返回true
        if (backtrack(nums, index - 1, target, size))
            return true;
        // 如果当前火柴放到size[i]这个边上,最终不能构成正方形,我们就把他从
        // size[i]这个边上给移除,然后在试其他的边
        size[i] -= nums[index];
    }
    // 如果不能构成正方形,直接返回false
    return false;
}

int main() {
    vector<int> nums = {1, 1, 2, 2, 2}; // 示例输入
    bool result = makesquare(nums);
    if (result) {
        cout << "可以拼成正方形" << endl;
    } else {
        cout << "不能拼成正方形" << endl;
    }
    return 0;
}

        回溯算法拥有一个经典的实现框架,其核心思想在于持续地进行尝试。每当遇到一条无法继续的路径时,算法便会回溯到上一步骤,并尝试其他可能的路径。上述代码已配备了详尽的注释,使得理解其运作方式变得尤为容易。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值