劍指offer JavaScript實現

1.二維數組的查找

在一個二維數組中(每一個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。node

思路:選擇最左下角的那個點做爲起始點,也就是a[array.lenth][0],比它大就往右邊走col++,比它小就往上面走row--。正則表達式

function Find(target, array)
{
    const n = array.length;
    const m = array[0].length;
    let row = n-1;
    let col = 0;
     while (row >= 0 && col <= m - 1) {
        if(target<array[row][col]){
            row--;
        }else if(target>array[row][col]){
            col++;
        }else if(target=array[row][col]){
            return true;
        }
     }
}
複製代碼

2.替換空格

題目描述算法

請實現一個函數,將一個字符串中的每一個空格替換成「%20」。例如,當字符串爲We Are Happy.則通過替換以後的字符串爲We%20Are%20Happy。數組

思路:使用正則表達式bash

function replaceSpace(str)
{
    return str.replace(/\s/g, '%20');
}
複製代碼

\s’表示正則匹配字符串中的空字符,‘g’表示所有匹配。數據結構

3.輸入一個鏈表,按鏈表值從尾到頭的順序返回一個ArrayList。

思路:把鏈表值全都裝入數組中,再對數組進行reverse,鏈表的head經過.val取值,經過.next獲取下一個值app

function printListFromTailToHead(head)
{
    var arr=[];
    while(head!=null){
        arr.push(head.val);
        head=head.next;
    }
    return arr.reverse();
}
複製代碼

4.重建二叉樹

題目描述:dom

輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。函數

思路:前序遍歷是先遍歷根,因此先找出根節點,再分出左右樹,遞歸左右子樹的前序,中序 。post

function reConstructBinaryTree(pre, vin) {
    // //前序的根節點爲第一個,經過pre[0]找到中序中根節點的位置,區分左右樹
    if(pre.length==0 || vin.length==0) return null;
    var index=vin.indexOf(pre[0]);
    var left=vin.slice(0,index);//中序左子樹
    var right=vin.slice(index+1);//中序右子樹
    //slice方法在只有一個參數的狀況下,該方法返回從該參數指定位置到當前數組末尾的全部項
    return {
        val:pre[0],
        //遞歸左右子樹的前序,中序 
        left:reConstructBinaryTree(pre.slice(1,index+1),left),
        right:reConstructBinaryTree(pre.slice(index+1),right)
     }; 
}

複製代碼

5.用兩個棧實現隊列

題目描述

用兩個棧來實現一個隊列,完成隊列的Push和Pop操做。 隊列中的元素爲int類型。

注意!棧是先進後出,隊列是先進先出,所以兩個棧,一個用來push,一個用來pop(shift是移除數組中的第一個元素)

const stack=[];
function push(node)
{
    stack.push(node);
}
function pop()
{
    return stack.shift();
}
複製代碼

6.旋轉數組的最小數字

題目描述

把一個數組最開始的若干個元素搬到數組的末尾,咱們稱之爲數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的全部元素都大於0,若數組大小爲0,請返回0。

用排序的方法來作, sort()方法返回值大於1就將b排到a的前面 !! 注意是輸出元素的最小值而不是數組

function minNumberInRotateArray(rotateArray)
{
    // write code here
    rotateArray.sort(function(a,b){
        if(a<b) return -1;
        else return 1;
    });
    return rotateArray[0];
}

複製代碼

斐波那契數列 跳臺階 變態跳臺階 矩形覆蓋

你們都知道斐波那契數列,如今要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項爲0)。n<=39

思路其實就是先寫終止條件,再寫遞歸條件

function Fibonacci(n){
    if (n == 0) return 0;
    var f =[];
    f[1] =1;
    f[2] =1;
    for(var i= 3;i<=n;i++){
      f[i]=f[i-1]+f[i-2];
    }
    return f[n];
}
複製代碼

跳臺階

一隻青蛙一次能夠跳上1級臺階,也能夠跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(前後次序不一樣算不一樣的結果)。

仔細分析一下也能看出這是一個斐波拉契數列

因此能夠得出總跳法爲: f(n) = f(n-1) + f(n-2),一樣採用上一個的方法

function jumpFloor(number){
   if(number == 1)
       return 1;
    if(number == 2)
        return 2;
    var f = [];
        f[1] = 1;
        f[2] = 2;
    for(var i=3;i<=number;i++){
     f[i] = f[i-1]+f[i-2];
    }
    return f[number];
}
複製代碼

變態跳臺階

一隻青蛙一次能夠跳上1級臺階,也能夠跳上2級……它也能夠跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。

function jumpFloorII(number)
{
    if(number<=1){
        return 1;
    }
    return 2*jumpFloorII(number-1);
}
複製代碼

矩形覆蓋

咱們能夠用2* 1的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n個2* 1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?

又是相似於斐波那契數列,因此代碼幾乎同樣,終止條件+遞歸

function rectCover(number) {
   if(number == 0) return 0;
    var f =[];
        f[1]=1;
        f[2]=2;
    for(var i=3;i <= number;i++){
        f[i]=f[i-1]+f[i-2];
    }
    return f[number];
}
複製代碼

二進制中1的個數

輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。

把一個整數減去1,再和原來的整數作與運算。這樣就會讓1的位置變換,那麼一個整數的二進制表示中有多少個1,就能夠進行多少次這樣的操做。count用來計數,這個方法的話記住就行了

function NumberOf1(n){
    let count = 0;
    while (n!=0){
        n=n&(n-1);
        count++;
    }
    return count;
}
複製代碼

最大連續1的個數

題目:給定一個二進制數組, 計算其中最大連續1的個數。

思路:時間按複雜度:O(n)

設置一個計數變量 count,來計數連續出現的 1,max用來存不一樣組1的連續量。

從頭至尾遍歷,若爲 1,count++,若爲0,求max與count的最大值,count變爲0.

var findMaxConsecutiveOnes = function(nums) {
  var max = 0,
      count = 0;
  for (var i in nums) {
    if (nums[i] === 1) {
      count++;
    } else {
      max = Math.max(count, max);
      count = 0;
    }
  }
  max = Math.max(count, max);  // 最後一連串的 1 有多是最長的
  return max;
};
複製代碼

查找兩個字符串中最長的公共子串

用動態規劃來解

化成一個二維數組,分解成單個的字符去匹配每一個單個的字符,只要相同的值就相比前一個+1,不相同設爲0;要知道起始位置和字串的長度。從圖中能夠看到的紅字就是存放這個位置的最優解字串的長度,因此應該創建兩個變量去存儲其實位置的index 和最大長度 max。

function find(str1,str2){
  //建立一個二維數組
  let temp = new Array()
  let max = 0
  let index = null
  for (let i = 0; i < str1.length; i++) {
    //初始化爲二維數組
    temp[i] = new Array()
    for (let j = 0; j < str2.length; j++) {
      //比較兩個位置的值是否相等,相等就將讓temp[i][j]相對於temp[i-1][j-1]加一(前提是temp[i-1][j-1]存在)
      if(str1.charAt(i) === str2.charAt(j)){
        if(i>0&&j>0&&temp[i-1][j-1]>0){
          temp[i][j] = 1 + temp[i-1][j-1]
        } else{
          temp[i][j] = 1
        }
        //保存當前temp中最大的數字,並找到index下標
        if(max<temp[i][j]){
          max = temp[i][j]
          index = i
        }
      } else {
        temp[i][j] = 0
      }
    }
  }
  console.log("max="+max)
  console.log("index="+index)
  console.log(temp)
  console.log(str2.substr(index-1,max)) 
  //substr(開始提取字符的位置,length提取的字符數)
}
(function(){
    find("aaabbb","eaebaaabbb2")
})()
複製代碼

找出無序數組中的最長連續序列的長度

方法1:

思路:先排序,排序以後,經過比較先後元素是否相差 1 來判斷是否連續。即arr[i]+1==arr[i+1]

方法2:

思路:用相似於 hashmap 這樣的數據結構優化查詢部分,將時間複雜度下降到 O(1),用Hash把數據整理過以後。就要找連續序列了。 對於一個數,看它相鄰的數在不在hash裏,要比看hash裏的數是否相鄰,即,看到一個數,就嘗試對其左右擴張,看是否在數組中。

var longestConsecutive = function(nums) {
  nums = new Set(nums);
  let max = 0;
  let y = 0;
  nums.forEach(x => {
    // 說明x是連續序列的開頭元素
    if (!nums.has(x - 1)) {
      y = x + 1;
      while (nums.has(y)) {
        y = y + 1;
      }
      max = Math.max(max, y - x); // y - x 就是從x開始到最後有多少連續的數字
    }
  });
  return max;
};
複製代碼

洗牌算法 100個格子,10個雷,怎麼實現每一個格子有雷的機率都是1/10算法

在每次迭代時交換這個被取出的數字到原始列表的最後

1. 初始化原始數組和新數組,原始數組長度爲n(已知);

2. 從還沒處理的數組(假如還剩k個)中,隨機產生一個[0, k)之間的數字p(假設數組從0開始);

3. 從剩下的k個數中把第隨機數的位置取出;

4. 重複步驟2和3直到數字所有取完;

5. 從步驟3取出的數字序列即是一個打亂了的數列。

下面證實其隨機性,即每一個元素被放置在新數組中的第i個位置是1/n(假設數組大小是n)

Array.prototype.shuffle = function() {
    var input = this;
    for (var i = input.length-1; i >=0; i--) {
        var randomIndex = Math.floor(Math.random()*(i+1));
        var itemAtIndex = input[randomIndex];

        input[randomIndex] = input[i];
        input[i] = itemAtIndex;
    }
    return input;
}
複製代碼

二叉樹遍歷 遞歸&非遞歸

遞歸

前序:其實就是根據根左右的順序,將根打印出來,再一次遞歸左右

var preOrderRecur = function(node) {
  if(!node) {
    return
  }
  console.log(node.value)
  preOrderRecur(node.left)
  preOrderRecur(node.right)
}
複製代碼

中序:按照左根右的順序,打印出根

var inOrderRecur = function(node) {
  if(!node) {
    return
  }
  inOrderRecur(node.left)
  console.log(node.value)
  inOrderRecur(node.right)
}
複製代碼

後序:左右根

var postOrderRecur = function(node) {
  if(!node) {
    return
  }
  posOrderRecur(node.left)
  posOrderRecur(node.right)
  console.log(node.value)
}
複製代碼

非遞歸:可用棧的方式去模擬

前序

用數組來模擬棧,首先判斷根是否爲空,將根節點入棧

1.若棧爲空,則退出循環

2.棧不爲空,將棧頂元素彈出

3.若是彈出的節點的右節點不爲空則將右節點入棧,同理再判斷左節點

4.這個地方先push右節點再push左節點是由於取出來的順序相反

var preOrderUnRecur = function(node) {
  if(node) {
    var stack = []
    stack.push(node)
    while(stack.length !== 0) {
      node = stack.pop()
      console.log(node.value)
      if(node.right) {
        stack.push(node.right)
      }
      if(node.left) {
        stack.push(node.left)
      }
    }
  }
}
複製代碼

中序:左根右 先把左邊的所有push進棧裏再取出,而後再處理右邊的。

var inOrderUnRecur = function(node) {
  if(node) {
    var stack = []
    while(stack.length !== 0 || node) {
      if(node) {
        stack.push(node)
        node = node.left
      } else {
        node = stack.pop()
        console.log(node.value)
        node = node.right
      }
    }
  }
}
複製代碼

後序:使用兩個棧 這裏使用了一個新的數組記錄上次入棧/出棧的節點。思路是先把根節點和左樹推入棧,而後取出左樹,再推入右樹,取出,最後取根節點。

var postOrderUnRecur1 = function(node) {
  if(node) {
    var s1 = []
    var s2 = []
    s1.push(node)
    while(s1.length !== 0) {
      node = s1.pop()
      s2.push(node)
      if(node.left) {
        s1.push(node.left)
      }
      if(node.right) {
        s1.push(node.right)
      }
    }
    while(s2.length !== 0) {
      console.log(s2.pop().value);
    }
  }
}
複製代碼

二叉樹中和爲某一值的路徑

思路: 前序遍歷二叉樹,當訪問到某一結點時,把該結點添加到路徑上,並累加當前結點的值。若是當前結點爲葉結點而且當前路徑的和恰好等於輸入的整數,則當前的路徑符合要求,把當前路徑保存在result結果中;若是當前結點不是葉結點,彈出此結點。

function FindPath(root, expectNumber) {
    var result = [];
    if (root === null) {
        return result;
    }
    dfsFind(root, expectNumber, [], 0, result);
    return result; 

}
function dfsFind(root, expectNumber, path, currentSum, result) {
    currentSum += root.val;

    path.push(root.val);

    if (currentSum == expectNumber && root.left == null && root.right == null) {
        result.push(path.slice(0)); 
    }
    if (root.left != null) {
        dfsFind(root.left, expectNumber, path, currentSum, result);
    }

    if (root.right != null) {
        dfsFind(root.right, expectNumber, path, currentSum, result);
    }

    path.pop();
}

複製代碼

反轉鏈表

解法一:迭代

首先判斷是否爲空或者只有一個節點,是的話就不用反轉,

設置兩個指針,p 和 q,p指向head.next,q指向head.next.next。而後聲明temp作交換的臨時指針,而後就交換指針來反轉

var reverseList = function(head) {
  if (head === null || head.next === null) {  
  // 鏈表爲空或只有一個節點時,不用反轉
    return head;
  }
  var p = head.next;
  head.next = null;    // 讓本來的head變爲尾節點
  var temp;    // 臨時指針
  while (p !== null) {
    temp = p.next;
    p.next = head;
    head = p;
    p = temp;
  }
  return head;
};
複製代碼

另!!!若是是雙向鏈表的話就多加一句

head.last = next
複製代碼

解法二:遞歸:遞歸的方法就是不斷調用自身函數,函數返回的是原鏈表的尾節點=新鏈表的頭節點。新鏈表的尾節點指向null。

var reverseList = function(head) {
  if (head === null || head.next === null) {
    return head;
  }
  var new_head = reverseList(head.next);  // 反轉後的頭節點
  head.next.next = head;                 
  // 將反轉後的鏈表的尾節點與當前節點相連
  head.next = null;
  return new_head;
};
複製代碼

揹包問題&動態規劃

就是有i個物品,容量爲j的揹包,怎麼放最優。若是第i個物品不放,那麼問題就變成了V[i-1][j],若是第i個物品放,那麼問題就變成了V[i-1][j-weight[i]]。

並且若是MAX的取值是前者(也就是不放更優),也就是說v[i][j]的最優解是由V[i-1][j]演變而來的,那麼則必定有V[i-1][j]也是最優解。

公式:

V[i, j] = max(V[i-1, j-w(i)] + v(i),V[i-1,j])
複製代碼

其實已是對「全部」方案進行了比對,選了一個最優的。因此咱們能夠用表來記錄下這些子問題的解,來避免重複計算。動態規劃dp能解決的問題還有一個特性,就是最優子結構, 最優子結構的意思是一個問題的最優解方案必然是由它的子問題的最優解方案構成的(或者說演變而成的)。

堆老是一棵徹底二叉樹

爲何要爲徹底二叉樹呢?咱們知道徹底二叉樹除最後一層外,其它層的節點個數都是滿的,最後一層節點都靠左排列

最大堆就是每一個節點的值都>=其左右孩子(若是有的話)值的徹底二叉樹。最小堆即是每一個節點的值都<=其左右孩子值的徹底二叉樹。

建立堆方法1:插入法

從空堆開始,依次插入每個結點,直到全部的結點所有插入到堆爲止。

時間:O(n*log(n))

方法2:調整法

將這n個元素先順序放入一個二叉樹中造成一個徹底二叉樹,而後來調整各個結點的位置來知足最大/小堆的特性。找到最後一個結點,從它的父節點開始調整。根據性質,小的數字往上移動,大的往下沉;而後遞歸完成。

時間:O(n)

最大堆的插入

因爲須要維持徹底二叉樹的形態,須要先將要插入的結點x放在最底層的最右邊,插入後滿 足徹底二叉樹的特色;而後把x依次向上調整到合適位置知足堆的性質

時間:O(logn)。

「結點上浮」

刪除:當刪除節點的數值時

如要刪除72,先用堆中最後一個元素來替換72,再將這個元素下沉到合適位置,最後將葉子節點刪除。

   「結點下沉」    

相關文章
相關標籤/搜索