Permutation 排列組合,主要是字串的排列offer上的題目,還有leetcode的組合
*一個簡潔版的結果過程說明,固定一個位,變換其他位
a b c d
a b d c
a c b d
a c d b
a d c b
a d b c
void perm(char* list, int i, int n) { int j; if( i == n) { for(j=0; j <= n; ++ j) cout<<list[j]<<" "; cout<<endl; }else { for( j = i; j <= n; ++ j) { cout<<"i:j "<<i<<":"<<j<<list[j]<<endl; swap(list[i], list[j]); perm(list, i+1, n); swap(list[i],list[j]); } } }
1.basic permutation,遞迴實現,固定一個字母,從後往前依次交換字母,交換後恢復,遞歸回溯時,回到前一個字母,再重複以上操作,
*pB = d
a b c d ->a b c d (*pB = c)
a b d c -> a b c d (*pB = b)
a c b d -> a c d b , a c b d , a b c d(*pB = b)
b a c d -> b c a d ,....... (*pB = a)
// EightQueen.cpp : 定義控制檯應用程式的入口點。 //StringPermutation #include "stdafx.h" void Permutation(char* pstr,char* pBegin); void Permutation(char* str) { if(str == NULL) return; else Permutation(str,str); } void Permutation(char* pstr,char* pBegin) { if(*pBegin == '\0') printf("%s\n",pstr); 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; } } } void Test(char* str) { if(str == NULL) printf("NULL\n"); else printf("Test on string %s begins\n",str); Permutation(str); printf("\n"); } int _tmain(int argc, _TCHAR* argv[]) { Test(NULL); char string1[] = ""; Test(string1); char string2[] = "a"; Test(string2); Test("a");//編譯通過,但是不能正確輸出,why?? /* char* p是一個指標,根本沒分配記憶體,他指向的"abc123ABC" 是隻讀的,不能改變,你改變他的值肯定是錯的 而char p[]是一個數組,已經分配記憶體,是將"abc123ABC" 複製到該記憶體裡面,這個記憶體是可讀寫的 */ char string3[] = "abc"; Test(string3); return 0; }
如果是java編寫可以考慮,把陣列傳入,int 下標,length長度
考慮如果有重複的字元怎麼處理:擴充套件1:存在相同字元的情況怎麼求出全排列,在進行交換的那步新增一個判斷如果兩個字元相等不用進行交換,這樣子輸出的還是重複的,因為,在遞迴的過程中,是交換,固定第一個字元,再接著對後面的字元進行交換,固定的遞迴,程式碼是copy的網上的:
#include "stdafx.h" #include<iostream> using namespace std; #include<assert.h> //在[nBegin,nEnd)區間中是否有字元與下標為pEnd的字元相等 bool IsSwap(char* pBegin , char* pEnd) { char *p; for(p = pBegin ; p < pEnd ; p++) { if(*p == *pEnd) return false; } return true; } void Permutation(char* pStr , char *pBegin) { assert(pStr); if(*pBegin == '\0') { static int num = 1; //區域性靜態變數,用來統計全排列的個數 printf("第%d個排列\t%s\n",num++,pStr); } else { for(char *pCh = pBegin; *pCh != '\0'; pCh++) //第pBegin個數分別與它後面的數字交換就能得到新的排列 { if(IsSwap(pBegin,pCh)) { swap(*pBegin , *pCh); Permutation(pStr , pBegin + 1); swap(*pBegin , *pCh); } } } } int main(void) { char str[] = "baa"; Permutation(str , str); return 0; }
擴充套件1:正方體八個頂點問題
全排列問題的STL用法(next_permutation類)點選開啟連結
// EightQueen.cpp : 定義控制檯應用程式的入口點。
//StringPermutation
#include "stdafx.h"
#include <algorithm>
using namespace std;
/*
總共只有8個點,因此直接給每個頂點編號, 然後求一個全排列,再判斷是否存在合法解就好了。
另外也可以稍微優化一下,固定 一個點,求其他7個點的全排列, 複雜度為 O(7!) = 5040
*/
bool Can(int* Node)// 輸入8個頂點的值
{
#define GetSum(x1,x2,x3,x4)(Node[Pos[x1]]+Node[Pos[x2]]+Node[Pos[x3]]+Node[Pos[x4]])
int sum = 0;
for(int i = 0; i < 8; ++ i)
sum += Node[i];
if(sum % 2 !=0) // 和為奇數?
return false;
int Pos[8] = {0,1,2,3,4,5,6,7};
do
{// 只需要檢查3個面, 當一個面的頂點和 = Sum / 2 時,對面的頂點和必然也 = Sum / 2
if(GetSum(0,1,2,3) == sum / 2 && GetSum(0,2,4,6) == sum / 2 && GetSum(0,1,4,5))
//得到一組合法解,此時若需要可以輸出
return true;
}while(next_permutation(Pos + 1, Pos + 7)); // 用STL求其餘7個頂點的全排列
return false;
}
void Test(int *num)
{
if(num == NULL)
printf("NULL\n");
else
printf("Test on int[] begins\n");
char* s = Can(num) ? "yes":"no";
printf("It pass %s\n", s);
}
int _tmain(int argc, _TCHAR* argv[])
{
int num[8] = {1,2,3,4,5,6,7,8};
Test(num);
return 0;
}
擴充套件2:八皇后問題,全排列解法
2.組合問題
題目:輸入一個字串,輸出該字串中字元的所有組合。舉個例子,如果輸入abc,它的組合有a、b、c、ab、ac、bc、abc。
假設我們想在長度為n的字串中求m個字元的組合。我們先從頭掃描字串的第一個字元。針對第一個字元,我們有兩種選擇:一是把這個字元放到組合中去,接下來我們需要在剩下的n-1個字元中選取m-1個字元;而是不把這個字元放到組合中去,接下來我們需要在剩下的n-1個字元中選擇m個字元。這兩種選擇都很容易用遞迴實現。下面是這種思路的參考程式碼:
// EightQueen.cpp : 定義控制檯應用程式的入口點。
//StringCombination
#include "stdafx.h"
#include <string.h>
#include <vector>
using namespace std;
//number是還需要新增的字元個數,vector是儲存已有字元組合的容器
void Combination(char* str, int number, vector<char>& result);
void Combination(char* str)
{
if(str == NULL)
return;
vector<char> result;
int length = strlen(str);
//相當於N個字元取M個,length=M
for(int i = 1; i <= length; ++ i)
Combination(str, i, result);
}
//vector是取引用,&引用時別名,不用拷貝,值傳遞是需要拷貝的,有時間空間浪費
void Combination(char* str, int number, vector<char>& result)
{
//當組合數滿足number的要求,輸出
if(number == 0)
{
//對vector不熟悉,字元輸出用%c,iter是指標所以是*iter
vector<char>::iterator iter = result.begin();
for(; iter < result.end(); ++ iter)
printf("%c",*iter);
printf("\n");
return;
}
//當字元搜尋到'\0'結束,&&&&&&&&&&指標忘了寫*str取值符
if(*str == '\0')
return;
//在result中新增字元直至滿足number的要求
result.push_back(*str);
Combination(str + 1, number - 1, result);//深度呼叫,每一次呼叫返回都會在自己所在的層次中*刪除一個字元,後在呼叫**
//*
result.pop_back();
//**
Combination(str + 1, number, result);
}
void Test(char* str)
{
if(str == NULL)
printf("NULL\n");
else
printf("Test on string %s begins\n",str);
Combination(str);
printf("\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
Test(NULL);
char string2[] = "a";
Test(string2);
char string3[] = "abc";
Test(string3);
return 0;
}
(2) 01轉換法
本程式的思路是開一個數組,其下標表示1到n個數,陣列元素的值為1表示其代表的數被選中,為0則沒選中。
首先初始化,將陣列前n個元素置1,表示第一個組合為前n個數。
然後從左到右掃描陣列元素值的“10”組合,找到第一個“10”組合後將其變為“01”組合,同時將其左邊的所有“1”全部移動到陣列的最左端。
當第一個“1”移動到陣列的n-m的位置,即n個“1”全部移動到最右端時,就得到了最後一個組合。
排列的特殊方法:設有n個字元,模擬2進位制加法器,某一個為1,則取對應的字元,若為0則不取,就能夠實現字元組合。
int num 從 1 自增到 2^n -1, 將num右移i位,跟1做按位&操作,即可判斷第i個字元取還是不取。int main()
{
string str= "abc";
int N = str.size();
int num = pow(2.,N) ;
for(int i=1;i<num;i++)//因為除了一個也不取,一共有7個組合,所以是1~num
{
for(int j=0;j<N;j++)
{
if((i>>j)&1)
cout<<str[j];
}
cout<<endl;
}
return 0;
}
如果排列中有重複的字元,就先將字串掃描一遍,記錄下字元出現次數>1的字元,再去除字串中重複字元,進行組合。
Subset
class Solution {
public:
vector<vector<int> > subsets(vector<int> &S)
{
vector<vector<int> > result;
if(S.size() == 0)
return result;
sort(S.begin(), S.end());
int n = S.size();
vector<int> each;
result.push_back(each);
for(int i = 1; i < pow(2,n); ++ i)
{
each.clear();
for(int j = 0; j < n ; ++ j )
{
if( (i >> j) & 1 == 1 )
each.push_back(S[j]);
}
result.push_back(each);
}
return result;
}
};
SubsetII
class Solution {
public:
vector<vector<int> > subsetsWithDup(vector<int> &S) {
vector<vector<int> > result;
if(S.size() == 0)
return result;
sort(S.begin(), S.end());
int n = S.size();
vector<int> each;
result.push_back(each);
for(int i = 1; i < pow(2,n); ++ i)
{
each.clear();
for(int j = 0; j < n ; ++ j )
{
if( (i >> j) & 1 == 1 )
each.push_back(S[j]);
}
vector<vector<int> >::iterator itr = find(result.begin(), result.end(),each);
if(itr == result.end())
result.push_back(each);
}
return result;
}
};
1,2,3,4依次輸出3個數的組合,2個數的組合,和一個數的組合
#include "stdafx.h"
#include <vector>
#include <iostream>
using namespace std;
vector<vector<char> > result;
void Combination2(char* str, int i, vector<char> &perstr)
{
if(i == 0)
{
result.push_back(perstr);
return;
}
if(*str == '\0')
return;
perstr.push_back(*str);
Combination2(str+1, i-1, perstr );
perstr.pop_back();
Combination2(str+1, i, perstr );
}
void Combination(char* str)
{
if(str == NULL )
return;
vector<char> perstr;
int length = strlen(str);
// cout<<length<<endl;
for(int i = length-1; i >= 1; -- i)
Combination2(str,i,perstr);
}
int _tmain(int argc, _TCHAR* argv[])
{
char b[] = "1234";
// cout<<b[1]<<endl;
Combination(b);
for(size_t i = 0; i < result.size(); ++ i)
{
for(size_t j = 0; j < result[i].size(); ++ j)
cout<<result[i][j];
cout<<endl;
}
return 0;
}