剑指offer 题目详解
面试题3:二维数组中的查找
题目: 一个二维数组,每一行按照从左到右递增,每一列按照从上到下递增,查找数组中是否存在某个数。如数组:
1 2 8 9
2 4 9 12
4 7 10 13
6 8 11 15
思路:这道题有其特殊性,从右上角或者左下角开始查找。比如是查找7,我们从右上角开始,9大于7,则排除最右边一列,减少列下标,查找13的话就增加行下标,减少一行,查找的方向是确定的,这样就容易实现了。
public static void main(String args[]) { // 测试用的例子 int a[][] = { { 1, 2, 8, 9 }, { 2, 4, 9, 12 }, { 4, 7, 10, 13 }, { 6, 8, 11, 15 } }; System.out.println(find(a, 7)); } public static boolean find(int array[][], int number) { int row=array.length; int col=array[0].length; int start=0; int end=col-1; while(start<row&&end>=0) { if(array[start][end]==number) { return true; }else if(array[start][end]<number){ start++; }else { end--; } } return false; }
2、面试题4:替换空格
题目大致为:
实现一个函数,把字符串中的每个空格替换成"%20"。
思路:这道题其实考察的是替换成新的值后,字符串变长会覆盖后面原有的值。但是c++的字符串和java是不一样的,c++是用数组生成的字符串,所以会覆盖后面的值。因此本题我们首先实现java下的要求,并增加一例,数组情况下的替换方法。
首先是字符串的形式:
public static void main(String args[]) { String s = "We are happy."; String a=replace(s); System.out.println(a); } public static String replace(String s) { StringBuffer sb=new StringBuffer(); for(int i=0;i<s.length();i++) { if(s.charAt(i)==' ') { sb.append("%20"); }else { sb.append(s.charAt(i)); } } return sb.toString(); }
然后是数组形式:
public static void main(String args[]) { String s = "We are happy. "; char[] a=s.toCharArray(); System.out.println(replace1(a)); } public static char[] replace1(char[] a) { int sum=0; for(int i=0;i<a.length;i++) { if(a[i]==' ') { sum++; } } char[] newLength=new char[a.length+sum*2]; int index=newLength.length-1; for(int i=a.length-1;i>=0;i--) { if(a[i]==' ') { newLength[index--]='0'; newLength[index--]='2'; newLength[index--]='%'; }else { newLength[index--]=a[i]; } } return newLength; }
3.面试题5:从尾到头打印链表
题目: 输入一个链表的头结点,从尾到头反过来打印每个结点的值。
用一个栈去存储当前节点的值,顺序遍历下去,最后从栈中pop中即可。
此处给出两种解法,一种递归,一种迭代。
public static void main(String args[]) { ListNode head=new ListNode(0); ListNode one=new ListNode(1); ListNode two=new ListNode(2); ListNode three=new ListNode(3); ListNode four=new ListNode(4); ListNode five=new ListNode(5); head.setNext(one); one.setNext(two); two.setNext(three); three.setNext(four); four.setNext(five); five.setNext(null); printListReverse_1(head); System.out.println(); printListReverse_2(head); System.out.println(); } public static void printListReverse_1(ListNode head) { Stack<ListNode> stack=new Stack<>(); while(head!=null) { stack.push(head); head=head.getNext(); } while(!stack.isEmpty()) { System.out.print(stack.pop().getValue()+ " "); } } public static void printListReverse_2(ListNode head) { if(head!=null) { if(head.getNext()!=null) { printListReverse_2(head.getNext()); } System.out.print(head.getValue()+", "); } }
4、面试题6:重建二叉树
题目:已知前序遍历序列和中序遍历序列,要求重建二叉树
思路:(leetcode中类似)
假设树的先序遍历是12453687,中序遍历是42516837。
这里最重要的一点就是先序遍历可以提供根的所在,而根据中序遍历的性质知道根的所在就可以将序列分为左右子树。
比如上述例子,我们知道1是根,所以根据中序遍历的结果425是左子树,而6837就是右子树。
接下来根据切出来的左右子树的长度又可以在先序便利中确定左右子树对应的子序列(先序遍历也是先左子树后右子树)。
根据这个流程,左子树的先序遍历和中序遍历分别是245和425,右子树的先序遍历和中序遍历则是3687和6837,我们重复以上方法,可以继续找到根和左右子树,直到剩下一个元素。
可以看出这是一个比较明显的递归过程,对于寻找根所对应的下标,我们可以先建立一个HashMap,以免后面需要进行线行搜索,这样每次递归中就只需要常量操作就可以完成对根的确定和左右子树的分割。
public class BinaryTreeNode { private int value; private BinaryTreeNode left; private BinaryTreeNode right; public BinaryTreeNode(int value) { this.value = value; } public BinaryTreeNode(int value, BinaryTreeNode left, BinaryTreeNode right) { this.value = value; this.left = left; this.right = right; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public BinaryTreeNode getLeft() { return left; } public void setLeft(BinaryTreeNode left) { this.left = left; } public BinaryTreeNode getRight() { return right; } public void setRight(BinaryTreeNode right) { this.right = right; } }
public class Item { public static void main(String args[]) { int preOrder[] = { 1, 2, 4, 5, 3, 6, 8, 7 }; int inOrder[] = { 4,2,5,1,6,8,3,7 }; Map<Integer,Integer> map=new HashMap<>(); for(int i=0;i<inOrder.length;i++) { map.put(inOrder[i], i); } BinaryTreeNode root = constructTree(preOrder, 0,preOrder.length-1,inOrder,0,inOrder.length-1,map); } private static BinaryTreeNode constructTree(int[] preOrder, int preStart,int preEnd,int[] inOrder,int inStart,int inEnd,Map<Integer,Integer> map) { if(preStart>preEnd||inStart>inEnd) { return null; } BinaryTreeNode root=new BinaryTreeNode(preOrder[preStart]); int index=map.get(root.getValue()); root.setLeft(constructTree(preOrder,preStart+1,index-inStart+preStart,inOrder,inStart,index-1,map)); root.setRight(constructTree(preOrder,index-inStart+preStart+1,preEnd,inOrder,index+1,inEnd,map)); return root; } }
面试题7:用两个栈实现队列
题目:用两个栈实现队列的两个函数appendTail和deleteHead。
思路:栈的特性是:后进先出,而队列的特性是:先进先出。这里使用两个栈实现队列有点负负得正的意思。栈1负责添加,而栈2负责删除。
package test; import java.util.Stack; //基于链表实现的下压堆栈 public class SQueue<T> { private Stack<T> stack1; private Stack<T> stack2; public SQueue () { this.stack1=new Stack<>(); this.stack2=new Stack<>(); } public void appendTail(T node) { stack1.push(node); } public T deleteHead() { if(stack2.isEmpty()) { if(stack1.isEmpty()) { try{ throw new Exception("队列为空"); }catch(Exception e) { e.printStackTrace(); } }else { while(!stack1.isEmpty()) { stack2.push(stack1.pop()); } } } return stack2.pop(); } }
public class Item { public static void main(String args[]) { SQueue<Integer> sq=new SQueue<>(); for(int i=0;i<5;i++) { sq.appendTail(i); } for (int i = 0; i < 5; i++) { System.out.print(sq.deleteHead() + "、"); } System.out.println(); } }
面试题8:旋转数组的最小数字
题目:一个递增排序的数组的一个旋转(把一个数组最开始的若干元素搬到数组的末尾,称之为数组的旋转),输出旋转数组的最小元素。
思路:不论怎么旋转总有一半是排序好的。我们就找排序好的那一半,二分解决。同时考虑重复的情况。
假设原数组是{1,2,3,3,3,3,3},那么旋转之后有可能是{3,3,3,3,3,1,2},或者{3,1,2,3,3,3,3},
这样的我们判断左边缘和中心的时候都是3,我们并不知道应该截掉哪一半。
解决的办法只能是对边缘移动一步,直到边缘和中间不在相等或者相遇,这就导致了会有不能切去一半的可能。
所以最坏情况就会出现每次移动一步,总共移动n此,算法的时间复杂度变成O(n)。
public static void main(String args[]) { int a[] = {3,1,2,3,3,3,3}; System.out.println(findMin(a)); } public static int findMin(int[] nums) { int left=0; int right=nums.length-1; while(left<right) { int mid=left+(right-left)/2; if(nums[mid]>nums[right]) { left=mid+1; }else if(nums[mid]<nums[left]) { right=mid; }else { right--; } } return nums[left]; }
面试题9:斐波那契数列
思路:用递归实现的过程中会出现重复计算的情况,此时,可以利用动态规划的思想,保存中间结果,这样可以避免不必要的计算。
public class Item09 { public static void main(String args[]) { int n = 3; System.out.println(fibonacci(n)); } public static int fibonacci(int n) { if (n == 0) { return 0; } else if (n == 1) { return 1; } else { //由zero和one保存中间结果 int zero = 0; int one = 1; int fN = 0; for (int i = 2; i <= n; i++) { fN = one + zero; zero = one; one = fN; } return fN; } } }
面试题10:二进制中1的个数
public class Item10 { public static void main(String args[]) { int n = 9; System.out.println("9的二进制表示中1的个数为:" + numberOf1(n)); } /** * 利用了与的操作 * @param n * @return */ public static int numberOf1(int n) { int count = 0; while (n != 0) { count++; n = (n - 1) & n; } return count; } }
还有一些相关的题目:
比如用一条语句判断一个数是不是2的整数次方。一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1,而其他所有位都是0。根据之前的分析,我们只要判断该数减一后,再跟之前的数进行与运算得到的结果是否为0,即可。
面试题11:数值的整数次方
思路:需要考虑到 exponent为负数或者为0的情况,所以用传统的for循环就不行了,因此我们提出如下解法,考虑对指数折半,这样只需要计算一半的值,若指数是奇数,则-1再折半,否则直接折半。
public class Item11 { public static void main(String args[]) { int base = 2; int exponent_1 = 9; int exponent_2 = 10; System.out.println("当exponent为奇数:" + power(base, exponent_1)); System.out.println("当exponent为偶数:" + power(base, exponent_2)); } /** * 整数次方 * * @param base底 * @param exponent指数 * @return */ public static double power(double base, int exponent) { if (exponent == 0) { return 1; } if (exponent == 1) { return base; } if (exponent >> 1 == 0) {// 偶数 int exponent_1 = exponent >> 1; double tmp = power(base, exponent_1); return tmp * tmp; } else {// 非偶数 int exponent_2 = exponent - 1;// -1后就是偶数 double tmp = power(base, exponent_2); return tmp * base;// 最后还有先开始减掉的一个base } } }
posted on 2017-03-06 17:14 Hennessy_Road 阅读(220) 评论(0) 编辑 收藏 举报