经典面试题

1、编程实现两个正整数的除法,当然不能用除法操作符。

//编程实现两个正整数的除法,当然不能用除法操作符
int div(const int x, const int y)
{
	int left_num = x;
	int result = 0;
	int multi;
	while (left_num >= y)    //模拟小学学过的竖式除法运算
	{
		multi = 1;
		while (y * multi <= (left_num >> 1))
		{
			multi = multi << 1;
		}
		result += multi;
		left_num -= y * multi;
	}
	return result;
}

2、现在有一个数组,已知一个数出现的次数超过了一半,请用O(n)的复杂度的算法找出这个数。

第1种方法:

创建一个hash_map,key为数组中的数,value为此数出现的次数。遍历一遍数组,用hash_map统计每个数出现的次数,并用两个值存储目前出现次数最多的数和对应出现的次数。
这样可以做到O(n)的时间复杂度和O(n)的空间复杂度,满足题目的要求。
但是没有利用“一个数出现的次数超过了一半”这个特点。也许算法还有提高的空间。
第2种方法(推荐):

使用两个变量A和B,其中A存储某个数组中的数,B用来计数。开始时将B初始化为0。   遍历数组,如果B=0,则令A等于当前数,令B等于1;如果当前数与A相同,则B=B+1;如果当前数与A不同,则令B=B-1。遍历结束时,A中的数就是要找的数。
这个算法的时间复杂度是O(n),空间复杂度为O(1)。

int main(void)
{
    int i, A, B;
    int a[10] = {7,1,3,1,2,1,1,6,1,1};
    
    
    B=0;
    for(i=0; i<10; i++)
	{
        if(B==0)
        {
            A = a[i];
            B = 1;
        }
		else if( A == a[i] )
            B++;
        else if(A != a[i])
            B--;
	}
    printf("%d\n", A);
    return 0;
}

 3、求取字符串长度,不使用while、for等循环语句和字符串处理函数。

int mystrlen(const char *str)
{
	if(*str=='\0')
		return 0;
	else
		return 1+mystrlen(str+1);
}

4、两个单向链表,有可能交叉,请设计算法判断是否交叉,如果交叉,返回交叉点!算法复杂度o(n)
两个链表最后是合并成一个而不是交叉,所以:
(1)先找到p1,p2的最后一个节点,同时记录节点数量a,b;
(2)判断最后一个节点是否相同,

如果不相同则没相交。如果相同,则从第一个节点和|a-b|+1个节点开始比较,看是否相等,不相等都寻找下一个节点,直到找到交叉点。

如果两个单向链表有公共的结点,也就是说两个链表从某一结点开始,它们的m_pNext都指向同一个结点。但由于是单向链表的结点,每个结点只有一个m_pNext,因此从第一个公共结点开始,之后它们所有结点都是重合的,不可能再出现分叉。所以,两个有公共结点而部分重合的链表,拓扑形状看起来像一个Y,而不可能像X。

看到这个题目,第一反应就是蛮力法:在第一链表上顺序遍历每个结点。每遍历一个结点的时候,在第二个链表上顺序遍历每个结点。如果此时两个链表上的结点是一样的,说明此时两个链表重合,于是找到了它们的公共结点。如果第一个链表的长度为m,第二个链表的长度为n,显然,该方法的时间复杂度为O(mn)。

接 下来我们试着去寻找一个线性时间复杂度的算法。我们先把问题简化:如何判断两个单向链表有没有公共结点?前面已经提到,如果两个链表有一个公共结点,那么 该公共结点之后的所有结点都是重合的。那么,它们的最后一个结点必然是重合的。因此,我们判断两个链表是不是有重合的部分,只要分别遍历两个链表到最后一 个结点。如果两个尾结点是一样的,说明它们用重合;否则两个链表没有公共的结点。

在上面的思路中,顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长l个结点,我们先在长的链表上遍历l个结点,之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。由于两个链表从第一个公共结点考试到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。

在这个思路中,我们先要分别遍历两个链表得到它们的长度,并求出两个长度之差。在长的链表上先遍历若干次之后,再同步遍历两个链表,知道找到相同的结点,或者一直到链表结束。此时,如果第一个链表的长度为m,第二个链表的长度为n,该方法的时间复杂度为O(m+n)。

基于这个思路,我们不难写出如下的代码:

struct ListNode      //链表的结点
{
	int m_nKey;
	ListNode*   m_pNext;
};

///
// Find the first common node in the list with head pHead1 and 
// the list with head pHead2
// Input: pHead1 - the head of the first list
//        pHead2 - the head of the second list
// Return: the first common node in two list. If there is no common
//         nodes, return NULL
///
ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2)
{
	// Get the length of two lists
	unsigned int nLength1 = ListLength(pHead1);
	unsigned int nLength2 = ListLength(pHead2);
	int nLengthDif = nLength1 - nLength2;

	// Get the longer list
	ListNode *pListHeadLong = pHead1;
	ListNode *pListHeadShort = pHead2;
	if(nLength2 > nLength1)
	{
		pListHeadLong = pHead2;
		pListHeadShort = pHead1;
		nLengthDif = nLength2 - nLength1;
	}

	// Move on the longer list
	for(int i = 0; i < nLengthDif; ++ i)
		pListHeadLong = pListHeadLong->m_pNext;

	// Move on both lists
	while((pListHeadLong != NULL) &&  (pListHeadShort != NULL) && (pListHeadLong != pListHeadShort))
	{
		pListHeadLong = pListHeadLong->m_pNext;
		pListHeadShort = pListHeadShort->m_pNext;
	}

	// Get the first common node in two lists
	ListNode *pFisrtCommonNode = NULL;
	if(pListHeadLong == pListHeadShort)
		pFisrtCommonNode = pListHeadLong;
	return pFisrtCommonNode;
}

///
// Get the length of list with head pHead
// Input: pHead - the head of list
// Return: the length of list
///
unsigned int ListLength(ListNode* pHead)
{
	unsigned int nLength = 0;
	ListNode* pNode = pHead;
	while(pNode != NULL)
	{
		++nLength;
		pNode = pNode->m_pNext;
	}
	return nLength;
}

5、在if里面请写入语句,使得打印出  Hello  World。

int main(void)
{
	if()    //应该填入!printf("Hello "),会先打印出Hello,然后进行if()判断,!printf()取反就是0,所以不成立只能运行else,接着打印出World
	{
		printf("Hello ");
	}
	else
	{
		printf("World");
	}
	return 0;
}

6、我们通常登陆或者注册要输入验证码,今天注册某个网站的验证码不是直接给出来的,它给出了一道程序,让我写出输出结果,题目如下:(输出:4321)

int main(void)
{
	int i=43;
	printf("%d",printf("%d",printf("%d",i)));    //这个是嵌套的,应该先运行最里面的那个printf,输出43,然后printf返回2,在输出2后printf返回值为1,最后输出1
	return 0;
}

printf函数返回一个int值,表示被打印的字符数。

int main(void)
{
	int i = 43, m, n;
	m = printf("%d",i);         //printf函数打印43后,返回被打印的字符个数,2
	n = printf("%d\n",i);       //printf函数打印43及回车后,返回被打印的字符个数,3
	printf("%d %d\n",m,n);      //输出2、3
	return 0;
}
//double类型的例子
int main(void)
{
	int m, n;
	double i;
	i = 0.27;              //小数点后面不足6位的要补足6位
	m = printf("%lf",i);          //printf函数,返回被打印的字符个数,小数点后面6位加上0.共是8个字符
	n = printf("%lf\n",i);        //小数点后面6位加上回车,再加上0.共是9个字符
	printf("%d %d\n",m,n);
	
	i = 345.27; 
	m = printf("%lf",i);          //小数点后面6位加上345.共是10个字符
	n = printf("%lf\n",i);        //小数点后面6位加上回车,再加上345.共是11个字符
	printf("%d %d\n",m,n);
	return 0;
}

7、百度面试题目,现在有1千万个随机数,随机数的范围在1到1亿之间。现在要求写出一种算法,将1到1亿之间没有在随机数中的数求出来。

解决办法:

一)用一个32位的整数32位表示32个数,1亿/32 = 3125000,使用3.125 * 4M byte空间即可保存1亿个数,即index[3125000].

二)对于数n,(n-1) / 32 为其在数组中的下标,table[(n - 1) % 32]与数组中下标(n-1)/32的值使用或操作。

三)表table中值为   table[ 0 ]=0x00000001,

                                table[ 1 ]=0x00000002, 

                                ... ...

                                table[29]=0x20000000,

                                table[31]=0x80000000,   等这样的表示方式,具体的数值使用查表法加快速度。

四)最后算某值是否存在,使用与操作即可计算出。 

数据存储比如:

第一个N=30是一个随机数,则存储可以表示为:index[(30-1)/32] = index[0] = index[0] || table[(30-1)%32] /*刚开始时候初始化index[32]={0}*/

                                         =  0 || 0x20000000 = 0x20000000;

第二个N=31是一个随机数,则存储可以表示为:index[(31-1)/32] = index[0] = index[0] || table[(31-1)%32] /*第30位1,其他位为0*/

                                         =  0x20000000 || 0x40000000 = 0x60000000;

... ...

依次类推,即可。 

数据验证比如:

1. 当要查询30是否存在的时候,由于:(30-1)/32 = 0;(30-1)%32=29;我们只需要计算:index[0] & table[29] 是真还是假,就可以得出30是否存在。

2. 当要查询31是否存在的时候,由于:(31-1)/32 = 0;(31-1)%32=30;我们只需要计算:index[0] & table[30] 是真还是假,就可以得出31是否存在。

... ...

依次类推,即可。 

小结:

        通过分析此题目,首先这种思路和方法,在一定程度上用相对小的空间存储了大量的数据,节省了比较大的内存空间;在运算方面,位运算的速度相当来说效率是比较高的,因而也再一定程度上节省了时间复杂。

        总之,这种存储方式和思维方式,在一定方面能够有效的解决海量数据存储与运算。基于此题目,凡是大量数据筛选,判断是否存在等问题,我们都可以借鉴此题目的思维和方法。

8、判断点是否在多边形内的问题? 
我知道有两种方法:    
 1、累计角度法   
 过此点连接多边形的每一顶点,各相邻边角度之和为360度,则此点在多边形内。   否则为0度,在多边形外部。   
2、射线法   
过此点向任意角度发一条射线,若与多边形的各条边交点个数之和为偶数,则此点在多边形之外,否则在多边形之内。  若有交点为多边形顶点则要另选一条射线重算。      
请问哪种方法好一点(时间复杂度)?   
方法一对凹多边形可能会出现问题吧。 

 

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值