您的位置:首页 > Web前端

剑指Offer试题总结

2013-11-27 18:47 274 查看
此篇文章是记录《剑指Offer》书中觉得有启发性的算法题和相关考题,以后面试前再拿来扫一遍,记录贴。

-------------------------------------------------我是邪恶的昏割线------------------------------------------------------

二维数组查找问题:一个二维数组的每一行是递增序列,每一列也是递增序列,要求判断一个数A是否在该数组中?

为了缩小比较范围,希望比较的时候能舍弃尽量多的无用数据,如果从右上角数B开始比较,若A大于B,则B所处的行被淘汰,如果A小于B,则B所处的列被淘汰。

将数组中的空格替换为%20,假设字符串后内存足够?

如果正序匹配空格并替换的话,其后的字符每次都要后移,时间复杂度为O(n^2)。Another Way,首先遍历数组,统计出空格数量N,这样替换后的数组大小比之前多出了2N。然后从数组末尾开始,如果当前字符不是空格,则一直往后移,如果是空格,则替换即可,这样时间复杂度为O(n)。

有两个有序数组A1和A2,A1后内存足够多,求将A2加入到A1中,结果仍为有序?

从A1的末尾,A2的末尾,开始比较,假设A2当前比较的数a2,A1当前比较的数a1,如果a1大于a2,则a1一直往后移,否则先放入a2。这样,A1中的每个数只移动了一次。

从尾到头打印链表?

这个用栈做个中间存储就行了

重建二叉树:给定前序遍历序列和中序遍历序列,构造出此二叉树?

前序遍历确定根节点,然后查找中序序列中该根节点的位置,从而分离出左子树和右子树,然后就是递归了。

代码如下:

/**
* 给定二叉树的前序遍历和中序遍历,重构出此二叉树
*
* @author
*
*/

class Node{
int value;
Node leftChild;
Node rightChild;

@Override
public String toString() {
// TODO Auto-generated method stub
return "value:" + value
+" leftchild value:"
+ (leftChild == null ? "null" : leftChild.value)
+"rightchild value:"
+ (rightChild == null ? "null" : rightChild.value);
}
}

public class RebuildBinaryTree {

/**
* 给定前序遍历序列和中序遍历序列,和各自的搜索范围,确定当前(子)树的根节点
*
* @param preList    前序遍历序列
* @param start1          前序序列开始下标
* @param end1              前序序列结束下标
* @param midList    中序遍历序列
* @param start2          中序序列开始下标
* @param end2              中序序列结束下标
*
* @return           根节点
* @throws Exception
*/
private static Node getRoot(int[] preList, int start1, int end1,
int[] midList, int start2, int end2) {
//参数合法性检测
if(start1 > end1 || start2 > end2 || preList.length < end1
|| midList.length < end2){
return null;
}
//根节点为前序序列中搜索范围内的第一个节点
Node root = new Node();
root.value = preList[start1];
//将根节点带入中序序列中,从而分割出左子树和右子树,返回分割点的位置下标
int divideIndex = getIndex(midList, root.value, start2, end2);
//计算左子树长度
int offset = divideIndex - start2 -1;
//分别获取左右子树的根节点
root.leftChild = getRoot(preList, start1+1, start1 + offset + 1,
midList, start2, start2 + offset);
root.rightChild = getRoot(preList, start1 + offset + 2, end1,
midList, divideIndex + 1, end2);
System.out.println(root);
return root;
}

/**
* 从指定数组中找出目标的位置
*
* @param array    指定数组
* @param des      查找对象
* @param start    开始下标
* @param end      结束下标
* @return
*/
private static int getIndex(int[] array, int des, int start, int end){
int len = array.length;
//参数合法性检测
if(start > end || len < end)
return -1;
for(int i = 0; i < len; i++){
if(des == array[i])
return i;
}
return -1;
}

public static void main(String[] agrs){

int[] preList = {1,2,4,7,3,5,6,8,};
int[] midList = {4,7,2,1,5,3,8,6,};
Node root = getRoot(preList, 0, preList.length-1,
midList, 0, midList.length-1);
}

}

旋转数组的最小数字:把一个数组的最开始的若干元素搬到数组的末尾,称之为数组的旋转。输入一个递增数组的旋转,要求输出最小元素。如{3,4,5,1,2}是{1,2,3,4,5,}的一个旋转,则输出1?

遍历数组的时间复杂度为O(n),Another way,采用二分查找,但是要注意元素相等的情况。

代码如下:

/**
* 旋转数组:把数组最开始的若干个元素搬到数组的末尾。
* 现输入一个递增数组的旋转,要求输出最小元素。
* 如输入{3,4,5,1,2},输出1
*
* @author
*
*/

public class RotateArray {

/**
* 返回旋转数组的最小元素
*
* @param numbers
* @return
* @throws Exception
*/
public static int getMin(int[] numbers) throws Exception{
if(numbers == null)
throw new Exception("ERROR: Array cannot be null.");
if(numbers.length == 0)
throw new Exception("ERROR: Array length cannot be 0.");
//定义起止下标
int start = 0;
int end = numbers.length - 1;
//定义二分法的中间下标,初始化为起始下标
int mid = start;
while(numbers[start] >= numbers[end]){
if(end - start == 1){
mid = end;
break;
}
mid = (start + end) / 2;
//如果起始、终止和中间下标对应的值都相等,则顺序查找
if(numbers[start] == numbers[mid]
&& numbers[mid] == numbers[end]){
int result = numbers[start];
for(int i = start+1; i < end; i++){
if(numbers[i] < result)
result = numbers[i];
}
return result;
}
//缩短查找范围
if(numbers[start] <= numbers[mid])
start = mid;
else if(numbers[end] >= numbers[mid])
end = mid;
}
return numbers[mid];
}

public static void main(String[] agrs) throws Exception{
int[] numbers = {3,4,5,6,7,8,9,0,1};
System.out.println(getMin(numbers));
}

}

*递归与循环:递归代码简洁,但大多数情况下,不如循环容易理解,效率普遍不如循环,而且深层递归会导致内存栈溢出等情况。

一只青蛙一次可以跳上一级台阶,也可以跳上2级台阶,求该青蛙跳上一个n级台阶有多少种跳法?

其实上是一个斐波拉契数列。设跳上n级台阶对应的跳法数量为函数f(n),如果第一次跳上1级台阶,则后n-1级台阶的跳法数量为f(n-1);如果第一次跳上2级台阶,则后n-2级台阶的跳法数量为f(n-2)。所以,f(n) = f(n-2) + f(n-1),即斐波拉契数列。

青蛙跳台变种:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶,······,也可以跳上n级台阶,求该青蛙跳上一个n级台阶有多少种跳法?

同上f(n),如果第一次跳1级台阶,则后面有f(n-1)种跳法,如果第一次跳2级台阶,则后面有f(n-2)种跳法,······,如果第一次跳n级台阶,则后面有f(0)种跳法,即f(n) = f(0)+f(1)+...+f(n-1),而f(n-1)=f(fn-2)+...+f(0),带入有f(n)=2*f(n-1)。over。

在Excel中,用A表示第1列,B表示第2列,···,Z表示第26列,AA表示第27列,AB表示第28列,以此类推。请写出一个函数,输入用字母表示的列号编码,输出它是第几列?

其实就是把26进制的数转换成10进制

求二进制中1的个数:输入一个十进制整数,输出该数二进制表示中1的个数?

方法1:将该整数先与1做与运算,因为1除了最后一位是1,其余位都是0,所以结果如果为1,表示整数的最后一位为1,如果结果为0,表示整数的最后一位为0.然后将1左移一位,此时只有倒数第二位为1,其余都为0,此时与整数做交运算,则可以知道整数的倒数第二位是否为1,依次类推。

代码如下:

public static int method1(int number){
int count = 0;
int flag = 1;
while(flag > 0){
if((number&flag) != 0){
count++;
}
flag = flag<<1;
}
return count;
}


方法2:整数减一后,从二进制上解释,即是整数最后一个1变成0,后面的全变成1,而前面的不变。此时将原整数与该整数做与运算,则会将整数最后一个1变成0,其余位都不变。这样,只要整数不为0,迭代几次,就表示有几个1.即:N&(N-1)的结果是将N的最后一个1变为0.

代码如下:

public static int method2(int number){
int count = 0;
while(number > 0){
count++;
number = number & (number - 1);
}
return count;
}

判断一个整数是不是2的整数次方2?

因为2的整数次方的二进制只有1位为1,其他均为0,所以可以按照上面方法2的思想,将这个整数减一再做与运算,如果结果为0,则是2的整数次方。

输入两个整数m和n,计算需要改变m的二进制中的几位,才能与n相等?

既然要改变,说明之前不相等,所以可以将m与n做异或运算,这样结果中1的个数即是m需要改变的位数,然后按照上面的思想,计算1的个数即可。

*double和float等小数,在做比较的时候,不能完全用==比较,因为有存储的误差存在,需要用差值是否小于0.00...1这样比较。

实现乘方函数Power(double base, int exp),不用考虑大数问题?

首先,exp是否为0,是正是负,这些边界条件需要考虑周全。其次,在效率上,如果指数为偶数,乘方可以转换成其1半的平方的思想。这样可以减少乘法的次数。

打印1到最大的n位数,打印1到最大的3位数,即1到999?

考虑到大数问题,除了可以用字符串模拟加法外,因为结果是每位上0-9的排列,所以用递归更简单。

代码如下:

/**
* 打印1到最大的n位数,如打印1到最大的3位数:
* 即打印从1到999
*
* @author
*
*/

public class Print1ToMax {

/**
* 入口函数
*
* @param n    最大的n位数
*/
public static void print1ToMax(int n){
if(n <= 0)
return;
char[] number = new char
;
for(int i = 0; i < 10; i++){
number[0] = (char) (i + '0');
print1ToMaxRecursively(number, n, 0);
}
}

/**
* 迭代函数,如果已到最高位了,则打印;否则继续
*
* @param number
* @param length
* @param index
*/
public static void print1ToMaxRecursively(char[] number, int length, int index){
//此时已经设置到最高位了,打印
if(index == length - 1){
printNumber(number);
return;
}
for(int i = 0; i < 10; i++){
number[index + 1] = (char) (i + '0');
print1ToMaxRecursively(number, length, index + 1);
}
}

/**
* 从第一个非零数字开始打印
*
* @param number
*/
public static void printNumber(char[] number){
boolean flag = true;
for(char num : number){
if(flag && num != '0'){
flag = false;
}
if(!flag)
System.out.print(num);
}
System.out.println();
}

public static void main(String[] args) throws Exception{
print1ToMax(1);
}
}

给定单链表的头指针和一个结点指针,需要在O(1)时间内删除则个结点指针?

如果遍历的话,复杂度为O(n);可以将该结点的下一个结点,假设为next,复制到该结点,然后再将该结点指向Next的next,最后删除next即可。需要考虑到边界情况,如结点指针为尾结点,单链表节点数为1等情况。

调整数组顺序,使奇数位于偶数前面?

维护两个指针p1,p2,分别指向数组的头和尾,p1只向后移动,p2只向前移动,一旦p1指向的值为偶数,p2指向的值为奇数,则调换两个值,直到p1和p2相遇。当然,这里如果想要增强代码的可扩展性,可以将判断是否该交换值的那个条件抽象成一个函数。

链表中倒数第K个结点:输出单链表中倒数第K个结点?

维护两个指针,第一个指针先从头指针向后移动K-1个结点,然后两个指针同时向后移动,当第一个指针到末尾的时候,此时第二个指针指向的结点即为倒数第K个结点,同时注意代码的鲁棒性,考虑参数的边界情况,代码如下:

/**
* 输出单链表中倒数第K个结点,注意边界情况
*
* @author
*
*/

class Node{
int value;
Node next;
public Node(int value){
this.value = value;
}
}

public class FindKthToTail {

public static Node getKthNode(Node head, int k) throws Exception{
//边界情况,如果链表为空或倒数位数小于1时,直接返回NULL
if(head == null || k < 1)
return null;
Node first = head;
Node second = head;
for(int i = 0; i < k-1; i++){
if(first.next != null){
first = first.next;
}else {
return null;
}
}
while(first.next != null){
first = first.next;
second =second.next;
}
return second;
}

}

反转链表:输入一个单链表的头结点,反转该链表并输出反转后链表的头结点?

这个主要注意代码的鲁棒性即可,没有什么技巧性,非递归和递归的代码如下:

/**
* 反转链表(递归)
*
* @param nowNode    当前处理的结点,初始为头结点
* @param pre    当前处理结点的前一结点,初始为null
*
* @return    反转链表的头结点
*/
public static Node reverseRecursive(Node nowNode, Node preNode){
if(nowNode == null)
return preNode;
Node next = nowNode.next;
//下一节点为前结点
nowNode.next = preNode;
return reverseRecursive(next, nowNode);
}

/**
* 反转链表(非递归)
*
* @param head	初始链表的头结点
* @return
* @throws Exception
*/
public static Node reverse(Node head){
//如果链表为空会链表为单节点,则直接返回即可
if(head == null || head.next == null)
return head;
//标记反转过程中的新的头结点
Node newHead = null;
//当前反转的结点
Node nowNode = head;
//当前结点的前一个结点
Node preNode = null;

//如果当前节点不为空,则让下一节点作为新的头结点,并更新当前结点等
while(nowNode != null){
Node next = nowNode.next;
if(next != null)
newHead = next;

nowNode.next = preNode;
preNode = nowNode;
nowNode = next;
}

return newHead;
}

合并有序链表:输入两个递增排序的列表,合并这两个链表使新链表中的结点仍是递增排序的?

这个没啥技巧性,注意鲁棒性,代码如下:

/**
* 合并两个排好序的单链表,新链表仍为有序,<升序>
*
* @param head1
* @param head2
* @return
*/
public static Node merge(Node head1, Node head2){
if(head1 == null)
return head2;
if(head2 == null)
return head1;

Node newHead = null;
//每次选择较小的头结点加入新链表即可
if(head1.value < head2.value){
newHead = head1;
newHead.next = merge(head1.next, head2);
}else{
newHead = head2;
newHead.next = merge(head1, head2.next);
}

return newHead;

}
树的子结构:输入两颗二叉树A和B,判断B是不是A的子结构?

这个也没啥技巧性,从A中找到和B根节点相等的结点,然后依次判断左右结点是否相同,直到叶节点,

代码如下:

/**
* 判断二叉树B是不是二叉树A的子树
*
* @author
*
*/

class BinaryNode{
int value;
BinaryNode leftNode;
BinaryNode rightNode;

public BinaryNode(int value){
this.value = value;
}
}

public class SubTree {

private static boolean Tree1HasTree2(BinaryNode head1, BinaryNode head2){
//先判断子树,否则当两者都为Null的时候,实际是子树的,可是却返回false了
if(head2 == null)
return true;
if(head1 == null)
return false;
if(head1.value != head2.value)
return false;
return (Tree1HasTree2(head1.leftNode, head2.leftNode) &&
Tree1HasTree2(head1.rightNode, head2.rightNode));
}

public static boolean isSubTree(BinaryNode head1, BinaryNode head2){
boolean result = false;

if(head1 != null && head2 != null){
if(head1.value == head2.value)
result = Tree1HasTree2(head1, head2);
//继续找相同的结点
if(!result)
result = isSubTree(head1.leftNode, head2);
if(!result)
result = isSubTree(head1.rightNode, head2);
}

return result;
}
二叉树的镜像:非叶结点的左右子结点互换?
/**
* 树的镜像:非叶结点的左右子节点互换(递归)
*
* @param head
*/
public static void mirrorTreeRecursive(BinaryNode head){
//如果二叉树为空或只有一个结点,则不用变换了
if(head == null)
return;
if(head.leftNode == null && head.rightNode == null)
return;

//交换左右子节点
BinaryNode tmp = head.leftNode;
head.leftNode = head.rightNode;
head.rightNode = tmp;

if(head.leftNode != null)
mirrorTree(head.leftNode);
if(head.rightNode != null)
mirrorTree(head.rightNode);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法