题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串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
暂时就想到这么多,如有问题欢迎交流指正!