2014年,兹于毕业生找工作之际,共揽七记offer,再回首,呈以小结记忆。
1、快速找出故障机器
1.1 一组数字中,所有的数字全部出现两次,现在删除其中一个数字,找出这个数。
- 所有数字进行异或运算,得出出现一次的数字。
现在继续提升难度,把题目改为偶数次中找两个出现奇数次的数字:
- 进行一步异或运算:x or y = z;现在把z按二进制进行写开,找出其中为1的某一位,所有这一位为1的为一类,为0的为另外一类;那么x归为一类,y归为另外一类,问题就得到了转化。
- 进行一步异或运算:x or y or z = w;现在把w按二进制进行写开,找出其中一位为0的,不妨设x与y此位同数(同为1),z异数(为0);所有这一位为1的为一类,为0的为另外一类;那么x、y为一类,z为另外一类,问题因此得到转化。
- 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.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); }
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); }
数据结构 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; }
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; }
- 令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; }
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.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); }
堆排序的关键在于插入和删除上,插入是把待插入元素放置为最后一个叶子节点,然后进行上调,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.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; }
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.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