【2.1】回溯算法-全排列

一、题目

        给定一个 不含重复数字 的数组nums,返回其所有可能的 全排列 。你可以按任意顺序返回答案。

二、解题思路

        全排列问题确实是一道经典的算法题,可以通过回溯算法来解决。具体实现细节可以进一步探讨。假设我们有一个长度为n的数组,我们可以将回溯算法视为一棵n叉树的前序遍历。在这棵树中,第一层有n个子节点,第二层有n-1个子节点,依此类推。为了更好地理解,我们可以通过一个具体的例子来详细说明。

代码实现:

#include <iostream>
#include <vector>

using namespace std;

void backtrack(vector<vector<int>>& list, vector<int>& tempList, vector<int>& nums) {
    // 终止条件,如果数字都被使用完了,说明找到了一个排列
    if (tempList.size() == nums.size()) {
        list.push_back(tempList);
        return;
    }
    // 遍历n叉树每个节点的子节点
    for (int i = 0; i < nums.size(); i++) {
        // 因为不能有重复的,所以有重复的就跳过
        if (find(tempList.begin(), tempList.end(), nums[i]) != tempList.end())
            continue;
        // 选择当前值
        tempList.push_back(nums[i]);
        // 递归遍历子节点的子节点
        backtrack(list, tempList, nums);
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> list;
    vector<int> tempList;
    backtrack(list, tempList, nums);
    return list;
}

int main() {
    vector<int> nums = { 1, 2, 3 };
    vector<vector<int>> result = permute(nums);

    for (const auto& perm : result) {
        cout << "[";
        for (int i = 0; i < perm.size(); i++) {
            cout << perm[i];
            if (i < perm.size() - 1) cout << ", ";
        }
        cout << "]" << endl;
    }

    return 0;
}
用数组[1,2,3]来测试一下,看一下打印结果
[1, 2, 3]
        是不是很意外,上面示例给出的是6个结果,这里打印的是1个,这是因为list是引用传递, 当遍历到叶子节点以后要往回走,往回走的时候必须把之前添加的值给移除了 ,否则会越 加越多,我们来看下下面例子

三、代码实现

由此最后代码实现如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void backtrack(vector<vector<int>>& list, vector<int>& tempList, vector<int>& nums) {
    // 终止条件,如果数字都被使用完了,说明找到了一个排列
    if (tempList.size() == nums.size()) {
        list.push_back(tempList);
        return;
    }
    // 遍历n叉树每个节点的子节点
    for (int i = 0; i < nums.size(); i++) {
        // 因为不能有重复的,所以有重复的就跳过
        if (find(tempList.begin(), tempList.end(), nums[i]) != tempList.end())
            continue;
        // 选择当前值
        tempList.push_back(nums[i]);
        // 递归遍历子节点的子节点
        backtrack(list, tempList, nums);
        // 撤销选择,把最后一次添加的值给移除
        tempList.pop_back();
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> list;
    vector<int> tempList;
    backtrack(list, tempList, nums);
    return list;
}

int main() {
    vector<int> nums = { 1, 2, 3 };
    vector<vector<int>> result = permute(nums);

    for (const auto& perm : result) {
        cout << "[";
        for (int i = 0; i < perm.size(); i++) {
            cout << perm[i];
            if (i < perm.size() - 1) cout << ", ";
        }
        cout << "]" << endl;
    }

    return 0;
}

运行结果如下:

[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]

        这道题还可以通过交换元素的方式来利用回溯算法解决。具体来说,我们可以先选择第一个数字,然后将其与后续的所有数字逐一交换,从而确定全排列的第一位。接着,我们选择第二个数字,并将其与后续的所有数字逐一交换,以确定全排列的第二位。这个过程持续进行,直到最后一个数字无法再进行交换。为了更直观地理解这个过程,我们可以绘制一个图示来展示每一步的交换情况。

交换实现:

#include <iostream>
#include <vector>

using namespace std;

void backtrack(vector<int>& nums, int index, vector<vector<int>>& res) {
    // 到最后一个数字,没法再交换了,直接把数组转化为list
    if (index == nums.size() - 1) {
        // 把数组转为list
        vector<int> tempList(nums.begin(), nums.end());
        // 把list加入到res中
        res.push_back(tempList);
        return;
    }
    for (int i = index; i < nums.size(); i++) {
        // 当前数字nums[index]要和后面所有的数字都要交换一遍(包括他自己)
        swap(nums[index], nums[i]);
        // 递归,数组[0,index]默认是已经排列好的,然后从index+1开始后面元素的交换
        backtrack(nums, index + 1, res);
        // 还原回来
        swap(nums[index], nums[i]);
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> res;
    backtrack(nums, 0, res);
    return res;
}

int main() {
    vector<int> nums = {1, 2, 3};
    vector<vector<int>> result = permute(nums);

    for (const auto& perm : result) {
        cout << "[";
        for (int i = 0; i < perm.size(); i++) {
            cout << perm[i];
            if (i < perm.size() - 1) cout << ", ";
        }
        cout << "]" << endl;
    }

    return 0;
}

递归实现:

        我们来探讨这个问题:假设我们有数组[1, 2, 3],如果我们已经知道[2, 3]的所有排列结果,那么我们只需要将1插入到这些排列结果的每一个可能位置,就能得到数组[1, 2, 3]的所有排列。为了更清晰地理解这个过程,我们可以通过绘制一个图示来展示如何将1插入到[2, 3]的每个排列中。

递归代码实现如下:

#include <iostream>
#include <vector>

using namespace std;

vector<vector<int>> helper(vector<int>& nums, int index) {
    vector<vector<int>> res;
    // 递归的终止条件,如果到最后一个数组,直接把它放到res中
    if (index == nums.size() - 1) {
        // 创建一个临时数组
        vector<int> temp;
        // 把数字nums[index]加入到临时数组中
        temp.push_back(nums[index]);
        res.push_back(temp);
        return res;
    }
    // 计算后面数字的全排列
    vector<vector<int>> subList = helper(nums, index + 1);
    // 集合中每个子集的长度
    int count = subList[0].size();
    // 遍历集合subList的子集
    for (int i = 0, size = subList.size(); i < size; i++) {
        // 把当前数字nums[index]添加到子集的每一个位置
        for (int j = 0; j <= count; j++) {
            vector<int> temp = subList[i];
            temp.insert(temp.begin() + j, nums[index]);
            res.push_back(temp);
        }
    }
    return res;
}

vector<vector<int>> permute(vector<int>& nums) {
    return helper(nums, 0);
}

int main() {
    vector<int> nums = {1, 2, 3};
    vector<vector<int>> result = permute(nums);

    for (const auto& perm : result) {
        cout << "[";
        for (int i = 0; i < perm.size(); i++) {
            cout << perm[i];
            if (i < perm.size() - 1) cout << ", ";
        }
        cout << "]" << endl;
    }

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

攻城狮7号

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

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

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

打赏作者

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

抵扣说明:

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

余额充值