《程序员面试金典(第6版)》面试题 08.08. 有重复字符串的排列组合(回溯算法,全排列问题)C++

题目描述

有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。

示例1:

  • 输入:S = “qqe”
    输出:[“eqq”,“qeq”,“qqe”]

示例2:

  • 输入:S = “ab”
    输出:[“ab”, “ba”]

提示:

  • 字符都是英文字母。
    字符串长度在[1, 9]之间。

解题思路与代码

这道题一看还是一道关于排列的问题。只要有关排列的问题,我们都可以通过回溯法去解决。

方法一: 回溯法 + 使用unordered_set数据结构进行去重

如果没有做过《程序员面试金典(第6版)》面试题 08.07. 无重复字符串的排列组合(回溯算法,全排列问题)C++
这道题的小伙伴,先去做一下这道题。

这道题与上面链接的那道题非常像,只不过,这里字符串中的字符开始出现有重复的字符了。所以我们做这道题的时候就需要去重。我们直接用unordered_set这种数据结构去去一下重好了。代码与上道题的代码没什么区别,这里给出这道题的代码:

class Solution {
public:
    vector<string> permutation(string S) {
        unordered_set<string> result;
        backtracking(S,result,0);
        vector<string> vec;
        for(auto a : result){
            vec.push_back(a);
        }
        return vec;
    }
    void backtracking(string& S,unordered_set<string>& result,int begin){
        if(begin == S.size()){
            result.insert(S);
            return;
        }
        for(int i = begin;i < S.size(); ++i){
            swap(S[i],S[begin]);
            backtracking(S,result,begin+1);
            swap(S[i],S[begin]);
        }
    }
};

在这里插入图片描述

复杂度分析

时间复杂度:

  • 这段代码的时间复杂度主要取决于两个部分:backtracking 函数的执行次数以及将结果从 unordered_set 转移到 vector 的时间。

  • backtracking 函数的执行次数:对于长度为 n 的字符串,我们需要对每个字符进行排列组合,这会产生 n! 个排列。在回溯算法中,我们会遍历整个排列空间。因此,backtracking 函数的执行次数为 O(n!)。

  • 将结果从 unordered_set 转移到 vector:result 中最多有 n! 个元素。遍历 result 并将其中的元素插入 vector 的时间复杂度为 O(n!)。

  • 综合这两部分,总的时间复杂度为 O(n!)。

空间复杂度:

  • 空间复杂度主要取决于三个方面:递归调用栈的深度、结果存储在 unordered_set 中所占用的空间,以及结果向量 vec 所占用的空间。

  • 递归调用栈的深度:在回溯算法中,递归调用栈的深度等于字符串的长度 n。因此,递归调用栈的空间复杂度为 O(n)。

  • 结果存储在 unordered_set 中所占用的空间:result 中最多有 n! 个元素,每个元素是一个长度为 n 的字符串。因此,结果存储在 unordered_set 中所占用的空间复杂度为 O(n * n!)。

  • 结果向量 vec 所占用的空间:vec 中有 n! 个元素,每个元素是一个长度为 n 的字符串。因此,结果向量所占用的空间复杂度为 O(n * n!)。

  • 由于这三部分空间是算法使用的空间,因此总的空间复杂度为这三者之和,即 O(n) + O(n * n!) + O(n * n!) = O(2 * n * n!) = O(n * n!)。

  • 所以,这段代码的时间复杂度为 O(n!),空间复杂度为 O(n * n!)。

方法二 :对代码一的方法进行优化。

在这道题里,因为可能有重复的字符,方法一是直接用unordered_set在结果处进行去重,重复的答案不会被存进集合中,但这种方法不会减少递归的此时。那有没有一种方法,可以在做交换的时候就进行剪枝操作而进行去重呢,而去减少递归的次数呢?

答案当然是有,我们可以通过一个unordered_set去记住在当前的这一层循环里出现过哪些字符,如果出现了重复的字符,那我们就跳过这次交换,直接进入下一次交换。这样也同样达到了去重的目的,也减少了递归的次数。但是不好的一点是增加了内存的存储空间,因为每一层递归都要创建一个unordered_set去存储出现过的字符。

具体代码如下:

class Solution {
public:
    vector<string> permutation(string S) {
        vector<string> result;
        backtracking(S, result, 0);
        return result;
    }

    void backtracking(string &S, vector<string> &result, int begin) {
        if (begin == S.size()) {
            result.push_back(S);
            return;
        }
        unordered_set<char> used_chars;  // 用于存储已经在当前位置出现过的字符
        for (int i = begin; i < S.size(); ++i) {
            if (used_chars.find(S[i]) != used_chars.end()) {
                continue;  // 如果当前字符已经在当前位置出现过,则跳过这次交换
            }
            used_chars.insert(S[i]);  // 记录当前字符
            swap(S[i], S[begin]);
            backtracking(S, result, begin + 1);
            swap(S[i], S[begin]);
        }
    }
};

在这里插入图片描述

复杂度分析

通过这种剪枝策略,我们避免了搜索重复的路径,从而降低了时间复杂度。然而,在最坏情况下(如所有字符都不同),算法的时间复杂度仍然是 O(n!)。空间复杂度与之前的分析相同,为 O(n * n!)。虽然这种剪枝策略不能在理论上改进时间复杂度,但在有重复字符的情况下,实际运行效率会有所提升,但是同样每一层都会多创建出一个unordered_set去存储至多n个字符,会多消耗一部分的内存空间。

方法三,对代码二的再次优化!

这一次我们写了一个hasDuplicate函数来检查有没有重复出现的字符。这样就不会使用额外的内存空间去存储字符了。
因为begin的值肯定是要比i小的,因为i会递增,而begin不会,从而我们在这个函数中,去递增begin,看看有没有会出现与i相当的字符,如果出现了,就说明有重复,就要跳过这个循环!

class Solution {
public:
    vector<string> permutation(string S) {
        vector<string> result;
        backtracking(S,result,0);
        return result;
    }
    void backtracking(string& S,vector<string>& result,int begin){
        if(begin == S.size()) {
            result.push_back(S);
            return;
        }
        for(int i = begin; i < S.size(); ++i){
            if(hasDuplicate(S,begin,i)) continue;
            swap(S[i],S[begin]);
            backtracking(S,result,begin+1);
            swap(S[i],S[begin]);
        }
    }
    bool hasDuplicate(string& S, int begin,int end){
        for(int i = begin; i < end; ++i)
            if(S[i] == S[end]) return true;
        return false;
    }
};

在这里插入图片描述

复杂度分析

时间复杂度:

  • permutation 函数的时间复杂度主要取决于 backtracking 函数。在最坏情况下,回溯算法将尝试所有可能的排列组合,即 n!。
  • hasDuplicate 函数的时间复杂度为 O(n)(在 for 循环内部进行比较)。
  • backtracking 函数中调用了 hasDuplicate 函数,所以在最坏情况下,总时间复杂度为 O(n! * n)。

空间复杂度:

  • 结果向量 result 的空间复杂度为 O(n!),因为它需要存储所有排列组合。
  • 递归栈的空间复杂度为 O(n),因为最深的递归调用次数等于字符串的长度。
  • 总的空间复杂度为 O(n! + n)。

综上所述,该算法的时间复杂度为 O(n! * n),空间复杂度为 O(n! + n)。

虽然说,代码1,2,3的时间复杂度都是O(n!),但是在代码三的实际时间复杂度要比1,2快了不少。

总结

这道题不算一道特别难的题。但是呢,剪枝和去重,才是这道题的重中之重。写出简洁并且高效的回溯算法并不容易。我们还得去多学习多总结!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis持久层框架 | Lombok搭建

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Lombok Lombok项目是一个java库&#xff0c;它可以自动插入到编辑器和构建工具中&#xff0c;增强java的性能。不需要再写getter、setter或equals方法&#xff0c;只要…

自然语言大模型介绍

1 简介 最近一直被大语言模型刷屏。本文是周末技术分享会的提纲&#xff0c;总结了一些自然语言模型相关的重要技术&#xff0c;以及各个主流公司的研究方向和进展&#xff0c;和大家共同学习。 2 Transformer 目前的大模型基本都是Transformer及其变种。本部分将介绍Transf…

【WEB前端进阶之路】 HTML 全路线学习知识点梳理(上)

前言 HTML 是一切Web开发的基础&#xff0c;本文专门为小白整理&#xff0c;针对前端零基础的朋友们&#xff0c;手把手教你学习HTML&#xff0c;让你轻松迈入WEB开发的行列。 首先&#xff0c;感谢 橙子_ 在HTML学习以及本文编写过程中对我的帮助。 文章目录前言一.HTML简介1.…

【NLP经典论文阅读】Efficient Estimation of Word Representations in Vector Space(附代码)

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

二值mask转polygon/RLE (coco segment格式)

coco数据集annotation的segmentation并不是二值mask&#xff0c;而是polygon格式&#xff0c; 看一个annotation. {"segmentation": [[510.66,423.01,511.72,420.03,510.45......]], #两两组成(x,y)坐标&#xff0c;polygon格式"area": 702.1057499999998…

腾讯自研万亿级NLP大模型,自动生成和衍生广告文案

编者按&#xff1a;随着大数据与AI技术的不断发展&#xff0c;人们越来越看见AI大模型在数据理解、运算以及诸多泛化能力上的潜力&#xff0c;时下&#xff0c;大模型已然成为学术界与工业界探索的重点方向。然而&#xff0c;随着模型规模与容量的不断扩大&#xff0c;其所需训…

mac 把word公式默认字体Cambria Math换成LaTex字体以及带章节自动编号

word默认是Cambria Math&#xff0c;想用latex那种公式的字体&#xff0c;这里使用的是XITS Math字体 搜了很多地方&#xff0c;都是用ab Text这个方法先转成文本&#xff0c;再换字体&#xff0c;然后设置斜体 可是公式多起来的话这种办法很麻烦&#xff0c;而且一个公式里常…

PyTorch深度学习实战 | 典型卷积神经网络

在深度学习的发展过程中&#xff0c;出现了很多经典的卷积神经网络&#xff0c;它们对深度学习的学术研究和工业生产都起到了巨大的促进作用&#xff0c;如VGG、ResNet、Inception和DenseNet等&#xff0c;很多投入实用的卷积神经都是在它们的基础上进行改进的。初学者应从试验…

C语言实现堆

注&#xff1a;这里我们所实现的是大根堆&#xff08;即父节点不小于子节点的堆&#xff09; 目录 一&#xff0c;堆的介绍 二&#xff0c;堆结构的创建 三&#xff0c;接口实现 1&#xff0c;初始化与销毁 2&#xff0c;数据的插入与删除 3&#xff0c;其他接口 一&…

力扣:最后一个单词的长度(详解)

前言&#xff1a;内容包括&#xff1a;题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读 题目&#xff1a; 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组…

基于springboot实现留守儿童爱心网站平台【源码+论文】

基于springboot实现留守儿童爱心网站演示开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&…

qt 关于QtXlsx的编译 使用

版本&#xff1a;qt 5.14.0 qt creator4.11.0 平时用mingw编译器 QtXlsx源码下载地址&#xff1a;QtXlsxWriter&#xff1a;https://github.com/dbzhang800/QtXlsxWriter 在Qt的XLSX模块提供了一组类来读写Excel文件。它不需要 Microsoft Excel&#xff0c;可以…

EM7电磁铁的技术参数

电磁铁可以通过更换电磁铁极头在一定范围内改善磁场的大小和磁场的均匀度 &#xff0c;并且可以通过调整极头间距改变磁场的大小。主要用于磁滞现象研究、磁化系数测量、霍尔效应研究、磁光实验、磁场退火、核磁共振、电子顺磁共振、生物学研究、磁性测量、磁性材料取向、磁性产…

期货黄金交易平台重要吗?有哪些显著的期货黄金交易平台优势?

黄金交易平台就是可以在其上面做黄金买卖交易的系统&#xff0c;是一种依靠行业应用软件而搭建的平台&#xff0c;里面会包含一些交易指标、趋势图表、K线。市场上的黄金交易平台很多&#xff0c;只有正规的期货黄金交易平台才值得信任。主要还是因为期货黄金交易平台优势所决定…

【五】线程安全VS线程不安全

1. Java内存模型的特征 Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立。下面逐个看下哪些操作实现这三个特性&#xff1a; 1.1 原子性&#xff08;Atomicity&#xff09; 由Java内存模型来直接保证的原子性变量操作包括 read、load、assig…

【机器学习】线性回归

文章目录前言一、单变量线性回归1.导入必要的库2.读取数据3.绘制散点图4.划分数据5.定义模型函数6.定义损失函数7.求权重向量w7.1 梯度下降函数7.2 最小二乘法8.训练模型9.绘制预测曲线10.试试正则化11.绘制预测曲线12.试试sklearn库二、多变量线性回归1.导入库2.读取数据3.划分…

Linux--抓包-连接状态

目录 一、TCP&#xff1a; 1.抓包&#xff1a; 2.工具&#xff1a; 3.状态&#xff1a; 4.命令&#xff1a; 三次握手&#xff1a; 应答确认&#xff1a; 四次挥手 一、TCP&#xff1a; 面向连接、可靠的、流式服务 1.抓包&#xff1a; 三次握手、四次挥手 2.工具&…

数据库:Redis数据库

目录 一、数据库类型 1、关系型数据库 2、非关系型数据库 3、关系型非关系型区别 二、Redis数据库 1、什么是Redis 3、Redis特点 4、Redis为什么读写快 5、部署Redis数据库 6、redis管理 7、Redis数据库五大类型 8、Redis数据库基础使用 9、redis五大类型增删查 …

数据库管理-第六十三期 烦(20230327)

数据库管理 2023-03-27第六十三期 烦1 跨版本PDB迁移补遗2 BUGs3 就低不就高总结第六十三期 烦 上个周末呢&#xff0c;因为一些客户的事情整的一个周末都在干活&#xff0c;其中两天还搞到的晚上12点&#xff0c;几乎没咋休息&#xff0c;现在感觉贼累&#xff0c;继续写文章…

为什么我们认为GPT是一个技术爆炸

从23年初&#xff0c;ChatGPT火遍全球&#xff0c;通过其高拟人化的回答模式&#xff0c;大幅提升了人机对话的体验和效率&#xff0c;让用户拥有了一个拥有海量知识的虚拟助手&#xff0c;根据UBS发布的研究报告显示&#xff0c;ChatGPT在1月份的月活跃用户数已达1亿&#xff…