剑指offer-面试题28:字符串的排列

题目:输入一个字符串,打印出该字符串中字符放入所有排列。例如输入字符串abc,则打印出字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。

思路:全排列的问题实际就是交换问题。把问题划分成两个部分,第一个字符和后面的部分,所以这个问题分为两个步骤:(1)考虑第一个字符的所有可能情况,将第一个字符和后面的所有字符交换;(2)考虑第一个字符后面的整体部分,那么这部分又可以分成两个部分,递归的调用前面的代码。

非递归算法见:http://blog.csdn.net/moses1213/article/details/51058053

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

Permutation(char* pStr, char* pBegin)
{
    if(*pBegin == '\0')
        cout << pStr << endl;
    else
    {   
        for(char* pCh = pBegin; *pCh != '\0'; ++pCh)
        {
            char temp = *pCh;
            *pCh = *pBegin;
            *pBegin = temp;
            
            Permutation(pStr, pBegin + 1);
            
            temp = *pCh;
            *pCh = *pBegin;
            *pBegin = temp;
        }
    }
}

上面的算法只适用于没有重复输入的情况,对于存在重复的字符例如“abb"会输出六种结果,其中有重复的,正确的结果应该有(3!)/(2!) = 3种,3!表示3的阶乘。下面采用一种新的算法:为了避免重复交换,每次检查与首字符交换的的字符是否已经交换过,判断的过程是从头指针一直遍历到当前交换字符的指针,如果指向的值相等,代表当前字符已经与首字符交换过,直接跳过当前字符。例如"abb",a与第二个b交换后为"bab",a与第三个b交换的时候因为b之前已经有一个b,这种情况直接跳到下一步骤。

//不重复版本
bool HasBeSwaped(char* pBegin, char* pCurrent)
{
    for(char* pCh = pBegin; pCh != pCurrent; ++pCh)
    {
        if(*pCh == *pCurrent)
            return true;
    }
    
    return false;
}

void Permutation(char* pStr, char* pBegin)
{
    if(*pBegin == '\0')
        cout << pStr << endl;
    else
    {   
        for(char* pCh = pBegin; *pCh != '\0'; ++pCh)
        {
            if(HasBeSwaped(pBegin, pCh)
                continue;
                
            char temp = *pCh;
            *pCh = *pBegin;
            *pBegin = temp;
            
            Permutation(pStr, pBegin + 1);
            
            temp = *pCh;
            *pCh = *pBegin;
            *pBegin = temp;
        }
    }
}

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

问题扩展:字符的组合问题

输入三个字符a、b、c,则他们的组合有a、b、c、ab、ac、bc、abc。如果输入n个字符,则这n个字符能构成长度为1的组合、长度为2的组合、……、长度为n的组合。在求n个字符长度为m(1<=m<=n)的组合的时候,我们把这n个字符划成两部分:第一个字符和其余所有字符。如果组合里包含第一个字符,则下一步在剩余的字符里选取m-1个字符;如果组合里不包含第一个字符,则下一步在剩余的n-1个字符里选取m个字符。也就是说,我们可以把求n个字符组成长度为m的组合的问题分解成两个子问题,分别求n-1个字符串中长度为m-1的组合,以及求n-1个字符的长度为m的组合。这两个子问题都可以用递归的方式解决。

代码实现:

void Combination(char* pStr, size_t size, vector<char> result)
{
	if(strlen(pStr) < size || size < 0)
		return;
	//已经达到要求长度,输出
    if(size == 0)
    {
        for(auto it = result.cbegin(); it != result.cend(); ++it)
            cout << *it;
		cout << endl;
		return;
    }
	//组合里包含第一个字符
    result.push_back(*pStr);
	Combination(pStr + 1, size - 1, result);

	//组合里不包含第一个字符
    result.pop_back();
    Combination(pStr + 1, size, result);
}

void Combination(char* pStr)
{
    if(pStr == NULL)
        return;
    for(size_t size = 1; size <= strlen(pStr); ++size)
	{
		vector<char> result;
        Combination(pStr, size, result);
	}
}
 
八皇后问题: 

8皇后问题也可以用全排列的方式解决。问题是这样的:在8*8的国际象棋上摆放8个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角线上。求一共有多少种符合条件的八法。

由于8个皇后任意两个不能处在同一行,那么肯定每个皇后占据一行。于是我们可以定义一个数组ColumnIndex[8],数组中第i个数字表示位于第i行皇后的列号。先把数组ColumnIndex的8个数字分别用0~7初始化,接下来就是对数组ColumnIndex做全排列。因为我们是用不同的数字初始化数组,所以任意连个皇后肯定不同列。只需要判断每一个排列对应的8个皇后是不是在同一对角线上,也就是对于数组的两个下标i和j,是不是i-j==ColumnIndex[i] - ColumnIndex[j]或者j-i=ColumnIndex[i] - ColumnIndex[j]。

#include <iostream>
using namespace std;

bool IsRightArrange(int* ColumnIndex, int n)
{
	for(int i = 0; i < n; ++i)
	{
		for(int j = i+1; j < n; ++j)
		{
			if(i - j == ColumnIndex[i] - ColumnIndex[j] || j - i == ColumnIndex[i] - ColumnIndex[j])
				return false;
		}
	}

	return true;
}

void Permutation(int* ColumnIndex, int* pBegin, int& count)
{
	if(pBegin - ColumnIndex == 8)
	{
		if(IsRightArrange(ColumnIndex, 8))
			++count;
	}
	else
	{
		for(int* pSwap = pBegin; pSwap != ColumnIndex + 8; ++pSwap)
		{
			int temp = *pBegin;
			*pBegin = *pSwap;
			*pSwap = temp;

			Permutation(ColumnIndex, pBegin + 1, count);

			temp = *pBegin;
			*pBegin = *pSwap;
			*pSwap = temp;
		}
	}
}

int Queen8()
{
	int ColumnIndex[8];
	for(int i = 0; i < 8; ++i)
		ColumnIndex[i] = i;
	
	int count = 0;
	Permutation(ColumnIndex, ColumnIndex, count);

	return count;
}

int main()
{
	cout << Queen8() << endl;
	getchar();

	return 0;
}



 






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值