一,颠倒栈。
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)
{
<wbr><wbr><wbr></wbr></wbr></wbr>if(!stack.empty())
<wbr><wbr><wbr></wbr></wbr></wbr>{
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>T top = stack.top();
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>stack.pop();
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>ReverseStack(stack);
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>AddToStackBottom(stack, top);
<wbr><wbr><wbr></wbr></wbr></wbr>}
}
我们需要考虑的另外一件事情是如何把一个元素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)
{
<wbr><wbr><wbr></wbr></wbr></wbr>if(stack.empty())
<wbr><wbr><wbr></wbr></wbr></wbr>{
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>stack.push(t);
<wbr><wbr><wbr></wbr></wbr></wbr>}
<wbr><wbr><wbr></wbr></wbr></wbr>else
<wbr><wbr><wbr></wbr></wbr></wbr>{
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>T top = stack.top();
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>stack.pop();
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>AddToStackBottom(stack, t);
<wbr><wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>stack.push(top);
<wbr><wbr><wbr></wbr></wbr></wbr>}
}
先弹出,处理,再恢复,化作子问题的一个重要方式
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,S为N个骰子顶面数之和为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<=N,j<=0或j<i或j>6*i,ai,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;
}