JZ27_字符串的排序

题目陈述

大意:给定一个字符串,求出它的字符的所有排序(元素可能有重复),并且答案按从小到大给出

方法一: O ( n n ) O(n^n) O(nn)做法

算法思路

  • 递归搜索进行搜素,对于第h层,枚举第h个位置,应该填入什么下标的元素
  • 注意:此处因为字符串可能有重复的元素,故我们记录的是下标,因为下标不可能重复(相当于一种没有哈希冲突的哈希函数)
  • 然后我们也需要开一个bool数组来记录哪些下标没有被使用过,确保同一下标只被使用过一次
  • 递归边界条件h==s.size(),因为第h层代表正在枚举第h个位置(即前面h-1个位置已经枚举完毕),所以第h=s.size()层代表前面s.size()-1层都已经枚举完毕
  • 回缩的时候,记得将原来状态恢复即可
  • 时间复杂度 O ( n n ) O(n^n) O(nn),每一层都需要枚举所有数是否能使用,总共需要枚举n层

代码实现

class Solution {
public:
    void dfs(int h,string &s,string cur,set &ans,vector &f){
        if(h==s.size()){//递归边界
            ans.insert(cur);//找到一个答案并且插入
            return ;
        }
        for(int i=0;i<s.size();i++){
            if(!f[i]){//当前位置的字符未被使用
                f[i]=1;//则使用
                cur+=s[i];//更新当前字符排序
                dfs(h+1,s,cur,ans,f);//递归下一层
                cur.pop_back();//还原当前字符排序
                f[i]=0;//还原f
            }
        }
    }
    vector Permutation(string str) {
        if(str.empty())return {};
        set ans;//因为题目说str中可能有重复元素,所以需要集合来去重,并且起到从小到大排序作用
        vector f(str.size(),0);//用来标记第i个位置是否已被使用
        string cur;//当前的搜索出来的字符串
        dfs(0,str,cur,ans,f);//进行递归搜索字符串排序
        return vector({ans.begin(),ans.end()});//vector迭代器拷贝初始化,只需要元素的类型一样即可,跟容器无关
    }
};

思考:是否一定需要用set

  • 肯定有同学在这边会有疑问,set内嵌红黑树,每次维护需要花费 log ⁡ 2 n \log{2n} log2n的代价,但是如果我们一开始先将str排序一下,是不是就不需要set了呢?或者可以将set换成vector?
  • 答案是显然的,不可以。为什么?因为题目里面的元素可能是重复的,所以最后总的排序个数可能小于(N!)
  • 就算我们先排序,最后求出的答案,在vector中依旧会面临一个去重的问题,依旧需要借助set来执行,所以此处我们set还是不能被替换为vector的

算法二: O ( n ! ) O(n!) O(n!)做法

算法思路

  • 我们可以发现,全排列的个数也就刚好 A n n = n ! A_n^n=n! Ann=n!个,我们这个算法的时间复杂度也刚好是 O ( n ! ) O(n!) O(n!),可以说,已经是最优的了
  • 此处我们先说算法的复杂度,随后再证明正确性
  • 对于字符串s,递归搜索,对于第h层,依次和下标在[h,s.size()-1]中的元素互换
  • 递归边界为h==s.size()-1,(自己跟自己换此时没有意义)
  • 我们先来看递归代码,顺带证明正确性

证明:算法正确性

    void dfs(int h,string s,set &ans){
        if(h==s.size()-1){//递归边界
            ans.insert(s);//找到一个排序,并且插入
            return ;
        }
        for(int i=h;i<s.size();i++){//假设原来的元素映射到他的下标,那么我们搜索的是下标的全排序
            swap(s[i],s[h]);
            dfs(h+1,s,ans);//进入下一层递归
            swap(s[i],s[h]);//还原s
        }
    }

复杂度分析

  • 对于第i层循环,设第i层的时间复杂度为f(i),不难发现,对于第二层for循环有, f ( i ) = ( s i z e − i ) ∗ f ( i + 1 ) f(i)=(size-i)*f(i+1) f(i)=(sizei)f(i+1),即循环执行n-i次,因为有这么多个位置允许被互换,内层又执行递归第i+1层
  • 不难得出f(size-1)=1,易得 T ( n ) = f ( 0 ) = n ! T(n)=f(0)=n! T(n)=f(0)=n!,故该算***生成 n ! n! n!个排序

互异性证明

  • 接下来只需要证明,递归搜索生成的序列,具有互异性,即两两不同,则该算法正确
  • 反证法
  • 假设可以通过不同的互换,得到两个相同的序列,设前面[0,i-1]个位置的互换都相同,在第i层,互换不同,即i和j换,和i和k换,且 ( j ! = k ) (j!=k) (j!=k),
  • 又,最后的序列相同,后续的交换无法改变前面的位置,则推出 j = = k j==k j==k
  • 故矛盾,则不存在不同的交换方式,使得两个序列的一样的,证毕
  • 注意:此处下标所生成的排序不是字典序递增!

动画演示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cHtuvoSW-1624100679373)(https://img-blog.csdnimg.cn/20210619175656586.png?x-oss-process=image/watermark],type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0doMHN0X0x4,size_16,color_FFFFFF,t_70)

代码实现

C++

class Solution {
public:
    void dfs(int h,string s,set &ans){
        if(h==s.size()-1){//递归边界
            ans.insert(s);//找到一个排序,并且插入
            return ;
        }
        for(int i=h;i<s.size();i++){//假设原来的元素映射到他的下标,那么我们搜索的是下标的全排序
            swap(s[i],s[h]);
            dfs(h+1,s,ans);//进入下一层递归
            swap(s[i],s[h]);//还原s
        }
    }
    vector Permutation(string str) {
        if(str.empty())return {};
        set ans;//因为题目说str中可能有重复元素,所以需要集合来去重,并且起到从小到大排序作用
        dfs(0,str,ans);//开始搜索字符串
        return vector({ans.begin(),ans.end()});//vector迭代器拷贝初始化,只需要元素的类型一样即可,跟容器无关
    }
};

python

递归枚举不要s[i]的permutation,然后再拼接上去,递归边界,空串或者一个字符

class Solution(object):
    def Permutation(self, ss):
        n=len(ss)
        if n<=1:#递归边界,空串或者一个字符
            return ss
        lst=[]
        for i in range(n):
            s1=ss[i]
            for s2 in self.Permutation(ss[:i]+ss[i+1:]):#递归搜索,不取ss[i]
                new_str=s1+s2# s1和s2拼接成新的排序
                if new_str not  in lst :#如果当前没有这个排序
                    lst.append(new_str)    
        lst=sorted(lst)#字典序,从小到大排序
        return lst
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个使用插入排序实现的8086汇编程序示例,该程序从键盘输入一个长度不超过 10 的字符串,对字符串中的字符进行排序(升序),并输出排序后的字符串: ``` DATA SEGMENT STR DB 11 DUP ('$') ; 存储输入的字符串 LEN DB 0 ; 字符串长度 DATA ENDS CODE SEGMENT START: MOV AX, DATA ; 初始化数据段寄存器 MOV DS, AX ; 从键盘输入字符串 MOV AH, 0AH LEA DX, STR ; DX 存储字符串地址 INT 21H ; 计算字符串长度 MOV AL, STR[1] SUB AL, '0' MOV LEN, AL ; 对字符串进行排序 MOV BX, 1 ; BX 存储已排序序列的末尾下标 MOV CX, LEN ; CX 存储未排序序列的长度 OUTER_LOOP: DEC CX ; 将未排序序列长度减 1 JZ END_SORT ; 如果未排序序列长度为 0,排序完成 MOV AL, STR[BX+1] ; AL 存储待插入元素 MOV SI, BX ; SI 存储已排序序列的最后一个元素下标 INNER_LOOP: CMP SI, 0 ; 如果已排序序列为空,跳过比较 JL INSERT ; 如果已排序序列中没有比待插入元素大的元素,插入到最前面 MOV DL, STR[SI+1] ; DL 存储当前元素 CMP AL, DL ; 比较待插入元素与当前元素大小 JGE INSERT ; 如果待插入元素大于等于当前元素,插入到当前元素后面 ; 如果待插入元素小于当前元素,将当前元素后移一位 MOV STR[SI+2], DL DEC SI JMP INNER_LOOP INSERT: ; 将待插入元素插入到合适位置 MOV STR[SI+2], AL INC BX JMP OUTER_LOOP END_SORT: ; 排序完成后,输出排序后的字符串 LEA DX, STR MOV AH, 09H INT 21H MOV AH, 4CH ; 退出程序 INT 21H CODE ENDS END START ``` 该程序使用了插入排序算法,通过将待排序的元素插入到已排序序列中适当的位置,不断扩大已排序序列的范围,最终得到一个升序排列的字符串。在程序中,变量 BX 存储已排序序列的末尾下标,SI 存储已排序序列的最后一个元素下标,AL 存储待插入元素的值,DL 存储当前元素的值。在循环中,每次比较待插入元素与当前元素的大小,如果待插入元素大于等于当前元素,则插入到当前元素后面,否则将当前元素后移一位,直到找到合适的位置插入待插入元素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值