现在的位置: 首页 > 综合 > 正文

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

2013年10月06日 ⁄ 综合 ⁄ 共 7688字 ⁄ 字号 评论关闭

一,颠倒栈。

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++;
		else
			break;
	}
	//统计间隔数
	int preCard = pCards[i];    
	for(i = i + 1; i < nLen; i++)
	{
		int curCard = pCards[i];
		if(preCard == curCard)  //与前一张牌比较
			return false;
		else
			capCount += 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 compare1
	
	for(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;
}

 

抱歉!评论已关闭.