【2.2】回溯算法-解含有重复数字的全排列 II

news2024/11/15 7:15:18

一、题目

        给定一个可包含 重复数字 的序列nums,按任意顺序返回所有不重复的全排列。

二、求解思路及代码实现

回溯算法思路:

        这道题目与之前讨论的全排列问题类似,但有一个关键的区别:本题中数组包含重复的数字,而之前的则没有重复数字。由于存在重复数字,这会导致生成重复的排列组合。因此,本题的关键在于如何有效地过滤掉这些重复的组合。如果不进行过滤,生成的排列中将会包含大量重复项。为了更直观地展示这一点,我们可以通过一个图示来演示示例一的情况,其中为了区分第一个1和第二个1,我们分别用黑色和红色进行标记。

        为了过滤掉重复的数字并避免生成重复的排列组合,我们可以采用一种称为“剪枝”的策略。具体步骤如下:

        1. **排序数组**:首先对数组进行排序,这样相同的数字就会相邻排列。

        2. **剪枝条件**:在遍历数组生成排列的过程中,当我们遇到当前数字与前一个数字相同,并且前一个数字没有被使用时,我们就跳过当前分支,即进行剪枝。这样可以有效地避免生成重复的排列。

        通过这种方式,我们可以在生成排列的过程中直接过滤掉重复的组合,而不需要事后进行复杂的数组比较。下图展示了这一剪枝过程的示意图,帮助我们更直观地理解如何在生成排列时避免重复。

代码实现:

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

using namespace std;

void backtrack(vector<int>& nums, vector<bool>& used, vector<int>& tempList, vector<vector<int>>& res) {
    // 如果数组中的所有元素都使用完了,类似于到了叶子节点,
    // 我们直接把从根节点到当前叶子节点这条路径的元素加入
    // 到集合res中
    if (tempList.size() == nums.size()) {
        res.push_back(tempList);
        return;
    }
    // 遍历数组中的元素
    for (int i = 0; i < nums.size(); i++) {
        // 如果已经被使用过,则直接跳过
        if (used[i])
            continue;
        // 注意,这里要剪掉重复的组合
        // 如果当前元素和前一个一样,并且前一个没有被使用过,我们也跳过
        if (i > 0 && nums[i - 1] == nums[i] && !used[i - 1])
            continue;
        // 否则我们就使用当前元素,把他标记为已使用
        used[i] = true;
        // 把当前元素nums[i]添加到tempList中
        tempList.push_back(nums[i]);
        // 递归,类似于n叉树的遍历,继续往下走
        backtrack(nums, used, tempList, res);
        // 递归完之后会往回走,往回走的时候要撤销选择
        used[i] = false;
        tempList.pop_back();
    }
}

vector<vector<int>> permuteUnique(vector<int>& nums) {
    // 先对数组进行排序,这样做目的是相同的值在数组中肯定是挨着的,
    // 方便过滤掉重复的结果
    sort(nums.begin(), nums.end());
    vector<vector<int>> res;
    // boolean数组,used[i]表示元素nums[i]是否被访问过
    vector<bool> used(nums.size(), false);
    // 执行回溯算法
    vector<int> tempList;
    backtrack(nums, used, tempList, res);
    return res;
}

int main() {
    vector<int> nums = {1, 1, 2};
    vector<vector<int>> result = permuteUnique(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;
}

        除了之前提到的剪枝方式,我们还可以采用另一种剪枝策略:在遍历数组生成排列的过程中,如果当前数字与数组中前一个数字相同,并且前一个数字已经被使用,我们就跳过当前分支,即进行剪枝。这种剪枝方式与之前的剪枝方式相反,但同样可以有效地避免生成重复的排列。下图展示了这种剪枝过程的示意图,帮助我们更直观地理解如何在生成排列时避免重复。

        这两种剪枝方式都是可以的, 一种是把整个大枝剪掉,一种是在每个大枝下面不停的剪小枝 。很明显第一 种剪枝效率更高一些,我们来看下代码

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

using namespace std;

void backtrack(vector<int>& nums, vector<bool>& used, vector<int>& tempList, vector<vector<int>>& res) {
    // 如果数组中的所有元素都使用完了,类似于到了叶子节点,
    // 我们直接把从根节点到当前叶子节点这条路径的元素加入
    // 到集合res中
    if (tempList.size() == nums.size()) {
        res.push_back(tempList);
        return;
    }
    // 遍历数组中的元素
    for (int i = 0; i < nums.size(); i++) {
        // 如果已经被使用过,则直接跳过
        if (used[i])
            continue;
        // 注意,这里要剪掉重复的组合
        // 如果当前元素和前一个一样,并且前一个被使用了,我们也跳过
        if (i > 0 && nums[i - 1] == nums[i] && used[i - 1])
            continue;
        // 否则我们就使用当前元素,把他标记为已使用
        used[i] = true;
        // 把当前元素nums[i]添加到tempList中
        tempList.push_back(nums[i]);
        // 递归,类似于n叉树的遍历,继续往下走
        backtrack(nums, used, tempList, res);
        // 递归完之后会往回走,往回走的时候要撤销选择
        used[i] = false;
        tempList.pop_back();
    }
}

vector<vector<int>> permuteUnique(vector<int>& nums) {
    // 先对数组进行排序,这样做目的是相同的值在数组中肯定是挨着的,
    // 方便过滤掉重复的结果
    sort(nums.begin(), nums.end());
    vector<vector<int>> res;
    // boolean数组,used[i]表示元素nums[i]是否被访问过
    vector<bool> used(nums.size(), false);
    // 执行回溯算法
    vector<int> tempList;
    backtrack(nums, used, tempList, res);
    return res;
}

int main() {
    vector<int> nums = {1, 1, 2};
    vector<vector<int>> result = permuteUnique(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;
}
上面两种代码非常相似,唯一不同的就是下面这行,其他的都一样。
 if (i > 0 && nums[i - 1] == nums[i] && used[i - 1])
如果让我们选择的话,我们肯定会选择第一种方式,把整个大的枝给剪掉。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2038015.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot集成Proguard生成混淆jar包

背景 当我们需要将 JAR 包交付给第三方时&#xff0c;常常担心代码可能会被反编译。因此&#xff0c;对 JAR 包进行混淆处理显得尤为重要。 市面上有许多 JAR 包源码混淆工具&#xff0c;但真正能稳定投入使用的并不多。例如&#xff0c;ClassFinal (ClassFinal: Java字节码加…

C++类和对象1

一.类的定义 1.1类的创建 类是C中用户自己建立的类型。类似于C语言中的结构体。定义类的关键字为class。格式为&#xff1a; class 类名 {成员函数成员变量…… }; class 类名称为类头&#xff0c;花括号中的称为类体。类的声明以花括号后的分号结束&#xff0c;分号不可省…

Go-Zero微服务框架下开发接口流程

目录 一&#xff1a;定义api入参和返回值 二&#xff1a;生成入参和返回值文件 三&#xff1a;定义rpc参数和返回值 四&#xff1a;生成返回值和参数 五&#xff1a;定义数据库 六&#xff1a;生成数据库文件 今天我们来讲解下如何在Go-Zero下开发一个api接口的具体流程&…

UCOSIII内存管理机制详解

目录 前言 1. 内存管理概述 2. 内存区域&#xff08;存储区&#xff09;和内存块 3. 存储区控制块&#xff08;OS_MEM&#xff09; 4. 内存管理函数 5. 内存碎片问题 6. 注意事项 7.代码实现 7.1创建内存区域 7.2申请内存 7.3释放内存 前言 UCOSIII&#xff08;即Mi…

算法的学习笔记—正则表达式匹配的动态规划算法解析

&#x1f600;前言 正则表达式是一种强大的工具&#xff0c;广泛应用于文本匹配和处理。在许多编程任务中&#xff0c;我们可能会遇到需要匹配字符串与某个特定模式的情况。本文将介绍如何使用动态规划算法实现一个支持 . 和 * 的正则表达式匹配功能&#xff0c;并以 Java 为例…

中科院TOP“灌水神刊”合集!年发文量动辄数千篇,TOP的地位,4区的录用率!

【SciencePub学术】本期&#xff0c;给大家推荐几本环境领域的“灌水神刊”&#xff01;均隶属于中科院TOP刊之列&#xff0c;但是每年庞大的发文量致使投稿接收率极高&#xff01;话不多说&#xff0c;想“灌水”的建议收藏&#xff01; 01 年刊文量4000 Journal of Cleaner …

【C++】---红黑树详解

【C】---红黑树详解 一、什么是红黑树&#xff1f;1、概念2、性质3、四个规则 二、红黑树的定义1、红黑树 结点 定义&#xff08;1&#xff09;将新插入的结点 设置为黑色&#xff08;2&#xff09;将新插入的结点 设置为红色 2、红黑树的定义 三、红黑树插入1、插入节点2、控制…

Zabbix自动导出PDF报告

zabbix6提供了定时导出PDF报告功能。此功能可按照Dashboard维度&#xff0c;定时自动导出报告&#xff0c;并通过邮件发送。 1.安装 zabbix 提供了官方的rhel8版本的rpm包&#xff0c;可使用yum方式安装&#xff0c;zabbix自动导出PDF功能是基于go环境的zabbix web service程…

应用方案 | 低功耗接地故障控制器D4145

一、概述 D4145 是一个接地故障断路器。它能够检测到不良的接地条件&#xff0c;譬如装置接触到水时&#xff0c;它会在有害或致命的电击发生之前将电路断开。 D4145能检测并保护从火线到地线,从零线到地线的故障.这种简单而传统的电路设计能够确保其应用自如和长时间的可靠性。…

Vue3+Echarts+Setup实现动态曲线堆叠图+图例分页

提前安装引入echarts 效果图 dom实例 <div id"rightCharterwang" style"height: 28vh"></div> 配置项&#xff0c;将数据换成从接口请求回来的数据&#xff08;这里是写死的假数据&#xff09; const rightCharterwang () > {let named…

vcruntime140_1.dll丢失是什样的错误?五种vcruntime140_1.dll修复方法详细步骤教程

对于经常使用Windows操作系统的用户来说&#xff0c;面对“vcruntime140_1.dll文件丢失”的错误提示可能既熟悉又令人苦恼。这个错误通常发生在尝试启动或安装一些依赖于此特定DLL文件的应用程序时&#xff0c;在本文中&#xff0c;我们将详细介绍 ​vcruntime140_1.dll​ 所承…

使用Python和Pillow创建照片马赛克应用

在这篇博客中,我们将探讨如何使用Python创建一个简单而有趣的桌面应用程序。我们的目标是构建一个应用,允许用户选择一张照片,然后在照片的右下角添加马赛克效果。这个项目将展示如何结合使用wxPython来创建图形用户界面(GUI)和Pillow库来处理图像。 D:\spiderdocs\eraselogo.p…

Linux 基本指令讲解 上

linux 基本指令 clear 清屏 Alt Enter 全屏/退出全屏 pwd 显示当前用户所处路径 cd 改变目录 cd /root/mikecd … 返回上级目录cd - 返回最近所处的路径cd ~ 直接返回当前用户自己的家目 roor 中&#xff1a;/root普通用户中&#xff1a;/home/mike mkdir 创建一个文件夹(d) …

通义灵码:AI 研发趋势与效果提升实践丨SDCon 全球软件技术大会演讲全文整理

作者&#xff1a;张昕东 大家好&#xff0c;我是来自阿里云通义灵码团队的张昕东。很高兴和各位同仁做这次分享&#xff0c;分享的主题是人机协同趋势与效果提升实践。我们所做的模型提升和功能开发是为了促进人机在研发领域的协同&#xff0c;而当今的人机协同现状又决定了我…

基于Spring Boot的农田智能管理系统

目录 前言 功能设计 系统实现 获取源码 博主主页&#xff1a;百成Java 往期系列&#xff1a;Spring Boot、SSM、JavaWeb、python、小程序 前言 农田智能管理系统是基于SpringBoot框架开发的一款针对农田管理的智能化平台。随着农业现代化的发展&#xff0c;农田管理需要更…

docker拉取kafka镜像|启动kafka容器

1、kafka官网快速开始模块查看如何拉取kafka的docker镜像 https://kafka.apache.org/quickstart 2、移除本机已拉取kafka的docker镜像 docker rmi apache/kafka:3.7.03、拉取kafka的docker镜像 docker pull apache/kafka:3.7.04、启动kafka容器 docker run -p 9092:9092 ap…

iOS 18(macOS 15)Vision 中新增的任意图片智能评分功能试玩

概述 在 WWDC 24 中库克“大厨”除了为 iOS 18 等平台重磅新增了 Apple Intelligence 以外&#xff0c;苹果也利用愈发成熟的机器学习引擎扩展了诸多内置框架&#xff0c;其中就包括 Vision。 想用本机人工智能自动为我们心仪的图片打一个“观赏分”吗&#xff1f;“如意如意&…

【2.3】回溯算法-重新排序得到 2 的幂

一、题目 给定正整数N&#xff0c;我们按任何顺序&#xff08;包括原始顺序&#xff09;将 数字重新排序 &#xff0c;注意其前导数字不能为零。 如果我们可以通过上述方式得到2的幂&#xff0c;返回 true&#xff1b;否则&#xff0c;返回false。 提示&#xff1a; 1 < …

学习记录第二十六天

进程运行 1&#xff0c;子进程和父进程做相同的事----创建子进程 执行任务 2&#xff0c;子进程做与父进程不同的事 ----fork exec exec族 l VS v :主要是第二个参数的传参方式不同 p :表示寻找可执行文件 是通过PATA环境变量 e : 表示可以给…

升级软文发稿开源系统源码论文期刊一键发布

升级软文发稿运营管理源码—论文期刊一键发布 软文发稿系统源码&#xff08;软文发布系统&#xff09;在基于旧版本的媒介软文发布平台项目改造升级了新的功能模块简称&#xff08;3.0版&#xff09;本系统还是基于开源的PHPMYSQLlayui&#xff08;前端界面&#xff09;代码进行…