@qiezhian
2014-07-03T13:30:25.000000Z
字数 17632
阅读 1850
算法
题目:输入一个正数 n,输出所有和为 n 连续正数序列。
例如输入 15,由于 1+2+3+4+5=4+5+6=7+8=15,所以输出 3 个连续序列 1-5、4-6 和 7-8。
分析:这是网易的一道面试题。假设有m个连续正数序列和为n,则设其第一个元素为x,则序列为x,x+1...x+m-1,其和为
所以对于n,求具有m个连续正数序列,首先
#include <iostream>
#include <stdlib.h>
using namespace std;
void ContinuousSum( int element );
int main( int argc, char* argv[] )
{
if( argc <= 1 )
{
cout<< "Arguments not enough!"<<endl;
return 0;
}
while( --argc )
{
cout<< " The Continuous Sum of "
<< atoi( *++argv )<<" is:"<<endl;
ContinuousSum( atoi( *argv ) );
}
return 0;
}
void ContinuousSum( int element )
{
int m=2, n;
int tmp;
int meta;
while( ( tmp = m*(m-1)/2 )
&& ( meta = element - tmp ) > 0 )
{
if( meta%m == 0 )
{
n = meta/m;
for( int i=0; i<m; i++ )
cout<< n+i<<" ";
cout<<endl;
}
m++;
}
}
题目:求一个数组的最长递减子序列,比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}。
分析:创新工厂笔试题。这是很经典的一个问题,用动态规划解决。假设源数组为A,定义一个辅助数组为B,B[i]表示以A[i]结尾的最长递减序列的长度。举个简单的例子,如果A[i]大于之前的所有元素,那么B[i] = 1。
有了这个辅助数组后,可以推出下面这个递推式子。B[i] = max{B[k] + 1, A[k]>A[i]&&0= A[k]时,A[i]就是前一个元素,递归求解即可。算法的时间复杂度为O(n^2),网上还有一种解法,复杂度为O(nlogn)。有兴趣的网友可以搜一下,本人水平有限没怎么明白,这里就不引用了。转自:http://blog.csdn.net/wuzhekai1985/article/details/6733505。
/*
Filename: FindLDS.cpp
Function: 求数组的最长递减子序列(可不连续)
Details:
*/
#include <iostream>
#include <stdlib.h>
#include <memory.h>
using namespace std;
void FindLDS( int A[], int length );
int Find( int B[], int length );
int main()
{
int A[] = { 9,5,4,3,2,6,5,3,2,1};
int length = sizeof( A )/sizeof( int );
FindLDS( A, length );
}
void FindLDS( int A[], int length )
{
//分配辅助数组B的空间
int* B = (int*) malloc( sizeof(int)*length );
if( !B )
return;
B[0] = 1;
int i=1, k;
int bMax=0;
int mLastPos;
//计算辅助数组B的值
while( i < length )
{
for( k=0; k<i; k++ )
if( A[k] > A[i] && B[k] > bMax )
bMax = B[k];
B[i] = bMax+1;
bMax = 0;
i++;
}
//找出B中最大值,它是最长递减子序列的末尾元素
mLastPos = Find( B, length );
//辅助数组C是用来标记最终结果的,
//如果C[i]==1则A[i]是结果中的一个元素
unsigned char* C = (unsigned char *) malloc(
sizeof(unsigned char)*(mLastPos+1) );
if( !C )
return ;
memset( C, 0, mLastPos+1 );
//对数组C进行标记
C[ mLastPos ] = 1;
for( i = mLastPos; i>=0 && k>=0 ; )
{
k = i;
while( --k >= 0 )
if( B[k] == B[i]-1 && A[k] > A[i] )
{
C[k]=1; i=k; break;
}
}
//输出最长递减子序列
for( i=0; i<=mLastPos; i++ )
if( C[i] )
cout<<A[i]<<" ";
cout<<endl;
}
int Find( int B[], int length )
{
if( B==NULL || length<=0 )
return -1;
int max=0, index=-1;
for( int i=0; i<length; i++ )
if( B[i] > max )
{
max = B[i];
index = i;
}
return index;
}
题目:输入一个链表,输出该链表的第K个节点。为符合多数人习惯,指定链表尾节点为倒数第一个节点。
分析:思路很简单我们在遍历时维持两个指针,第一个指针从链表的头指针开始遍历,在第k-1步之前,第二个指针保持不动;在第k-1步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
相关题目:求链表中间节点,如果节点总数为奇数,则返回中间节点,为偶数,返回中间两个节点中任意一个。思路就是两个指针,一个走两步,一个走一步,当走得快的到达末尾时,走得慢的就是中间节点。这个函数在下面代码中有实现。
判断一个单向链表是否形成了环形结构,可以定义两个指针,一个走一次一步,一个一次两步,如果走得快的指针走到了链表末尾都没有追上慢指针,则没有环形结构。
/*
Filename: FindKthToTail.cpp
Function: 找出链表倒数第K个节点,找出链表中间节点
Details:
*/
#include <iostream>
using namespace std;
struct Node
{
int m_nElement;
Node* m_pNext;
};
Node* FindKthToTail( Node* list, int k);
Node* FindMiddle( Node* list );
Node* create_node( int i );
Node* create_list( int length );
int main()
{
Node* list = create_list( 10 );
cout<< FindKthToTail( list, 2 )->m_nElement <<endl;
cout<< FindMiddle( list )->m_nElement <<endl;
}
Node* create_list( int length )
{
Node* head = NULL;
Node* tmp;
for( int i=0; i<length; i++ )
{
if( head==NULL )
{
head = create_node( i );
tmp = head;
}
else
{
tmp->m_pNext = create_node( i );
tmp = tmp->m_pNext;
}
}
return head;
}
Node* create_node( int i )
{
Node* tmp = new Node;
if( tmp==NULL )
return NULL;
tmp->m_nElement = i;
tmp->m_pNext = NULL;
return tmp;
}
Node* FindKthToTail( Node* list, int k)
{
if( list==NULL || k==0 )
return NULL;
Node *pFirst, *pSecond;
pFirst = pSecond = list;
for( int i=1; i<k; i++)
if( pFirst->m_pNext!=NULL )
pFirst = pFirst->m_pNext;
else
return NULL;
while( pFirst->m_pNext!=NULL )
{
pFirst = pFirst->m_pNext;
pSecond = pSecond->m_pNext;
}
return pSecond;
}
Node* FindMiddle( Node* list )
{
if( list==NULL )
return NULL;
Node *pFirst, *pSecond;
pFirst = pSecond = list;
int i=0;
while( pFirst->m_pNext!=NULL )
{
pFirst = pFirst->m_pNext;
if( i++%2 )
pSecond = pSecond->m_pNext;
}
return pSecond;
}
http://blog.csdn.net/hackbuteer1/article/details/7462447
题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次,请找出这两个只出现一次的数字。要求时间复杂度为
分析:可以先考虑一个更基本的问题:即一个整型数组里除了一个数字之外,其他的数字都出现了两次,找出这个数字。如果能解决这个问题,那么就把数组分成两部分,每部分都有且只含有一个出现一次的数字,其他都出现两次。所以问题分解为两个子问题:1.将数组分成两组,每组有且只含有一个出现一次的数字;2.在子数组里找出所需数字。
先解决第二个问题,基于这样一个事实:两个相同的数字异或的结果为0.所以对数组从头到尾进行异或,相同数字异或为0,最后的异或结果就是只出现一次的数字。
再解决第一个问题,同样基于上述事实:两个数字异或后必然有一位为1,也就是说可以依靠这一位分辨两个不同的数字,也可以依靠这一位是否为1来将数组分成两个子数组,两个不同的数字必然分别在两个子数组中,而重复的数字必然在同一子数组中。
/*
Filename: FindNumsAppearOnce.cpp
Function: 找到整型数组中两个只出现一次的数字,其他数字均出现两次
Details:
*/
#include <iostream>
using namespace std;
void FindNumsAppearOnce( int A[], int length );
int FindFirstBit1( int n );
bool IsBit1( int n, int bitIndex );
int main( )
{
int A[] = { 1,2,3,4,5,6,4,3,2,1 };
int length = sizeof(A)/sizeof(int);
FindNumsAppearOnce( A, length );
}
void FindNumsAppearOnce( int A[], int length )
{
int ResultExclusiveOR = 0;
for( int i=0; i<length; i++ )
ResultExclusiveOR ^= A[i];
int index = FindFirstBit1( ResultExclusiveOR );
int num1=0, num2=0;
for( int i=0; i<length; i++ )
if( IsBit1( A[i], index ) )
num1 ^= A[i];
else
num2 ^= A[i];
cout<<" The numbers appear once are: "<<num1<<" and "<<num2<<endl;
}
/*
函数功能:找到数字n的从右往左、
第一个不为0的位的索引,最右边位索引为0,向左递增
*/
int FindFirstBit1( int n )
{
int index = 0;
while( !( n&1 ) && ( index < 8*sizeof(int) ) )
{
n = n>>1;
index++;
}
return index;
}
//函数功能:数字n的第bitIndex位是否为1
bool IsBit1( int n, int bitIndex )
{
while( bitIndex>0 )
n = n>>1;
return ( n & 1 );
}
题目: 我们定义两节点的距离为两节点边的个数,求一棵二叉树中相距最远两节点的距离。
分析: 第一想法是,一棵树的相距最远的两个节点有三种可能:两节点都在左子树,或都在右子树,或一左一右。若是一左一右,则最远距离为两子树深度之和,若是在同侧子树中,则递归求解左右子树的最远节点距离,最后结果取三者最大值。
但递归效率较低,是否有非递归算法?
/*
Filename: LargestDistanceInBintree.cpp
Function: 找到二叉树中相距最远的节点之间距离(边的个数)
Details:
*/
#include <iostream>
using namespace std;
#ifndef MAX
#define MAX( x,y ) ( x>y?x:y )
#endif
//二叉树节点定义
struct Node
{
int m_nElement;
Node* m_pLeft;
Node* m_pRight;
};
/*
函数功能:找出以root为根节点的二叉树中,
相距最远的节点之间的距离
*/
int LargestDistanceInBintree( Node* root );
/*
函数功能: 找出以root为根节点的二叉树深度,最底层节点深度定义为1
*/
int Depth( Node* root );
/*
函数功能: 创建一棵简单二叉树
*/
Node* create_tree( int n );
Node* create_node( int i );
int main( )
{
Node* root=NULL;
root = create_tree( 5 );
cout<<" Largest Distance between nodes in tree is: "
<<LargestDistanceInBintree( root )<<endl;
return 1;
}
int Depth( Node *root )
{
if( root==NULL )
return 0;
return MAX( ( Depth(root->m_pLeft)+1 ) ,( Depth(root->m_pRight)+1 ) );
}
int LargestDistanceInBintree( Node* root )
{
if( root==NULL )
return 0;
int LDistance = Depth( root->m_pLeft ) + Depth( root->m_pRight );
return MAX(
(MAX( LargestDistanceInBintree( root->m_pLeft ),
LargestDistanceInBintree( root->m_pRight ) )),
(LDistance) );
}
Node* create_tree( int n )
{
Node* head = NULL;
Node* left = NULL;
Node* right = NULL;
for( int i=0; i<n; )
{
if( head==NULL )
{
head = create_node( i++ );
left = head;
right = head;
}
else
{
left->m_pLeft = create_node( i++ );
right->m_pRight = create_node( i++ );
left = left->m_pLeft;
right = right->m_pRight;
}
}
return head;
}
Node* create_node( int i )
{
Node *tmp = new Node;
if( !tmp )
return NULL;
tmp->m_nElement = i;
tmp->m_pLeft = tmp->m_pRight = NULL;
return tmp;
}
题目: 要求下排每个数都是先前上排那十个数在下排出现的次数。
上排的十个数如下:
【0,1,2,3,4,5,6,7,8,9】
举一个例子,
数值: 0,1,2,3,4,5,6,7,8,9
分配: 6,2,1,0,0,0,1,0,0,0
0 在下排出现了6 次,1 在下排出现了2 次,
2 在下排出现了1 次,3 在下排出现了0 次....
以此类推..
分析: 腾讯面试题,竟然要求十分钟内得出结果(吓尿了),想了十分钟也没想出好的方法(太弱了),百度之,找到了一个较好的解法(参考:http://www.cnblogs.com/liketowindy/p/3606351.html)。
思路:
1、先初始化下排数组,使其值都为0。
2、初始化success状态为1,对上排数组进行遍历,获取每个值在下排数组出现的次数。
3、判断出现次数count,如果与下排数组对应值不相等,将count赋值给下排数组,并将success改为0。
4、判断success的值,为1退出整个循环,为0重复步骤2直至success的值为1。
下面的程序对0-n的序列均能得到正确结果,值得一提的是,当n<7时,会出现死循环,即没有正确的结果。个人分析认为,FindNNums()
函数最多运行length
次即可得到结果,否则即认为出现死循环,没有正确的结果。本题有待于继续完善。
/*
Filename: FindNNums.cpp
Function: 根据上排给出十个数,在其下排填出对应的十个数,
要求下排的每个数都是先前上排那十个数在下排出现的次数
Details:
*/
#include <iostream>
#include <stdlib.h>
#include <memory.h>
using namespace std;
bool FindNNums( int A[], int B[], int length );
int GetFrequency( int element, int B[], int length );
int main()
{
int A[]={0,1,2,3,4,5,6,7,8,9};
int length = sizeof(A)/sizeof(int);
int *B = (int*) malloc( sizeof(int)*length );
if( B==NULL )
return 0;
memset( B, 0, length );
int loops = 0;
while( !FindNNums( A, B,length ) )
if( loops++ > length )
{
loops = -1;
break;
}
if( loops == -1 )
cout<<"no result!"<<endl;
else
for( int i=0; i<length; i++ )
cout<<B[i];
cout<<endl;
return 1;
}
bool FindNNums(int A [ ],int B [ ],int length)
{
bool success=1;
int frequency=0;
for( int i=0; i<length; i++ )
{
frequency = GetFrequency( A[i], B, length );
if( frequency!=B[i] )
{
B[i] = frequency;
success=0;
}
}
return success;
}
int GetFrequency(int element,int B [ ],int length)
{
int frequency=0;
for( int i=0; i<length; i++ )
if( element == B[i] )
frequency++;
return frequency;
}
题目: 一个大小为n的数组,里面的数都属于范围[0,n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。
分析: Google面试题。主要的难点在于O(1)的时间复杂度(参考:http://blog.csdn.net/morewindows/article/details/8212446)。由于n大小的数组,数值范围也为[0,n-1],可以利用数组本身做一个哈希表。遍历数组A:如果A[A[i]]>n
则说明A[i]前面出现过,否则令 A[A[i]]+=n
。代码如下。
/*
Filename: FindRepeatedNums.cpp
Function: 一个大小为n的数组,里面的数都属于范围[0,n-1],
有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。
Details:
*/
#include <iostream>
#include <stdlib.h>
using namespace std;
void FindRepeatedNums( int A[], int length );
int main( )
{
int A[] = { 0,1,2, 4,5,6,4,6,8,4,9 };
int length = sizeof(A)/sizeof(int);
FindRepeatedNums( A, length );
}
void FindRepeatedNums(int A [ ],int length)
{
int num=-1;
int realIndex ;
for( int i=0; i<length; i++ )
{
realIndex = A[i] ;
while( realIndex > length )
realIndex -= length;
if( A[realIndex] > length )
cout<< realIndex <<endl;
A[realIndex] += length;
}
}
题目:定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。如把字符串abcdef左旋转2位得到字符串cdefab。请实现字符串左旋转的函数,要求对长度为n的字符串操作的时间复杂度为O(n),空间复杂度为O(1)。
分析:http://blog.csdn.net/zhoushuai520/article/details/7703368
/*
Filename: ReverseString.cpp
Function: 定义字符串左旋转操作:把字符串前面的若干个字符
移动到字符串的尾部。如把字符串abcdef左旋转2位得到字符
串cdefab。请实现字符串左旋转的函数,要求对长度为n的字符
串操作的时间复杂度为O(n),空间复杂度为O(1)。
Details:
*/
#include <iostream>
#include <string.h>
using namespace std;
/*
函数功能: 反转字符串str中start至end位部分
*/
void Rotate( char *str, int start, int end );
/*
函数功能: 字符串左旋转n位操作,
可处理n<0的情况,即右旋操作
*/
void ReverseString( char *str, int n );
int main()
{
char str[] = "abcdefghijklmn";
ReverseString( str, -3 );
cout<<str<<endl;
}
void ReverseString( char *str, int n )
{
int length = strlen( str );
if( n>0 )
{
n = n%length;
Rotate( str, 0, n-1 );
Rotate( str, n, length-1 );
Rotate( str, 0, length-1 );
}
else if( n<0 )
{
n = (-n)%length;
ReverseString( str, length-n );
}
}
void Rotate( char *str, int start, int end )
{
if( end<=start )
return;
int i=start, j=end;
char tmp;
while( i<j )
{
tmp = str[i];
str[i] = str[j];
str[j] = tmp;
i++; j--;
}
}
题目:输入一个英文句子,反转句子单词顺序,但单词内的字母顺序不变,标点符号和普通字符一样处理。
例如"I am a student."反转为"student. a am I"。
分析:是经典的左旋转字符串问题的扩展。首先我们对每个单词进行反转,最后对整个句子进行反转即可。
题目:输入N
个元素及一个整数K
,在N
个元素中找出第K
小的数。
方案1.直接快速排序,第K
个元素即所求。平均时间
方案2.快速选择,即在快速排序过程中:如果比枢纽元小的元素个数n<=K-1
,则所求元素在枢纽元和大于枢纽元的集合中去找,反之同理。也就是快速选择不是对N
个元素进行完全排序,而是有方向性的部分排序,直到找到第K
小的元素。平均时间
方案3.在任一时刻,维持一个K
个最小元素集合S
,接下来读入一个新元素时,将新元素与S
中最大元素比较,若小于最大元素则从S
中剔除最大元素,并将新元素加入S
。如果用堆来实现S
,总的时间是K=N/2
时,时间界为
上述两个方案均会打乱原序列,如果不能打乱,需要一份copy。
/*
Filename: KSelection.cpp
Function: 输入N个元素及一个整数K,在N个元素中找出第K小的数。
Details:
*/
#include <iostream>
#include "heap.hpp"
using namespace std;
int QuickSelectionK( int A[], int length, int K );
int HeapSelectionK( int A[], int length, int K );
void Swap( int *a, int *b );
int main( )
{
int A[] = { 1,2,3,4,5,6,7,8,9};
int length = sizeof(A)/sizeof(int);
int K = 5;
cout<<QuickSelectionK( A, length, K )<<endl;
cout<<HeapSelectionK( A, length, K )<<endl;
}
int QuickSelectionK( int A[], int length, int K )
{
if( A==NULL || length<=0 || K>=length )
return 0;//error
int pivot = length/2;
int i=0, j=length-2;
swap( A[pivot], A[length-1] );
while( i<j )
{
while( A[i]<A[length-1] )
i++;
while( A[j]>A[length-1] )
j--;
if( i<j )
Swap( &A[i], &A[j] );
else
break;
}
Swap( &A[i], &A[length-1] );
if( i+1>K )
return QuickSelectionK( A, i, K );
else if( i+1<K )
return QuickSelectionK( A+i+1, length-i-1, K-i-1 );
else
return A[i];
}
int HeapSelectionK( int A[], int length, int K )
{
if( A==NULL || length<=0 || K>=length )
return 0;//error
BinaryHeap<int> heap( K,1 );
for( int i=0; i<K; i++ )
heap.Insert( A[i] );
for( int i=K; i<length; i++ )
if( heap.FindTop() > A[i] )
{
heap.DeleteTop();
heap.Insert( A[i] );
}
return ( heap.FindTop() );
}
void Swap( int *a, int *b )
{
int tmp = *a;
*a = *b;
*b = tmp;
}
题目:输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点。用递归和循环两种方法完成树的镜像转换。
分析:递归的方法很简单,如果左右子树均已完成镜像转换,则只要交换左右子树根节点即可。循环的方法是对每个节点依次镜像转换,所以需要对树进行遍历,这里用宽度优先遍历(层序遍历)为佳。循环实现未完成。
/*
Filename: MirrorofBintree.cpp
Function:
Details:
*/
#include <iostream>
using namespace std;
struct Node
{
int m_nElement;
Node* m_pLeft;
Node* m_pRight;
};
Node* create_node( int i );
Node* create_tree( int n );
void MirrorofBintreeRecursively( Node *root );
void PrintTreePreOrderly( Node *root );
int main()
{
Node* root = NULL;
root = create_tree( 5 );
cout<<"The preorderly print of original tree:"<<endl;
PrintTreePreOrderly( root );
cout<<"The preorderly print of mirror tree:"<<endl;
MirrorofBintreeRecursively( root );
PrintTreePreOrderly( root );
}
void MirrorofBintreeRecursively( Node* root )
{
if( root==NULL )
return ;
Node *tmp=NULL;
MirrorofBintreeRecursively( root->m_pLeft );
MirrorofBintreeRecursively( root->m_pRight );
tmp = root->m_pLeft;
root->m_pLeft = root->m_pRight;
root->m_pRight = tmp;
}
Node* create_tree( int n )
{
Node* head = NULL;
Node* left = NULL;
Node* right = NULL;
for( int i=0; i<n; )
{
if( head==NULL )
{
head = create_node( i++ );
left = head;
right = head;
}
else
{
left->m_pLeft = create_node( i++ );
right->m_pRight = create_node( i++ );
left = left->m_pLeft;
right = right->m_pRight;
}
}
return head;
}
Node* create_node( int i )
{
Node *tmp = new Node;
if( !tmp )
return NULL;
tmp->m_nElement = i;
tmp->m_pLeft = tmp->m_pRight = NULL;
return tmp;
}
void PrintTreePreOrderly( Node *root )
{
if( !root )
return;
cout<< root->m_nElement << endl;
PrintTreePreOrderly( root->m_pLeft );
PrintTreePreOrderly( root->m_pRight );
}
题目:在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。
分析:这道题是2006年google的一道笔试题。构造一张简单的哈希表即可,这里假定输入字符串的字符集为大小写字符总共26个字符,如果有其他字符可扩展。
/*
Filename: FindFirstCharAppearOnce.cpp
Function:
在一个字符串中找到第一个只出现一次的字符,
如输入abaccdeff,则输出b。
Details: 本代码仅假设字符集为a-z,可扩展
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ASCII_LENGTH (26)
char FindFCAO( char *str );
int main()
{
char str[] = "abcdefgffdegahibgj";
printf("The first char appear once of string %s is: %c \n",
str, FindFCAO( str ) );
}
char FindFCAO( char *str )
{
unsigned char char_hash[ ASCII_LENGTH ];
memset( char_hash, 0, ASCII_LENGTH );
int i = 0;
while( str[i]!='\0' )
char_hash[ str[i++]-'a' ]++;
i = 0;
while( str[i] != '\0' )
if( char_hash[ str[i]-'a' ] == 1 )
break;
else
i++;
return str[i];
}
题目:约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。最后剩下1号。
分析: 第一种想法就是循环链表,按规则数,数到第M个就删除节点,从下一个节点接着数,直到只剩一个节点,时间复杂度为
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。因此如果要追求效率,就要打破常规,实施一点数学策略。
为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。
现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k)%n。
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式:
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1
由于是逐级递推,不需要保存每个f[i],程序也是异常简单,可见数学是多么的重要啊!!!
/*
Filename: Josephus.cpp
Function: 求解约瑟夫问题,得到最后一个幸存者编号。
约瑟夫问题是个有名的问题:N个人围成一圈,
从第一个开始报数,第M个将被杀掉,最后剩下一个,其余
人都将被杀掉。例如N=6,M=5,被杀掉的人的序号为5,4,
6,2,3。最后剩下1号。
Details:
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
struct Node
{
int m_nElement;
Node* m_pNext;
};
Node* create_node( int i );
int JosephusUsingList( int N, int M );
int Josephus( int N, int M );
//输入参数顺序为: N M
int main(int argc, char* argv[])
{
int M, N;
if( argc<3 )
{
printf("arguments not enough!\n");
return 0;
}
N = atoi(argv[1]);
M = atoi(argv[2]);
printf( "N:%d, M:%d \n", N, M );
JosephusUsingList( N, M );
Josephus( N, M );
return 0;
}
int JosephusUsingList(int N, int M )
{
int i;
clock_t tStart, tEnd;
int time;
Node *pHead,*pNode, *pPre;
pHead = pNode = pPre = NULL;
tStart = clock();
//build the loop list
for( i=1; i<=N; i++ )
{
if( pHead==NULL )
{
pHead = create_node( i );
pNode = pHead;
}
else
{
pNode->m_pNext = create_node( i );
pNode = pNode->m_pNext;
}
}
//create a loop list
pNode->m_pNext = pHead;
printf("build done!\n");
//start execuse process
pPre = pHead;
pNode = pHead;
while( pNode!= pNode->m_pNext )
{
for( i=0; i<M; i++)
{
pPre = pNode;
pNode = pNode->m_pNext;
}
//delete pNode
printf( "%d \n", pNode->m_nElement );
pPre->m_pNext = pNode->m_pNext;
free( pNode );
pNode = pPre->m_pNext;
}
tEnd = clock();
time = tEnd - tStart;
printf( "The surviver is %d .\n", pNode->m_nElement );
printf( "Run time is %d ms.\n",time);
return pNode->m_nElement;
}
Node* create_node( int i )
{
Node* tmp = new Node;
if( tmp==NULL )
return NULL;
tmp->m_nElement = i;
tmp->m_pNext = NULL;
return tmp;
}
int Josephus( int N, int M )
{
int num=1;
for( int i=1; i<N; i++ )
num = ( num + M ) % N;
printf("Josephus: %d\n", num );
return num;
}
题目: 输入n,求出Fibonacci数列第n项。f(0)=0,f(1)=1...f(n)=f(n-1)+f(n-2)。规定f(0)为第一项,所以第n项为f(n-1)。
分析: 千万不要用递归去实现,及其糟糕的时间界,因为涉及过多的递归调用会耗费大量的时间,还有可能会产生栈溢出。
/*
Filename: Fibonacci.cpp
Function:
输入n,得到Fibonacci数列的第n项,规定f(0)为第一项
f(0)=0,f(1)=1......f(n)=f(n-1)+f(n-2)
Details:
*/
#include <iostream>
using namespace std;
int Fibonacci( int n );
int main()
{
cout<< Fibonacci( 4 )<<endl;
return 1;
}
int Fibonacci( int n )
{
if( n==0 )
return 0;
else if( n==1 )
return 1;
int num1=0, num2=1;
int result;
for( int i=1; i<n; i++ )
{
result = num1 + num2;
num1 = num2;
num2 = result;
}
return result;
}
题目:输入一个表示整数的字符串,将字符串转成整数并输出。例如输入"345",则输出整数345.
分析: 要实现类似于atoi()
函数功能。
/*
Filename: StringtoInt.cpp
Function:
Details:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
int StringtoInt( char *str , int *err );
int main( )
{
char str[] = "-3456";
int err=0;
int result = StringtoInt( str, &err );
if( err )
printf("This string is not right!\n");
else
printf("The corresponding int of this string is: %d.\n",
result );
return err;
}
/*
函数功能: 第一个字符可以为'+'或'-'表示符号,
后面必须全部为0-9的数字,不能有其他字符,
否则认为输入错误,err置1,若str==NULL,err置2
*/
int StringtoInt( char *str, int *err )
{
if( str==NULL )
{
*err = 2;
return 0;
}
int result =0;
int length =0;
int exp = 1;
int j = 0;
if( str[0] == '+' )
return StringtoInt( str+1, err );
else if( str[0] == '-' )
return ( -StringtoInt( str+1, err) );
length = strlen( str );
for( int i=length-1; i>=0; i-- )
{
if( str[i]>='0' && str[i]<='9' )
{
j = str[i] - '0';
result += j*exp;
}
else
*err = 1;
exp *= 10;
}
return result;
}