首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

【100题】第六十六~第七十题(颠倒栈、扑克顺子和掷骰子概率、数字数组排成最小数、求旋转数组中最小值、全排列)

2012-09-08 
【100题】第六十六~第七十题(颠倒栈、扑克牌顺子和掷骰子概率、数字数组排成最小数、求旋转数组中最小值、全排列

【100题】第六十六~第七十题(颠倒栈、扑克牌顺子和掷骰子概率、数字数组排成最小数、求旋转数组中最小值、全排列)

一,颠倒栈。

1)题目:用递归颠倒一个栈。例如输入栈{1, 2, 3, 4, 5},1在栈顶。颠倒之后的栈为{5, 4, 3, 2, 1},5处在栈顶。

2)分析:乍一看到这道题目,第一反应是把栈里的所有元素逐一pop出来,放到一个数组里,然后在数组里颠倒所有元素,最后把数组中的所有元素逐一push进入栈。这时栈也就颠倒过来了。颠倒一个数组是一件很容易的事情。不过这种思路需要显示分配一个长度为O(n)的数组,而且也没有充分利用递归的特性。

我们再来考虑怎么递归。我们把栈{1, 2, 3, 4, 5}看成由两部分组成:栈顶元素1和剩下的部分{2, 3, 4, 5}。如果我们能把{2, 3, 4, 5}颠倒过来,变成{5, 4, 3, 2},然后在把原来的栈顶元素1放到底部,那么就整个栈就颠倒过来了,变成{5, 4, 3, 2, 1}。

接下来我们需要考虑两件事情:一是如何把{2, 3, 4, 5}颠倒过来变成{5, 4, 3, 2}。我们只要把{2, 3, 4, 5}看成由两部分组成:栈顶元素2和剩下的部分{3, 4, 5}。我们只要把{3, 4, 5}先颠倒过来变成{5, 4, 3},然后再把之前的栈顶元素2放到最底部,也就变成了{5, 4, 3, 2}。

至于怎么把{3, 4, 5}颠倒过来……很多读者可能都想到这就是递归。也就是每一次试图颠倒一个栈的时候,现在栈顶元素pop出来,再颠倒剩下的元素组成的栈,最后把之前的栈顶元素放到剩下元素组成的栈的底部。递归结束的条件是剩下的栈已经空了。这种思路的代码如下:

template<typenameT>voidReverseStack(std::stack<T>& stack)

{

if(!stack.empty())

{

T top = stack.top();

stack.pop();

ReverseStack(stack);

AddToStackBottom(stack, top);

}

}

我们需要考虑的另外一件事情是如何把一个元素e放到一个栈的底部,也就是如何实现AddToStackBottom。这件事情不难,只需要把栈里原有的元素逐一pop出来。当栈为空的时候,push元素e进栈,此时它就位于栈的底部了。然后再把栈里原有的元素按照pop相反的顺序逐一push进栈。

注意到我们在push元素e之前,我们已经把栈里原有的所有元素都pop出来了,我们需要把它们保存起来,以便之后能把他们再push回去。我们当然可以开辟一个数组来做,但这没有必要。由于我们可以用递归来做这件事情,而递归本身就是一个栈结构。我们可以用递归的栈来保存这些元素。

基于如上分析,我们可以写出AddToStackBottom的代码:

// Add an element to the bottom of a stack:

template<typenameT>voidAddToStackBottom(std::stack<T>& stack, T t)

{

if(stack.empty())

{

stack.push(t);

}

else

{

T top = stack.top();

stack.pop();

AddToStackBottom(stack, t);

stack.push(top);

}

}

先弹出,处理,再恢复,化作子问题的一个重要方式

3)源码:

#include <iostream>#include <stack> using namespace std;template<typename T>void ReverseStack(stack<T>& stack){    if(!stack.empty())    {        T top = stack.top();//Pop the top element        stack.pop();        ReverseStack(stack); // Reverse the remaining stack        AddToStackBottom(stack, top); //Add the top element to the bottom of the remaining stack    }}template<typename T> void AddToStackBottom(stack<T>& stack, T t){    if(stack.empty())    {        stack.push(t);    }    else    {        T top = stack.top();        stack.pop();        AddToStackBottom(stack, t);        stack.push(top);    }}void print(stack<int> mystack) {while(mystack.size()){cout<<mystack.top()<<" "; mystack.pop(); } }int main(){stack<int> mystack;mystack.push(1); mystack.push(2);mystack.push(3); mystack.push(4); mystack.push(5);print(mystack); printf("\n"); ReverseStack(mystack);printf("\n");   print(mystack); } 


二,扑克牌和掷骰子

1)题目:扑克牌的顺子

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2-10 为数字本身,A 为1,J为11,Q 为12,K 为13,而大小王可以看成任意数字。

2)分析:

思路一:排序法,其中大小王看成0。然后,统计0个数,和数字间隔数,如果有重复(对子)则返回失败。间隔数小于等于2则成功,否则失败。

思路二:Hash法,类似排序。

3)源码:

注意:sort(int array[],array+nLength,comp); //注意添加 <algorithm> using namespace std;

qsort(int array[],nLength,sizeof(array[0]),compare); //自带的

#include <iostream>#include <algorithm>using namespace std;//函数功能 : 从扑克牌中随机抽5张牌,判断是不是一个顺子//函数参数 : pCards为牌,nLen为牌的张数//返回值 :   是否顺子int comb(const int *x,const int *y){return x<y;  //}int cmp(const void *x,const void *y){return *(int *)x - *(int *)y;  //}bool IsContinuous(int *pCards, int nLen){if(pCards == NULL || nLen <= 0)return false;//sort(pCards, pCards + nLen,comb); //调用标准库的排序算法    qsort(pCards,nLen,sizeof(pCards[0]),cmp);         //for(int i=0;i<nLen;++i)    //   cout<<pCards[i]<<" ";    int i;int zeroCount = 0;   //大小王用0表示int capCount = 0;    //间隔数//统计0的个数for(i = 0; i < nLen; i++){if(pCards[i] == 0)zeroCount++;elsebreak;}//统计间隔数int preCard = pCards[i];    for(i = i + 1; i < nLen; i++){int curCard = pCards[i];if(preCard == curCard)  //与前一张牌比较return false;elsecapCount += curCard - preCard - 1; //累加间隔数preCard = curCard;}return (zeroCount >= capCount)? true: false; //只要王的个数大于间隔数}int main(){int pCards[]={1,2,3,4,5};int pCards2[]={1,2,8,4,12};int i=IsContinuous(pCards,5);bool j=IsContinuous(pCards2,5);cout<<i<<endl;cout<<j<<endl;}


1)题目:n 个骰子的点数。把n 个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,

2)分析:

PN,S来表示N个骰子,顶面数字之和为S出现的概率,则:

PN,S=aN,S/6^N

其中aN,SN个骰子顶面数之和为S的状态的数量,6^N为总的状态数量,因为每个骰子有6种状态,N个骰子组成的状态数就是6^N

下面给出求ai,j的递推公式,即求i(1=<i<=N)个骰子顶面数之和为j(i=<j<=6*i)时的状态数量:

ai,j= ai-1,j-1 //第i个骰子顶面为1时,其他i-1个骰子顶面数之和为j-1时的状态数量

+ ai-1,j-2 //第i个骰子顶面为2时,其他i-1个骰子顶面数之和为j-2时的状态数量

+ ai-1,j-3 //第i个骰子顶面为3时,其他i-1个骰子顶面数之和为j-3时的状态数量

+ ai-1,j-4 //第i个骰子顶面为4时,其他i-1个骰子顶面数之和为j-4时的状态数量

+ ai-1,j-5 //第i个骰子顶面为5时,其他i-1个骰子顶面数之和为j-5时的状态数量

+ ai-1,j-6 //第i个骰子顶面为6时,其他i-1个骰子顶面数之和为j-6时的状态数量

其中递推公式中i>1

对于任意的1=<i<=Nj<=0j<ij>6*iai,j=0

3)源码:

非递归求解:

#include <iostream>#include <math.h>using namespace std;void PrintSumProbabilityOfDices(unsigned int n)  {      const unsigned int MAX=12;  //max number of dices       if(n>MAX)      {          printf("Overflow!\n");          return;      };        unsigned int a[MAX+1][6*MAX+1];      unsigned int i,j,k;      memset(a,0,sizeof(a));   /* a[i][j]  i代表骰子个数  j代表 i个骰子可能出现的骰子的数量  *  */     for(j=1;j<=6;j++)//只有一个骰子的时候,每一个数出现的次数都是 1          a[1][j]=1;              for(i=2;i<=n;i++) //第 i个 骰子出现         for(j=i;j<=6*i;j++)  //j为所有骰子可以出现的次数         {              a[i][j]=0; //初始化要求的 数             for(k=1;k<=6&&k<=j;k++)  //k代表 第 i个骰子出现的可能点数  k<=j 说明 不能超过当前所求所有骰子 总点数                 a[i][j]+=a[i-1][j-k];  //其他 i-1个骰子 出现j-k 点的次数         }        unsigned int nTotal=pow(6,n);      for(i=n;i<=6*n;i++)          printf("Sum=%d,Probability=%.15lf\n",i,a[n][i]*1.0/nTotal);  }  int main(){PrintSumProbabilityOfDices(2);}

三,把数组排成最小的数。【经典牛逼的题】

1)题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。

例如:输入数组{32, 321},则输出这两个能排成的最小数字32132。

请给出解决问题的算法,并证明该算法。

2)分析:这是09 年6 月份百度的一道面试题,

思路:根据“字典序”排序,先比较整数中第一个数字,然后再比较第二个,直到所有数都成字典序排列。

很遗憾】这个是不可行的,比如32的字典序比322小,但是32322比32232大

那该如何是好?

主要是根据题目要求,先排列好顺序,然后按照顺序输出每个数。根据“字典序”排序已经被排除了,那么如何选择排序规则呢?

【巧妙】所以在这里自定义一个比较大小的函数,比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。

如果用char *表示字符串,那就可以使用qsort函数进行排序了,我这里用的是string,所以自定义了一个最简单的冒泡排序。

3)源码:

注意,int 转换为 string

#include<iostream>#include<algorithm>#include<string>#include<sstream>using namespace std;int compare1(string str1,string str2)//user defined compare function{string tmp1 = str1.append(str2);string tmp2 = str2.append(str1);if(tmp1.compare(tmp2)>=0)//tmp1 > tmp2 return true;return false;}void sort(string str[],int len)//Bubble sort{for(int i=len;i>0;i--){int exchange=0; for(int j=0;j<i;j++){if(compare1(str[j],str[j+1])){string tmp = str[j];str[j] = str[j+1];str[j+1] = tmp;exchange=1; }if(exchange==0)return; }}}int main(){int num[] = {332,41,322,32,414,4};int len = sizeof(num)/sizeof(int);//string *word = new string[len];string word[len];stringstream sst;for(int i=0;i<len;i++)//convert int to string{sst<<num[i];sst>>word[i];sst.clear(); /*第二种方法 将int 变为 string         char *temp; sprintf(temp,"%d",num[i]); string str(temp); word[i]=str; */  /*第三种方法 将 int 变为 string         char *temp; itoa(num[i],temp,10); string str(temp); word[i]=str;  */ }sort(word,len);//Bubble sort array of string by the rule defined by compare1for(int i=0;i<len;i++){cout<<word[i];}getchar();return 0;}

四,旋转数组中的最小元素。

1)题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。

例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。

2)分析:这道题最直观的解法并不难。从头到尾遍历数组一次,就能找出最小的元素,时间复杂度显然是O(N)。

二分查找法,这道题类似于“在已排序数组,旋转后,再查找某个数”这道题。

中值 > 数组开始 则 在右侧查找 3, 4, 5, 1, 2

中值 < 数组开始 则 在左侧查找(含中值) 4, 5, 1, 2, 3

退出条件:仅仅剩余一个值或者为顺序数组则取最小值

3)源码:

#include <iostream>using namespace std; int helper(int a[], int s, int t) {if (s == t || a[s] < a[t])  return a[s];int m = s + (t-s)/2;if (a[s]>a[m]) //中值小  return helper(a, s, m);else return helper(a, m+1, t);}int main(){int a[]={3,4,5,1,2}; int result=helper(a, 0, 4);cout<<result<<endl; } 

 

下面的源码为自己原始代码,有些地方多考虑了,注意观察

注意:除以2 为右移 1 位

#include<iostream>using namespace std;int GetMinNum(int num[],int begin,int end){if(begin==end ||num[begin]<num[end])return num[begin]; int mid=begin +(end-begin)>>1;if(num[mid]>num[begin]) //右侧  return min(GetMinNum(num,mid+1,end) , num[begin]); else     return min(GetMinNum(num,0,mid) , num[mid+1]);  }int main(){int num[] = {3,4,5,1,2};int MinNum=GetMinNum(num,0,4); cout<<MinNum<<endl;getchar();return 0;}

 

五,给出一个函数来输出一个字符串的所有排列。参考

分析:

        1) 每个元素依次放到首位,然后对其余元素递归

        2) 当当前元素到达末尾的时候,输出该序列

       相当于strlen(str)层循环,每层strlen(str)次。

源码一:注意应要使用数组参数char  str[],而不是用变量char  *str

                注意for 循环,第一个是 s ,就是说从第一个开始交换

#include  <iostream>using namespace std;void MySort(char str[],int s,int e){if(s==e)    cout<<str<<endl;         else{for(int i=s;i<e;++i){swap(str[s],str[i]); MySort(str,s+1,e);swap(str[s],str[i]); } }  }int main(){    char str[]="abc";     MySort(str,0,3);return 0;}

 

扩展:

         如果不是求字符的所有排列,而是求字符的所有组合,应该怎么办呢?(P72)

        思路:这里不采用交换方式,而是采用删减的方式。采用不同的剔除顺序,并用prex保留删减值,这样将得到所有符合条件的序列

 

#include  <iostream>using namespace std;void MySort(string str,string prex){    string temp=str;     cout<<prex<<endl; //注意这里输出         for(int i=0;i<str.length();++i){MySort(str.erase(i,1),prex+str.at(i)); //注意这里处理     str=temp; }   }int main(){    string str ="abc";     MySort(str,"");return 0;}

 

热点排行