剑指Offer-38:字符串的排列&字符串组合

题目:

输入一个字符串,按字典序打印出该字符串中字符的所有排列。

例子

输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

链接:

剑指Offer(第2版):P197

思路标签:

  • 算法:递归
  • 大问题的分解

解答:

c++

  • 大事化小,小事化了的思想;
  • 将字符串操作分为三步进行:
    • 将字符串分为两部分,第一部分是第一个字符;另外一部分是后面的字符串;每次循环将第一个字符与后面的每个字符进行交换;
    • 对后一部分字符串,视为当前字符串,进行前面同样的操作;
    • 子字符串操作完成后,需要恢复第一个位置的原本字符。
  • 例子:如“abcd”,a和b交换后,成为“bacd”;当后面的3个字符的子字符串操作完成后,需要对‘a’和‘c’进行交换,但是当前是“bacd”;所以需要再交换回来成为“abcd”,再进行交换及后面子字符串的操作。
#include 

class Solution {
public:
    vector<string> Permutation(string str) {
        std::vector<string> ret;
        if(str.size() == 0)
            return ret;
        Permutation(ret, str, 0);
        sort(ret.begin(), ret.end());  //这里加sort是为了通过线上的测试
        return ret;
    }

    // 注意递归函数中,需要传入的是数组的地址引用
    void Permutation(vector<string> &array, string str, int begin){
        if(begin == str.size()-1)
            array.push_back(str);

        for(int i=begin; i//将第一个字符和后面的交换
            swap(str[i], str[begin]);
            //处理后面的字符串
            Permutation(array, str, begin+1);
            //在处理完毕后面的字符串后,需要继续执行当前第一个字符串的交换,
            //所以为了确保程序的正确执行,需要将交换的字符串还原,然后再执行后序的循环交换操作
            swap(str[i], str[begin]);
        }
    }
};

数组的全排列(含重复元素)

  • 同上面字符串的情况类似,但是该问题考虑重复元素的情况。
  • 利用 isswap函数来判断后面是否存在与当前元素相同的元素,如果有,则不进行交换,相当于我们只对所有重复元素中的最后一个元素进行相关操作。
#include 
#include 
using namespace std;

int sum = 0;

void print(vector<int> &array) {
    cout << '{';
    for (int i = 0; i < array.size(); ++i)
        cout << array[i] << ' ';
    cout << '}' << endl;
}

void swap(vector<int> &arr, int i, int j) {
    if (i == j)
        return;
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

bool isswap(vector<int> &arr, int index) {
    for (int i = index + 1; i < arr.size(); ++i) {
        if (arr[i] == arr[index])
            return false;
    }
    return true;
}

void permutaion(vector<int> &array, int begin) {
    if (begin == array.size() - 1){
        ++sum;
        print(array);
    }

    for (int i = begin; i < array.size(); ++i) {
        if (isswap(array, i)) {

            swap(array, i, begin);
            permutaion(array, begin + 1);
            swap(array, i, begin);
        }
    }
}

int main() {
    vector<int> array{1, 2, 3, 3};
    permutaion(array, 0);
    cout << "sum = " << sum << endl;
    return 0;
}

其他相关问题

题目1: 求字符的所有组合(不含重复字符)

例子:

如,输入三个字符a、b、c,则它们的组合有a、b、c、ab、ac、bc、abc.
当交换字符串中的两个字符时,虽然能得到两个不同的排列,但却是同一个组合,比如ab和ba是不同的排列,但只算一个组合。

分析:

  • 输入长度为n的字符串,则n个字符串能构成长度为1,2,3,…,n的组合;
  • 在求长度为m的组合时,将n个字符串分成两部分:第一个字符和其余的所有字符;
  • 有两种情况:包含第一个字符,剩下字符选m-1个;不包含第一个字符,剩下字符选m个。
  • 将大问题分解为单个元素和其子问题,利用递归的思想逐步对子问题进行求解。

代码:

#include   
#include   
#include   

using namespace std;

static int num = 1;

void Combination(char *str, int number, vector<char> &result, vector<string> &results);

vector<string> Combination(char *str)
{
    std::vector<string> results;
    if (str == nullptr)
        return results;
    int i, length = strlen(str);
    for (i = 1; i <= length; ++i) {
        std::vector<char> result;
        Combination(str, i, result, results);//在长度为string字符串中取出i个组合数  
    }

    return results;
}

void Combination(char *str, int number, vector<char> &result, vector<string> &results)
{
    if (str == nullptr)
        return;
    if (number == 0)
    {
        string resultStr = "";
        //遍历vector
        for (vector<char>::iterator iter = result.begin(); iter != result.end(); ++iter)
        {
            resultStr += *iter;
        }
        results.push_back(resultStr);
        return;
    }

    if (*str == '\0')
        return;

    result.push_back(*str);
    Combination(str + 1, number - 1, result, results);
    result.pop_back();
    Combination(str + 1, number, result, results);
}

题目1-1: 字符串的全组合(包含重复字符)

  • 先去重,然后在同一层中增加b的数量
#pragma warning(disable:4996)  
#include    
#include    
#include    
#include  
using namespace std;
void Combination(char* begin, char* _string, int number, vector<char>& result, unordered_map<char, int>& map)
{
    if (number == 0)
    {
        vector<char>::iterator iter = result.begin();
        for (; iter < result.end(); ++iter)
            cout << (*iter);
        cout << endl;
        return;
    }

    if (*_string == '\0')
        return;

    int count1 = 0;
    int len = map[*_string];
    for (; count1 < len; ++count1)
    {
        result.push_back(*_string);
        Combination(begin, _string + 1, number - 1 - count1, result, map);
    }
    for (int i = 0; i < count1; ++i)
        result.pop_back();
    Combination(begin, _string + 1, number, result, map);
}


void Combination(char* _string)
{
    unordered_map<char, int>map;
    if (_string == NULL)
        return;
    int len = strlen(_string);
    for (int i = 0; i < len; ++i)
    {
        ++map[_string[i]];
    }
    int index = 0;
    for (int i = 0; i < 26; ++i)
    {
        if (map['a' + i])
        {
            _string[index++] = 'a' + i;
        }
    }
    _string[index++] = '\0';
    vector<char> result;
    for (int i = 1; i <= len; i++)
    {
        Combination(_string, _string, i, result, map);
    }
}
int main()
{
    char s[] = "abcb";
    Combination(s);
    system("pause");
    return 0;
}

题目2: 正方体8顶点和

输入一个含有8个数字的数组,判断有没有可能把这8个数字分别放到正方体的8个顶点上,使得正方体上三组相对的面上的4个顶点的和相等。

分析:

这相当于先得到a1,a2,a3,a4,a5,a6,a7,a8这8个数字所有的排列,然后判断有没有某一个排列符合题目中给定的条件。

条件为:a1+a2+a3+a4 == a5+a6+a7+a8,a1+a3+a4+a7 == a2+a4+a6+a8,并且 a1+a2+a5+a6 == a3+a4+a7+a8.

题目3: 国际象棋摆皇后

在8*8的国际象棋棋盘上摆放8个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一条对角线上。请问有多少种摆法?

分析:

  • 首先每个皇后占据一行;
  • 定义一个数组ColumnIndex[8],数组中第i个数字表示第i行的皇后的列号,将数组用0~7初始化,然后对数组ColumnIndex进行全排列。这里因为用不同的数字初始化,所以任意两个皇后也不同列。
  • 最后只需判断每个排列对应的8个数字是否在同一条对角线上:对数组的两个下标i和j,是否有i-j==ColumnIndex[i]-ColumnIndex[j]或者j-i==ColumnIndex[i]-ColumnIndex[j]

你可能感兴趣的:(剑指Offer,剑指Offer,笔试面试,编程题)