最近金九银十刚刚过去,可谓是有人欢喜有人愁,不过,今天的任务是跑不掉的,走到最后的才是胜利者,下面记录几个面试题
链表之快慢法
实现一个算法,找出单向链表中倒数第k个节点
太直观的解法我们就不去考虑了,我们考虑一下迭代法吧,使用两个指针P1和P2,将它们指向链表中相距K个结点的两个节点,具体做法是先将P1 和P2 指向链表首结点,然后将P2向前移动K个节点,然后以相同的速度移动这两个指针,P2 会在移动Length-k步后抵达链表尾结点,此时的P1 即指向倒数第K个节点
部分伪代码,时间O(n),空间O(1)
LinkedlistNode nthTolast(LinkedListNode head,int k){
if(k<=0)
return null;
LinkedlistNode p1=head;
LinkedlistNode p2=head;
for(int i=0;i<k-1;i++){ //P2向前移动K个结点
if(p2==null)
return null;
p2=p2.next;
}
if(p2==null)
return null;
while(p2.next!=null){ //P1和P2同时移动
p1=p1.next;
p2=p2.next;
}
return p1;
}
给定一个有环链表,实现一个算法返回环路的开头结点
这个题目就是典型的快慢法了,当在一个环形道路上,一辆车速度为a,另一辆车速度为2a,最终两辆车会碰到一起。(难道就不会出现刚好越过的情况吗??)别急,假设2a的车真的越过了a车,且a车处于位置i,那么2a车处于位置i+1,那么,在前一步,a车就处于位置i-1,2a车也处于位置i-1处,故还是碰到了一起。
有以上信息可以得出,碰撞点距离原点为K,则现在只需要再次将a车放在head,2a在碰撞点,都以a的速度前进,那么下次两车碰撞点就会在原点,此时返回原点指针即可。
伪代码
LinkedListNode FindBeginning(LinkedListNode head){
LinkedListNode slow=head;
LinkedListNode fast=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(slow==fast){ //碰撞
break;
}
}
if(fast==null||fast.next==null){ //错误检查,没有环路的情况
return null;
}
slow=head; //slow指向head,fast在碰撞点,
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return fast;
}
树
实现一个函数,检查二叉树是否平衡。在这个问题中,平衡树的定义如下:任意一个结点,其两棵子树的高度差不超过1.
从根节点递归向下检查每颗子树的高度。
伪代码
public static int checkHeight(TreeNode root){
if(root==null)
return 0;
int leftHeight=checkHeight(root.left);
if(leftHeight==-1)
return -1;
int rightHeight=checkHeight(root.right);
if(rightHeight==-1)
return -1;
int heightDiff=leftHeight-rightHeight;
if(Math.abs(heightDiff)>1)
return -1;
else
return Math.max(leftHeight,rightHeight)+1;
}
public static boolean isBalanced(TreeNode root){
if(checkHeight(root)==-1)
return false;
else
return true;
}
你有两颗非常大的二叉树:T1,有几百万个结点;T2,有几百万个结点。设计一个算法判断T2是否为T1的子树。
如果T1有这么一个结点n,其子树与T2一摸一样,则T2为T1的子树。也就是说,从结点n处把树砍断,得到的树与T2完全相同
解法一:如果结点比较少,自然使用字符串序列去进行匹配来的简单轻松,但是这里有几百万个结点,所以恐怕得另谋它法
解法二:搜遍较大的T1,每当T1的某个结点与T2的根节点匹配时,就调用treeMatch.treeMatch方法会比较两棵子树,检查是否相同,这个方法算是解法一的一个优化了,时间复杂度为O(n+k*m),n为T1的结点数,m为T2的结点数,k为T2根节点在T1中出现的次数。
伪代码
boolean containsTree(TreeNode t1,TreeNode t2){
if (t2==null) //t2为空,自然是t1的子树
return true;
return subTree(t1,t2);
}
boolean subTree(TreeNode r1,TreeNode r2){
if(r1==null)
return false;
if(r1.data==r2.data){
if(matchTree(r1,r2))
return true;
}
return (subTree(r1.left,r2)||subTree(r1.right,r2));
}
boolean matchTree(TreeNode r1,TreeNode r2){
if(r2==null&&r1==null) //二者都为空,则互为子树
return true;
if(r1==null||r2==null)
return false;
if(r1.data!=r2.data)
return false;
return(matchTree(r1.left,r2.left)&&matchTree(r1.right,r2,right));
}
位操作
编写一个函数,确定需要改变几位,才能够将整数A转换成整数B
看似十分复杂,实际上只需要计算出两个数之间有哪些位不同,很简单使用异或操作(^)。
两种办法,第一个是从C的右边开始检测,第二个是从C的左边开始检测
int bitSwap(int a,int b){
int count=0;
for(int c=a^b;c!=0;c=c>>1){
count+=c&1;
}
return count;
}
或者
int bitSwap(int a,int b){
int count=0;
for(int c=a^b;c!=0;c=c&(c-1)){
count++;
}
return count;
}
编写程序,交换某个整数的奇数位和偶数位(即位0与位1交换,位2与位3交换,。。。),使用指令越少越好
此题简单的方法就不讨论了,我们可以先将奇数位(偶数位)提出来统一移动一位,然后将二者的结构进行或运算即可。
提取奇数位:10101010(即0xAA)
int Swap(int x){
return (((x&0xaaaaaaaa)>>1)|((x&0x55555555)<<1));
数组A包含0到n的所有整数,但其中缺少一个。数组A的元素都可以用二进制表示,唯一可用的访问操作是“从A[i]取出第j位数据”,该操作的时间复杂度为常数。请编写代码找出那个缺失的整数。时间复杂度要求为O(n)
有个类似的题目,只需要求和然后与完整的序列和相减,即可求出缺失的那个数,但此题也是可以这么做的,只是时间复杂度达不到要求。故而采取另外一种方式
解法:我们将眼光聚集在完整数组A中 每个数的最低位,可以发现统计最低位的个数,若0的个数=1的个数,则为偶数个,则若0的个数>1的个数,则为奇数个。换言之,缺少一个整数的数组A中,若最低位0的个数<=1的个数,则缺少的那个整数必为偶数,即它的最低位只能为0,假设是奇数,那么数组A就是一个错误的数组;若最低位0的个数>1的个数,则缺少的那个整数最低位只能为1,因为完整数组A中最低位0的个数和1的个数相差不会超过1,所以最低位只能为1.
有了上面的结论,现在我们只需要通过逐位的筛选,最后将最低位到最高位组合起来,即可求得我们缺少的那个数了。
小结
Java的面试题是离不开数据结构的,说到底数据结构这种”高级编程语言“,是值得我们一生都去学习和研究的。