面试题 28(*),字符串的排列(排列问题的典型解法:采用递归,每次交换首元素和剩下元素中某一个的位置)

想了10多分钟才有一个思路,整体上和答案一致,也是递归,子问题为提出首字符后,子字符串的排列。但是繁琐很多。

不过我比答案考虑得周全了些,拿到这道题的第一反应,是询问字符串中如果有重复的字符,在输出排列时 这两个字符交换是否属于一种情况。书中没有考虑到这种情况。

我的思路:顺序遍历整个字符串中的每一个字符,遍历过程中,如果当前字符和之前遍历过任何一个字符都不同,就将它提出,放到前面,提出此字符后剩下的字符串变成一个新的字符串。继续调用原函数处理这个新的字符串,直到新的字符串长度为0,就将之前提出的字符组成的字符串打印出来。

定义函数rePermutate(char *preSub, char *endSub)。preSub字符串用来存放每次被提到前面的字符,endSub存放提取后剩下的字符串。

考虑到字符串是以char *的方式存储,也就是说,是存在一段内存上,每次递归都是访问同一个字符串,为了防止递归将原字串打乱,我让每次递归都新开了空间将新的字符串复制出来。又因为,遍历字符串的每个字符的过程中,需要判断这个字符是否和已经被处理过的字符相同,我由定义了map来存放处理过的字符。

这样做的坏处就是新开了太多空间来存储字符。

伪代码如下:

/**
仅仅是伪代码 
*/
void rePermutate(char *preSub, char *endSub){
    if(str(endSub)) == 0{
        print preSub;
        return;
    }
    char character = NULL;
    for(i = 0; i< strlen(endSub); i++){
        character = endSub[i];
        map<char, int> mp;
        if(!alReadyDealt(character, mp)){
            //把字符并到preSub后面 
            char *newPreSub = new char[strlen(preSub) + 1];
            strcpy(newPreSub, preSub);
            strcat(newPreSub, character);
            
            //处理提取字符后剩下的字符串 
            char *newEndSub = new char[strlen(endSub) - 1];
            for(j = 0;j < i;j++)
                newEndSub[j] = endSub[i];
            for(j = i+1;j < strlen(endSub);j++)
                newEndSub[j] = endSub[j];
            rePermutate(newPreSub, newEndSub);
        }
        mp.clear();
        delete mp;
    }
}

bool alReadyDealt(char *character, HashMap &mp){
    if(mp[character] == NULL){
        mp[character] = 1;
        return true;
    }else{
        return false;
    }
}

书中的方法更加节省空间和时间,它采用的方法是:

遍历字符串,然后将当前遍历的字符和首字符交换位置,调用本函数处理首字符之后的子字符串,函数执行完后,再调换回来

在不需要复制字符串的情况下,避免了每次递归函数相互之间的影响。

 

将书中的代码,再加上我用map存储已处理过字符的思路,综合起来如下:

// StringPermutation.cpp : Defines the entry point for the console application.
//

// 《剑指Offer——名企面试官精讲典型编程题》代码
// 著作权所有者:何海涛

#include "stdafx.h"
#include <map>

void Permutation(char* pStr, char* pBegin);

void Permutation(char* pStr)
{
    if(pStr == NULL)
        return;

    Permutation(pStr, pStr);
}

void Permutation(char* pStr, char* pBegin)
{
    if(*pBegin == '\0')
    {
        printf("%s\n", pStr);
    }
    else
    {
        std::map<char, int> mp;
        for(char* pCh = pBegin; *pCh != '\0'; ++ pCh)
        {
            if(mp[*pCh] == NULL){
                mp[*pCh] = 1;
                 
                char temp = *pCh;
                *pCh = *pBegin;
                *pBegin = temp;
                
                Permutation(pStr, pBegin + 1);
                
                temp = *pCh;
                *pCh = *pBegin;
                   *pBegin = temp;
               }
        }
        mp.clear();
    }
}

// ====================测试代码====================
void Test(char* pStr)
{
    if(pStr == NULL)
        printf("Test for NULL begins:\n");
    else
        printf("Test for %s begins:\n", pStr);

    Permutation(pStr);

    printf("\n");
}

int main()
{
    Test(NULL);

    char string1[] = "";
    Test(string1);

    char string2[] = "a";
    Test(string2);

    char string3[] = "aba";
    Test(string3);

    char string4[] = "abc";
    Test(string4);

    return 0;
}

 

推广:

这种类型的题目推广开来就是:求一个元素组中,中所有满足一定条件的排列。

对于这种问题,在没有很好的剪枝的前提下,往往采用先求出所有排列,然后挨个试验是否满足条件。

求所有排列的方法,就是上面所使用的 交换元素->递归->换回来 的思路。

类似的题目还有八皇后问题,求所有八皇后的排放方式。

思路:皇后必然是每列每行一个,因此我们用一个数组int column[8],里面存放0-7这8个整数。column[3] = 7代表第4列皇后放在第8行。然后求这个column数组的全排列,每个排列,验证是否有皇后在斜线上,即 column[i] - column[j] == i - j

全排列的求解方式,就是从第一个元素开始,交换元素->递归->换回来。

 

附:

Permutation是"排序"的意思,计算机高频词。

The permutation is even if the number of inversions it contains is even.

如果一个排列中含有的反序的个数是偶数,则称它为偶排列。

posted on 2014-01-26 05:24  Felix Fang  阅读(1479)  评论(0编辑  收藏  举报

导航