【剑指Offer】Python和C写的字符串的排列代码的区别(图解)

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

个人心路历程

做剑指offer一般我用的是python,当发现这道题不会时就去讨论区看别人的答案,排在前面的大部分是c++和java,就选了个写的简洁的递归实现的c++翻译成差不多的python,结果答案完全不一样,写错对于编程初学者而言是很正常的事情,但从逻辑上就是找不到二者的不同,经过一天的分析,终于知道了这是由于不同语言执行的机制。接下来我将给出C++的正确代码用python模仿写出的错误代码执行机制的分析

目录:

首先申明,这道题少有人用python写答案的主要原因是python不能像处理列表(数组)一样处理字符串,尤其是交换字符串内部的两个元素,因为python中字符串是个不可变的,只能依靠生成新字符串实现,如:

#错误代码
str = "abc"
str[0] = d
#是不会实现str被修改成"dbc"的,会报错
#正确写法
str = "abc"
str = 'd'+str[1:]

所以这道题为了简化解释的过程并和C++的实现保持一致,在python代码中我将用列表的形式输入字符串,即实现"abc"的全排列时,输入的是[‘a’,‘b’,‘c’],输出的全排列也是这种列表形式。

对着答案里C++的代码改的python代码:(非能通过的答案)
# -*- coding:utf-8 -*-
class Solution:
    def Permutation(self, ss):

        def re(ss,cur):
            if cur > len(ss)-1:
                print(ss)

            else:
                for i in range(cur,l):
                    if thesame(ss,cur,i):
                        print("跳过")
                        continue

                    swap(ss,cur,i)
                    re(ss,cur+1)
                    swap(ss, i, cur)
                    
        def swap(ss,cur,i):
            a = ss[cur]
            ss[cur] = ss[i]
            ss[i] = a
        def thesame(ss,cur,i):
            for k in range(cur, i):
                if ss[k] == ss[i]:
                    return True
            return False
        re(ss,0)


s = Solution()
result1 =s.Permutation(['a','b','c'])

re是递归的函数,re(ss,cur)中,ss是被传递的列表,cur是当前被循环交换的位置(大循环),i是小循环,i从cur开始遍历到列表最后一个位置,对于"abc"的全排列:
在下图中,cur指向要被交换的元素位置,i指向要交换的元素位置,总之就是交换str[cur]和str[i],如果不够清晰,就再参考下下一张图
在这里插入图片描述在这里插入图片描述
仔细看这个结果,和样例输出其实是不一样的!
样例输出:abc,acb,bac,bca,cab,cba
我的输出:abc,acb,bac,bca,cba,cab
最后 两位的顺序是错误的,当然这个错也被牛客网发现了且不给我通过。
PS:python写的代码目前在牛客网通过率50%,正好卡在"abc"的样例上,还没想到怎么改
那么能通过的C++代码是怎么写的呢?

c++代码
//比较简单的代码,这个题目算经典的
class Solution {
public:
    vector<string> Permutation(string str) {       
        if(str!="") dfs(str, 0);
        return ret;
    }
private:
    vector<string> ret;
    void dfs(string str, int s){
        int sz = str.size();
        if(s==sz){
            ret.push_back(str);
            return;
        }
        for(int i = s; i < sz; ++i){
            if(i!=s && str[s]==str[i]) continue;
            swap(str[s], str[i]);
            dfs(str, s+1);
        }
    }  
};

链接:https://www.nowcoder.com/questionTerminal/fe6b651b66ae47d7acce78ffdd9a96c7?f=discussion
来源:牛客网

dfs是递归的函数,dfs(str, s)中,str是被传递的列表,s是当前被循环交换的位置(大循环),i是从s开始的小循环

1.关于交换函数执行次数

我的两个位置指标i和cur被他换成i和s,每个递归内部也是一个输出判断一个循环体,值得注意的是它的for循环体:

for(int i = s; i < sz; ++i){
            if(i!=s && str[s]==str[i]) continue;
            swap(str[s], str[i]);
            dfs(str, s+1);//dfs是递归的函数
        }

对比我的for循环体

for i in range(cur,l):
                    if thesame(ss,cur,i):
                        print("跳过")
                        continue

                    swap(ss,cur,i)
                    re(ss,cur+1)
                    swap(ss, i, cur)

我的循环体内交换函数进行了两次,因为python的列表一直在被函数递归的修改,第二次交换是为了确保执行下一个for循环的字符串列表还是原来那个,参考下图,就是让右边的(1)步骤和(6)(11)步骤都是从"abc"开始交换,(7)(9)步骤都是从“bac”开始交换
在这里插入图片描述
他的居然只有一次交换函数!
冷静下来分析,首先,python的列表是一直按原型被传递的,每个函数都在修改最初的那一个列表,每次修改后如果不被修改回来就会出问题,但是,以我学习C语言的可怜的一点理解,记得C中的数组是按地址传递的,也就是大家都在修改同一个数组?(错误)

通过在程序中输出C++中输出字符串的地址,我发现,每个递归函数dfs(str, 0),dfs(str, 1),dfs(str, 2),中str的地址是不同的,也就是说每次递归都创造了新的空间!dfs(str, 2)中修改str是不会影响dfs(str, 1)中的str的值的,如果按照被修改的str是来自哪个内存地址的想法画出图:

在这里插入图片描述
红字表示这一步要交换的两个位置,方框框起来的部分是在同一个内存地址发生的, 编号是执行顺序,这次的输出就正确了:
样例输出:abc,acb,bac,bca,cab,cba
我的输出:abc,acb,bac,bca,cab,cba

3.关于重复字符判定

题目中说了可能有重复字符,这里以"acc"举例,我的判定函数:

if thesame(ss,cur,i):
                        print("跳过")
                        continue


def thesame(ss,cur,i):
            for k in range(cur, i):
                if ss[k] == ss[i]:
                    return True
            return False

他的判定函数:

if(i!=s && str[s]==str[i]) continue;

我判断了从位置cur到位置i之间有没有和str[i]相同的元素,若有则跳过;他则仅仅判断当前位置cur和i上的元素是否相同,若相同则跳过;
在python的代码执行中判断条件带来的变化如下图:
在这里插入图片描述红字表示这一步要交换的两个位置,靠左的位置是cur,靠右的是i
正确的C++代码的执行流程为:
在这里插入图片描述红字表示这一步要交换的两个位置,靠左的位置是cur,靠右的是i

暂时就想到这么多,如有问题欢迎交流指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值