IT笔试面试回忆录

2014年,兹于毕业生找工作之际,共揽七记offer,再回首,呈以小结记忆。

1、快速找出故障机器

1.1 一组数字中,所有的数字全部出现两次,现在删除其中一个数字,找出这个数。

  • 所有数字进行异或运算,得出出现一次的数字。
1.2 现在提升1.1的难度,把题目改为偶数次中找一个出现奇数次的数字:依然是一步异或运算。

         现在继续提升难度,把题目改为偶数次中找两个出现奇数次的数字:

  • 进行一步异或运算:x or y = z;现在把z按二进制进行写开,找出其中为1的某一位,所有这一位为1的为一类,为0的为另外一类;那么x归为一类,y归为另外一类,问题就得到了转化。
 1.3 现在继续提升题目难度,把题目改为偶数次中找出现三个奇数次的数字:
  • 进行一步异或运算:x or y or z = w;现在把w按二进制进行写开,找出其中一位为0的,不妨设x与y此位同数(同为1),z异数(为0);所有这一位为1的为一类,为0的为另外一类;那么x、y为一类,z为另外一类,问题因此得到转化。
1.4 一组数字,从1到n,每个数字出现一次,现在去除其中一个,求出这个数字。
  • Idea1:所有数字加和sum,用n*(n+1)/2 - sum。
  • Idea2:循环倒位:
  • #include<iostream>
    
    using namespace std;
    
    int swap(int &a,int &b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
    
    
    int findDup(int *a,int n)
    {
        for(int i=0;i<n;i++)
        {
    	if(a[i]<i)
    	{
    	    return i;
    	}else while(a[i]>i)
    	{
    	    if(a[i]==a[a[i]])
    		return a[i];
    	    swap(a[i],a[a[i]]);
    	}
        }
    }

1.5 继续提升1.4的难度,去掉其中的两个,求出这两个数字:
  • 设缺少的数字为x和y,先求出x+y,然后将数组从(x+y)/2处分开,那么左边和右边都是缺少一个数,问题得到转化。

2、电梯调度算法(这一题始终未考,不过应该快了)

  • 电梯停在哪一层楼,能够保证这次乘坐电梯的所有乘客爬楼梯的层数之和最少?
  • 此实际上为一优化问题,如下图所示:

  • 其中,|x-i|为停靠在第x楼层,第i层所需爬楼数;Ti为第i层下来的乘客数
3、链表问题

3.1  链表交叉问题:
Idea1:Hash

Idea2:查看最后一个元素是否相同

Idea3:让一个出现环(首尾相接),另外一个判断环

3.2  链表找环问题:

  • fast = head->next;
    slow = head;
    while(fast&&fast!=slow)
    {
    	fast = fast->next->next;//走两步
    	slow = slow->next;//走一步
    }
    if(fast==slow)//都已经进环,并且两者相遇
    {
    	fast = head;
    	while(fast!=slow)
    	{
    		fast = fast->next;
    		slow = slow->next;
    	}
    }
    return fast;

4、队、栈问题:

4.1  二队仿生一栈

  • bool Push(Ele e)
    {
    	//始终保持队列P1为空,P2用来填装元素
    	P2.push(e);
    	return true;
    }
    
    bool Pop(Ele &e)
    {
    	int count = 0;
    	while(p2.deque(e)!=null)
    	{
    		p1.push(e);
    		count++;
    	}
    	while(count>0)
    	{
    		p1.deque(e);
    		p2.push(e);
    		count---;
    	}
    	return p1.deque(e);
    }
4.2  二栈仿生一队
  • bool Push(Ele e)
    {
    	//S2用来填装新元素
    	S2.push(e);
    	return true;
    }
    
    bool Deque(Ele &e)
    {
    	if(!S1.empty())
    	{
    		e = S1.Pop();
    	}else{
    		while(!S2.empty())
    		{
    			S1.Push(S2.Pop());
    		}
    		e = S1.Pop();
    	}
    	return true;
    }

5、二叉树中的一系列问题

二叉树中的一个重要思想就是递归,围绕三种遍历方法的一系列问题。

5.1  重建二叉树

以前序、中序为例:

  • void rebulid(char *pre,char *pin,int beg,int pend,int mbeg,int mend,Node * &root)
    {
    	if(pend < pbeg || mend < mbeg)
    	{
    		return;
    	}
    	root = (Node*)malloc(sizeof(Node));
    	root->data = pre[pbeg];
    	int loc = find(pin,pre[pbeg]);//这儿涉及到算法取优,用Hash是一个思路,但是在HTC北京研究院,好像对答案不满意
    	int length = loc - mbeg;
    	rebuild(pre,pin,pbeg+1,pbeg+length,mbeg,mbeg+length-1,root->left);
    	rebuild(pre,pin,pbeg+length+1,pend,mbeg+length+1,mend,root->right);
    }
5.2  二叉树中结点的最大距离,二叉树中的结点深度,树的高度,这是同一类型的问题。
  • 数据结构
    Node{
    	Node *left;
    	Node *right;
    	int lengthLeft;//左子树高度
    	int lengthRight;//右子树高度
    }
    
    int maxLength = 0;
    
    void maxLen(Node *root)
    {
    	if(root->left==null)
    		root->lengthLeft = 0;
    	if(root->right==null)
    		root->lengthRight = 0;
    	if(root->left != null)
    		maxLen(root->left);
    	if(root->right != null)
    		maxLen(root->right);
    	
    	if(root->left)
    	{
    		temp = 0;
    		temp += root->left->lengthLeft;
    		if(temp < root->left->lengthLeft)
    			temp = root->left->lengthRight;
    		root->lengthLeft = temp + 1;
    	}
    	if(root->right)
    	{
    	//变右
    	}
    	if(maxLength < root->lengthLeft + root->lengthRight)
    		maxLength = root->lengthLeft + root->lengthRight;
    }

5.3  树的线索化,余左为前驱,余右为后继

  • Node *Thread(Node *&root,Node *&first)//记录子树首结点,返回最后一个结点
    {
    	if(root->left)
    	{
    		temp = Thread(root->left,first);
    		temp->right = root;
    	}else{
    		first = root;
    	}
    	
    	if(root->rchild)
    	{
    		last = Thread(root->rchild,temp);
    		temp->left = root;
    	}else{
    		last = root;
    	}
    	return last;
    }

5.4  二叉树的共同前驱,共同父亲

  • bool lca(Node *root,Node *va,Node *vb,Node *&result)
    {
    	bool left = root->left?lca(root->left,va,vb,result):false;
    	if(result)
    		return false;
    	bool right = root->right?lca(root->right,va,vb,result):false;
    	if(result)
    		return false;
    	bool mid = (root==va)+(root==vb);
    	ret = left + mid + right;
    	if(ret==2)
    		result = root;
    	return ret;
    }
5.5  二叉搜索树改变为双向链表
  • void binary2list(Node *&root,Node *first,Node *last)//first、last为二叉搜索树进行变更之后的头结点和尾结点
    {
    	if(root->left==null)
    	{
    		first = root;
    	}else{
    		Node *temp;
    		binary2list(root->left,first,temp);
    		temp->right = root;
    		root->left = temp;
    	}
    	
    	if(root->right==null)
    	{
    		last = root;
    	}else{
    		Node *temp;
    		binary2list(root->right,temp,last);
    		temp->left = root;
    		root->right = temp;
    	}
    }

5.6  二叉树的层次遍历

  • Idea:使用一队列,轻松easy搞定,但是使用分行打印进步一样了。
  • void printLevel(Node *root)
    {
    	Queue P;
    	if(root)
    	{
    		P.enqueue(root);
    		P.enqueue(null);
    		while(!P.empty())
    		{
    			t = P.deque();
    			if(t!=null)
    			{
    				cout<<t->data<<"\t";
    				if(t->left)
    					P.enqueue(t->left);
    				if(t->right)
    					P.enqueue(t->right);
    			}else{
    				if(P.empty())
    					break;
    				else{
    					P.enqueue(null);
    					cout<<endl;
    				}
    			}
    		}
    	}
    }

6、字符串的一系列问题:

Idea:字符串常与动态规划进行结合,考察问题

6.1  最大子段和

  • int maxSum(int a[],int m)
    {
    	int max = a[0];
    	int sum = a[0];
    	for(int i = 1;i < m;i++)
    	{
    		if(sum > 0)
    		{	
    			if(sum > 0)
    				sum += a[i];
    			else
    				sum = a[i];
    			if(sum > max)
    				max = sum;
    		}
    	}
    	return max;
    }
6.2  最大子段积
  • 令F(n)表示以d[n]结尾的最大乘积,G(n)表示以d[n]结尾的最小乘积,由于负数的存在,F(n)与G(n)有可能互相转化
  • F(n+1) = max{F(n)*d[n+1],d[n+1],G(n)*d[n+1]}
  • G(n+1) = min{F(n)*d[n+1],d[n+1],G(n)*d[n+1]}
  • #include<iostream>
    
    using namespace std;
    
    double max(double a,double b,double c)
    {
        if(a>b&&a>c)
    	return a;
        if(b>c)
    	return b;
        return c;
    }
    
    double min(double a,double b,double c)
    {
        if(a<b&&a<c)
    	return a;
        if(b<c)
    	return b;
        return c;
    }
    
    int main()
    {
        double a[7] = {-2.5,4,0,3,0.5,8,-1};
        double m_max = a[0];
        double m_min = a[0];
        double value = a[0];
        double temp_max,temp_min;
    
        int n = 7; 
        for(int i=1;i<n;i++)
        {
    	temp_max = m_max;
    	temp_min = m_min;
    	m_min = min(a[i],temp_max*a[i],temp_min*a[i]);
    	m_max = max(a[i],temp_max*a[i],temp_min*a[i]);
    
    	if(m_max > value)
    	    value = m_max;
        }
    
        cout<<value<<endl;
    }
6.3  最长递增子序列

LIS[i]表示数组a中,以a[i]结尾的最长递增子序列的长度。则,LIS[i+1] = max{1,LIS[k]+1} 对于任意的a[k] < a[i+1] 且k<=i

  • int maxLength(int a[],int m)
    {
    	int max = 1;
    	for(int i =1;i<m;i++)
    	{
    		LIS[i] = 1;
    		for(int j=0;j<i;j++)
    		{
    			if(a[i] > a[j]&&LIS[i] < LIS[j]+1)
    			{
    				LIS[i] = LIS[j] + 1;
    			}
    			
    			//对于本题的求优秀操作,可以对长度为i的最小子串进行记录,以空间换时间
    			if(LIS[i] > max)
    			{
    				max = LIS[i];
    			}
    		}
    	}
    	return max;
    }

6.4  最长公共子序列,最长公共子串

首先明确一点:子序列不必要挨着,而子串必须挨着。

这是一种类型的题目,主要是对二维矩阵的填充。

对于最长公共子序列:

m[i,j] = max {m[i-1,j] , m[i,j-1] , m[i-1,j-1]+1}        当m[i,j] = m[i-1,j-1]+1时,须有a[i] == b[j]

对于最长公共子串:

m[i,j] = m[i-1,j-1] + 1 a[i] == b[j] 或者 0 a[i] != b[j]

关于字符串的问题,这篇文章讲述的比较全面:http://www.cnblogs.com/zhangchaoyang/articles/2012070.html

6.5  最长回文序列

Idea: 这一题难度系数较大,主要是依据对称性对原问题进行求解。

首先对原串插入特殊字符:‘#’,然后设id为最大回文子串的中心位置,mx为最大回文子串的边界。

  • if(mx > i)
    {
    	if(mx-i > p[j])
    		p[i] = p[j];
    	else
    		p[i] = mx - i;//p[i]>=mx - i,取最小值,之后再更新匹配
    }else
    	//i >=  mx, 则依次进行更新
7、动态规划问题

动态规划的经典问题就是背包问题,网上有关于背包问题的九讲(百度:背包问题九讲),然后在面试、笔试过程中经常还会遇见其他动态规划问题。

7.1  子数组的最大乘积,N个数组成的数组,求出任意N-1个数的最大乘积。

7.2 人人网在2014年的校招题中,考过这么一道题:

一个矩阵a[n-1][m-1],仅有0,1组成,0代表此路不通,1代表道路通畅,问从左上角到右下角的道路数。(注意仅能向右、向下走)

s[i+1][j+1] = s[i][j+1] + s[i+1][j]

若a[i][j] =0,则s[i][j] = 0

7.3  提升题目难度,搜狗2013年考过这么一题:

一个整数矩阵a[n][m],仅有正整数组成,路过一格,获取这一格上的分数,问从左上角到右下角行走两次,所能获取的最大积分,其中一格若走两次,仅获取一次积分。

答案:http://blog.youkuaiyun.com/v_july_v/article/details/10212493

8、排序算法

8.1 快速排序

  • void quick_sort(int a[],int begin,int end)//闭区间
    {
    	if(begin >= end)
    		return;
    	int right = end;
    	int left = begin;
    	temp = a[left];
    	while(left <= right)
    	{
    		while(a[right] >= temp && right >= left)
    			right--;
    		if(right >= left)
    			a[left] = a[right];
    		
    		while(a[left] <= temp && right >= left)
    			left++;
    		if(right >= left)
    			a[right] = a[left];
    	}
    	
    	a[left] = temp;
    	quick_sort(a,begin,left-1);
    	quick_sort(a,left+1,end);
    }
8.2 堆排序 

堆排序的关键在于插入和删除上,插入是把待插入元素放置为最后一个叶子节点,然后进行上调,FixUp的过程,删除是把根元素和最后一个叶子节点进行互换,删除叶子节点,然后进行下调,FixDown的过程,以小顶堆为例。

  • void MinHeapFixUp(int a[],int ele,int m)
    {
    	a[m] = ele;
    	i = (m-1)/2;
    	j = m;
    	while(i >= 0)
    	{
    		if(a[i] < a[j])
    			break;
    		else
    			swap(a[i],a[j]);
    		j = i;
    		i = (i-1)/2;
    	}
    }
    
    void MinHeapFixDown(int a[],int m)
    {
    	a[0] = a[m];
    	i = 0;j = 2*i + 1;
    	while(j < m)
    	{
    		if(a[j+1] < a[j])
    			j++;
    		if(a[i] < a[j])
    			break;
    		else
    			swap(a[i],a[j]);
    		i = j;
    		j = 2*i +1;
    	}
    }
    
    //堆化数组
    void HeapArray(int a[],int m)
    {
    	for(int i = m/2 - 1;i > = 0;i--)
    	{
    		MinHeapFixDown(a,i,m);
    	}
    }
    
    //输出有序数组
    void SortArray(int a[],int m)
    {
    	t = m;
    	while( t >= 0)
    	{
    		cout<<a[0];
    		swap(a[0],a[t]);
    		t--;
    		MinHeapFixDown(a,0,t);
    	}
    }
  • 笔试之中,提升难度会考堆排序。
9、递归问题

走出赶集网,感觉面试官问的问题,都比较有水准,有种庙小神大的感觉。

9.1链表倒置,递归程序:

  • Node *ReverseLink(Node *head,Node *&reverseHead)
    {
    	if(head->next==null)
    	{
    		reverseHead = head;
    		tail = head;
    	}else{
    		tail = reverseHead(head->next,reverseHead);
    	}
    	tail->next = head;
    	tail = head;
    	tail->next = null;
    	return tail;
    }
9.2 考虑重复字母的全排列问题(面朝搜狗,一再惋惜):

  • void fullPex(char a[],int begin,int end)
    {
    	if(begin >= end)
    	{
    		cout<<a<<endl;
    	}else{
    		for(int i = begin;i <= end;i++)
    		{
    			if(IsSwap(a,begin,i))
    			{
    				swap(a[begin],a[i]);
    				fullPex(a,begin+1,end);
    				swap(a[begin],a[i]);
    			}
    		}
    	}
    }
    
    //查看a[i..j)中是否包含a[j],以防止重复出现
    bool IsSwap(char a[],int i.int j)
    {
    	for(int k = i;k < j;k++)
    	{
    		if(a[k] == a[j])
    			return false;
    	}
    	return true;
    }
10、智力类题目

10.1 金刚坐飞机问题

乘客坐飞机按票入座,但是第一个人不坐在他自己的一号位置上,而是随便坐,问第N个乘客坐在自己的位置上的概率是多少?

这是编程之美上的一道题目,书上给出了比较复杂的解法,鄙人因此一题而获得微策略的赏识,进而杀至终面。

设第n个乘客,坐在自己位置上的概率为f(n),对于第一个乘客坐在的位置情况分情况讨论:

  • 坐在了自己的位置上:概率为1/N,则第n个乘客坐在自己位置上的概率为1;
  • 坐在了第n个乘客的位置上:概率为1/N,则第n个乘客坐在自己位置上的概率为0;
  • 坐在了n之前的位置上:概率为1/N,则第n个乘客坐在自己位置上的概率为f(n);此时相当于被坐的那个人为金刚;
  • 坐在了n之后的位置上:概率为1/N,则第n个乘客坐在自己位置上的概率为1;

10.2 诺基亚手机质量问题

两部诺基亚手机从100层的大楼上摔下,一定能摔坏,问要确切的知道从哪一层坏,需要摔多少次?

1+2+3+……+n>=100  可以推出 n=14

n部手机呢?

1+((n-1)+1)+(2(n-1)+1)+……+(m(n-1)+1)>=100

10.3 100个石子,每次仅能取1个,2个,4个,最后一个将石子拿尽的人输,问怎么能保证赢?首先拿的人输。

10.4 买票找零问题:n个人持有50元,n个人持有100元钞票,保证可以找零,有多少种排列方法?

这为一Catalan数,答案为C^n_{2n}/(n+1)。

11、大数据问题

关于大数据问题的几个思路:1,进行Hash,然后分而治之;2,用bitmap进行映射。

对于大数据问题,这个帖子讲的比较详细:http://blog.youkuaiyun.com/v_JULY_v/article/details/6279498



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值