写在前面

本文的题目均来自于剑指offer中的题目,题目序号保持了书中的题目序号,由于某些题目并不适合于javascript这种语言,所以这些题目就没有写在本篇博客中,因此会出现题目序号的中断。

正文

面试题6:重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果都不含重复的数字。前序遍历序列为{1,2,4,7,3,5,6,8},中序遍历序列{4,7,2,1,5,3,8,6}。
分析:根据前序遍历和中序遍历的定义可知,前序遍历中的第一个值就是根节点的值,而在中序遍历中根节点的值是在左子树的后面右子树的前面,由于树中没有重复的值所以可以通过对中序遍历进行一遍搜索来找到这个值,从而也可以确定这棵树的左右子树。确定了左右子树后递归处理。
代码实现

function node(data, left, right){  //用来表示一棵树中的节点
  this.data = data;
  this.left = left;
  this.right = right;
  this.show = show;
}
function show(){
  return this.data;
} 

function rebuildBT(pre, mid){ //根据前序遍历和中序遍历重建这棵树并且输出头节点
  var root; //保存头结点
  var len = pre.length;
  if(len > 1){
    var data = pre[0]; //前序遍历的第一个元素即树的根节点
    for(var i = 0; i<len; i++){
      if(mid[i] === data){
        switch(i){
          case 0:
            var midtree = mid.slice(i+1);
            var pretree = pre.slice(1);
            root = new node(data, null, rebuildBT(pretree,midtree));
            break;
          case (len -1):
            var midtree = mid.slice(0,i);
            var pretree = pre.slice(1);
            root = new node(data, rebuildBT(pretree,midtree), null)
            break;
          default:
            var lfmidtree = mid.slice(0,i);
            var rgmidtree = mid.slice(i+1);
            var k = lfmidtree.length;               
            var lfpretree = pre.slice(1,k+1);
            var rgpretree = pre.slice(k+1);
            root = new node(data, rebuildBT(lfpretree,lfmidtree), rebuildBT(rgpretree,rgmidtree))
            break;
        }
      } 
    } 
  }else if(len === 1){
    var data = pre[0];
    root  = new node(data, null, null);
  }
  return root;
}

面试题7:用两个栈实现队列
题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入节点和在队列头部删除节点的功能。
分析:由于栈是一种先进后出的数据结构,且只能在栈顶来删除数据,所以本题目的实现思路是先将队列从头到尾压入栈中保存,如果要从尾部添加一个元素那么直接从栈顶压入一个元素即可,如果是要从头部删除一个元素那么将队列从栈1弹出保存到栈2,对栈2栈顶弹出一个元素再压回栈1.

面试题目8:旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组的末尾,称之为一个数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如旋转数组{3,4,5,1,2}的最小值为1.
分析:直观的方法是对整个数组进行一次遍历,找出最小值。但是这种复杂对为o(n)的方法显然不是最佳,因为我们这里没有用到旋转数组包含两个已排序的子序列的特点。对于已排序的数组我们可以用二分法来进行查找,可以将时间复杂度降低为(logn),那么这里我们也可以用相似的思路来实现。
代码实现:

function minNum(inputArr){
  //输入为旋转数组,利用二分法来查找最小元素
  var len = inputArr.length;
  var first=inputArr[0], last=inputArr[len-1], location, min;
  if(len > 2){
    location = Math.ceil(len/2)-1;
    var value = inputArr[location];
    if(value >last){//说明这个中间元素在前面的数组中,那么最小的元素它之后
      min = minNum(inputArr.slice(location));
    }else if(value <= last){//说明这个中间元素在后面的数组中,最小元素在它之前
      min = minNum(inputArr.slice(0,location+1));
    }
  }else if(len === 2){
    min = inputArr[0]>inputArr[1] ? inputArr[1] : inputArr[0];
  }else{
    min = inputArr[0];
  }
  return min;
}

面试题9:斐波那契数列
前言:如果需要多次计算相同的问题通常可以选择用递归或者循环来实现,递归是在一个函数内部调用自身,驯海则是通过设置计算值的初始值和终止条件在一个范围内重复计算。通常递归方法的代码会比较简洁,但是它也会有显著的缺点,由于每次递归都是一次函数的调用,所以递归效率较低;同时递归中有很多运算是重复的(分解的小问题存在重叠的部分),这会对性能造成影响;最重要的是递归还有可能造成调用栈的溢出。那么这个时候我们就可以适当地考虑一下用循环的方法来解决。
题目一:写一个函数,输入n求斐波那契数列的第n项。首先得知道斐波那契数列的第n项是如何定义的。定义其实就是如下面方法1函数描述。很明显使用了递归,当n很大的时候运行效率低下不说还会造成调用栈溢出,那么我么用一种循环的方法来实现一下,如方法2。

//方法1
function fn(n){
 if(n <= 0){
     return 0;
 }else if(n == 1){
     return 1
 }else{
     return fn(n-1)+fn(n-2);
 }
}

//方法2
function fn(n){
  var result, r1, r2;
  if(n<=0){
    return 0;
  }else if(n == 1){
    return 1;
  }else{
    r1 = 1;
    r2 = 0;
    for(var i = 2; i<=n; i++){
      result = r1 + r2;
      r2 = r1;
      r1 = result;
      
    }
  }
  return result;
}

 

面试题13:在O(1)时间删除链表结点。
题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该节点。
分析:在单链表中删除一个结点,直观的思路就是从链表头节点开始,顺序遍历查找要删除的节点,并在链表中删除该节点。但是这样的话时间复杂度就为o(n)。所以我们要换一种方法来做,删除一个结点是不是必须要已知这个节点之前的节点呢?答案是否定的。由于这里已知了删除节点的指针,那么我们可以方便地得到下一个节点,我们把下一个节点的内容复制到需要删除的节点上覆盖原有的内容,再把下一个节点删除,这就相当于把当前需要删除的节点删除掉了。


sushi
135 声望19 粉丝

小目标总是要有的,万一实现了呢^^