【Leetcode 作題學算法週刊】第五期

首發於微信公衆號《前端成長記》,寫於 2019.12.06javascript

背景

本文記錄刷題過程當中的整個思考過程,以供參考。主要內容涵蓋:前端

  • 題目分析設想
  • 編寫代碼驗證
  • 查閱他人解法
  • 思考總結

目錄

Easy

100.相同的樹

題目地址java

題目描述

給定兩個二叉樹,編寫一個函數來檢驗它們是否相同。node

若是兩個樹在結構上相同,而且節點具備相同的值,則認爲它們是相同的。算法

示例:數組

輸入:       1         1
          / \       / \
         2   3     2   3

        [1,2,3],   [1,2,3]

輸出: true

輸入:      1          1
          /           \
         2             2

        [1,2],     [1,null,2]

輸出: false

輸入:       1         1
          / \       / \
         2   1     1   2

        [1,2,1],   [1,1,2]

輸出: false

題目分析設想

題目直接說了是二叉樹,而二叉樹的遍歷方式有兩種:深度優先和廣度優先,我就從這兩個思路來做答。微信

編寫代碼驗證

Ⅰ.深度優先函數

代碼:優化

/**
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {boolean}
 */
var isSameTree = function(p, q) {
    if (p === null && q === null) return true
    if (p === null || q === null) return false

    if (p.val !== q.val) return false

    return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
};

結果:code

  • 57/57 cases passed (52 ms)
  • Your runtime beats 98.81 % of javascript submissions
  • Your memory usage beats 16.66 % of javascript submissions (33.8 MB)
  • 時間複雜度 O(n)n 爲節點個數

Ⅱ.廣度優先

代碼:

/**
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {boolean}
 */
var isSameTree = function(p, q) {
    if (p === null && q === null) return true
    if (p === null || q === null) return false

    let pQ =[p] // 左側比較隊列
    let qQ =[q] // 右側比較隊列

    let res = true

    while(true) {
        if (!pQ.length || !qQ.length) {
            res = pQ.length === qQ.length
            break
        }
        // 當前比較節點
        let curP = pQ.shift()
        let curQ = qQ.shift()
        if ((curP && !curQ) || (!curP && curQ) || (curP && curQ && curP.val !== curQ.val)) {
            res = false
            break
        } else {
            let pL = curP ? curP.left : null
            let pR = curP ? curP.right : null
            if (pL || pR) { // 至少一個存在纔有意義
                pQ.push(pL, pR) // 依次推入比較數組,實際上就是廣度優先
            }
            let qL = curQ ? curQ.left : null
            let qR = curQ ? curQ.right : null
            if (qL || qR) { // 至少一個存在纔有意義
                qQ.push(qL, qR) // 依次推入比較數組,實際上就是廣度優先
            }
        }
    }

    return res
};

結果:

  • 57/57 cases passed (64 ms)
  • Your runtime beats 73.27 % of javascript submissions
  • Your memory usage beats 15.53 % of javascript submissions (33.8 MB)
  • 時間複雜度 O(n)n 爲節點個數

查閱他人解法

思路基本上都是這兩種,未發現方向不一樣的解法。

思考總結

通常碰到二叉樹的題,要麼就深度遍歷,要麼就廣度遍歷。深度優先,也叫先序遍歷。

101.對稱二叉樹

題目地址

題目描述

給定一個二叉樹,檢查它是不是鏡像對稱的。

例如,二叉樹 [1,2,2,3,4,4,3] 是對稱的。

示例:

1
   / \
  2   2
 / \ / \
3  4 4  3

可是下面這個 [1,2,2,null,3,null,3] 則不是鏡像對稱的:

1
   / \
  2   2
   \   \
   3    3

說明:

若是你能夠運用遞歸和迭代兩種方法解決這個問題,會很加分。

題目分析設想

仍是一道二叉樹的題,因此常規思路就是遍歷操做,深度優先或廣度優先均可。鏡像對稱能夠觀察到很明顯的特色是有相同的根節點值,且每一個樹的右子樹與另外一個樹的左字數對稱相等。深度優先的方式,其實就是遞歸的思路,符合題目的說明。

編寫代碼驗證

Ⅰ.深度優先

代碼:

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isSymmetric = function(root) {
    function isMirror (l, r) {
        if (l === null && r === null) return true
        if (l === null || r === null) return false

        return l.val === r.val && isMirror(l.left, r.right) && isMirror(l.right, r.left)
    }
    return isMirror(root, root)
};

結果:

  • 195/195 cases passed (68 ms)
  • Your runtime beats 87.74 % of javascript submissions
  • Your memory usage beats 41.48 % of javascript submissions (35.5 MB)
  • 時間複雜度 O(n)n 爲節點個數

Ⅱ.廣度優先

代碼:

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isSymmetric = function(root) {
    if (root === null) return true
    // 初始隊列
    let q = [root.left, root.right]
    // 依次將同級push進隊列,每次取兩個對稱節點進行判斷
    while(q.length) {
        let l = q.shift()
        let r = q.shift()
        if (l === null && r === null) continue
        if (l === null || r === null) return false
        if (l.val !== r.val) return false

        q.push(l.left, r.right, l.right, r.left)
    }
    return true
};

結果:

  • 195/195 cases passed (64 ms)
  • Your runtime beats 94.88 % of javascript submissions
  • Your memory usage beats 28.3 % of javascript submissions (35.6 MB)
  • 時間複雜度 O(n)n 爲節點個數

查閱他人解法

看到一個有意思的思路,將樹按照左中右的順序輸入到數組,加上層數,該數組也是對稱的。

Ⅰ.左中右順序輸出數組

代碼:

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isSymmetric = function(root) {
    if (root === null) return true
    // 輸出數組
    let arr = []
    search(arr, root, 1);
    // 入參分別爲輸出,節點和層級
    function search(output, n, k) {
        if (n.left !== null) {
            search(output, n.left, k+1)
        }

        if (n.right !== null) {
            search(output, n.right, k + 1);
        }
    }
     //判斷是否對稱
     let i = 0, j = arr.length - 1
     while (i < j) {
         if (arr[i] != arr[j]) {
             return false
         }
         i++
         j--
     }
     return true
};

結果:

  • 195/195 cases passed (72 ms)
  • Your runtime beats 76.3 % of javascript submissions
  • Your memory usage beats 6.11 % of javascript submissions (36.3 MB)
  • 時間複雜度 O(n)n 爲節點個數

思考總結

這道題的大體解法都是遍歷節點或者利用隊列,只是在遞歸的細節上會有些差別。左中右輸出數組的思路很清奇,雖然效率明顯會更低下,可是不失爲一種思路。

104.二叉樹的最大深度

題目地址

題目描述

給定一個二叉樹,找出其最大深度。

二叉樹的深度爲根節點到最遠葉子節點的最長路徑上的節點數。

說明: 葉子節點是指沒有子節點的節點。

示例:

給定二叉樹 [3,9,20,null,null,15,7]

3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

題目分析設想

這道題最基本的思路就是計算出每條子節點的深度,再進行比較。爲了提高效率,能夠增長同級比對,去除不多是最長節點的葉節點計算。

因此這裏我就用如下幾種思路來實現深度優先算法。

  • 遞歸,直接獲取子樹最大高度加 1
  • 利用隊列,求深度轉化爲求有多少層

編寫代碼驗證

Ⅰ.遞歸

代碼:

/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if (root === null) return 0
    // 左側子樹的最大高度
    let l = maxDepth(root.left)
    // 右側子樹的最大高度
    let r = maxDepth(root.right)
    return Math.max(l, r) + 1
};

結果:

  • 39/39 cases passed (60 ms)
  • Your runtime beats 99 % of javascript submissions
  • Your memory usage beats 45.77 % of javascript submissions (37.1 MB)
  • 時間複雜度 O(n)n 爲節點個數

Ⅱ.利用隊列

代碼:

/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if (root === null) return 0
    // 隊列
    let q = [root]
    let dep = 0
    while(q.length) {
        let size = q.length
        dep++
        while(size > 0) {
            let node = q.shift()
            if (node.left !== null) q.push(node.left)
            if (node.right !== null) q.push(node.right)
            size--
        }
    }
    return dep
};

結果:

  • 39/39 cases passed (68 ms)
  • Your runtime beats 91.33 % of javascript submissions
  • Your memory usage beats 30.1 % of javascript submissions (37.2 MB)
  • 時間複雜度 O(n)n 爲節點個數

查閱他人解法

這裏看到一個用棧的角度來實現的,取棧高度的最大值,其餘的基本都是循環的細節差別,大致思路一致。

Ⅰ.利用棧

代碼:

/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if (root === null) return 0
    // 棧
    let s = [{
        node: root,
        dep: 1
    }]
    let dep = 0

    while(s.length) {
        // 先進後出
        var cur = s.pop()
        if (cur.node !== null) {
            let curDep = cur.dep
            dep = Math.max(dep, curDep)
            if (cur.node.left !== null) s.push({node: cur.node.left, dep: curDep + 1})
            if (cur.node.right !== null) s.push({node: cur.node.right, dep: curDep + 1})
        }
    }
    return dep
};

結果:

  • 39/39 cases passed (72 ms)
  • Your runtime beats 81.41 % of javascript submissions
  • Your memory usage beats 66.6 % of javascript submissions (37 MB)
  • 時間複雜度 O(n)n 爲節點個數

思考總結

二叉樹的操做,通常就是深度優先和廣度優先,因此基本上就朝這兩個方向上去解,而後進行優化就能夠了。

107.二叉樹的層次遍歷II

題目地址

題目描述

給定一個二叉樹,返回其節點值自底向上的層次遍歷。 (即按從葉子節點所在層到根節點所在的層,逐層從左向右遍歷)

例如:

給定二叉樹 [3,9,20,null,null,15,7],

3
   / \
  9  20
    /  \
   15   7

返回其自底向上的層次遍歷爲:

[
  [15,7],
  [9,20],
  [3]
]

題目分析設想

這道題在我看來仍是兩種方式,深度優先和廣度優先。

  • 深度優先,記錄下每一個節點對應的層數後,按層數反向輸出便可
  • 廣度優先,記錄下每層的節點後反向輸出

編寫代碼驗證

Ⅰ.深度優先

代碼:

/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrderBottom = function(root) {
    // 當前層級標識
    let idx = 0
    let res = []

    function levelOrder(node, floor, arr) {
        if (node === null) return arr
        if(arr[floor]) {
            arr[floor].push(node.val)
        } else {
            arr[floor] = [node.val]
        }
        levelOrder(node.left, floor + 1, arr)
        levelOrder(node.right, floor + 1, arr)
        return arr
    }

    return levelOrder(root, idx, res).reverse()
};

結果:

  • 34/34 cases passed (68 ms)
  • Your runtime beats 77.01 % of javascript submissions
  • Your memory usage beats 34.78 % of javascript submissions (34.7 MB)
  • 時間複雜度 O(n)n 爲節點個數

Ⅱ.廣度優先

代碼:

/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrderBottom = function(root) {
    if (root === null) return []
    // 初始隊列
    let q = [root]
    let res = []

    while(q.length) {
        // 當前層節點數量
        const count = q.length
        let curArr = []
        for(let i = 0; i < count;i++) {
            const node = q.shift()
            curArr.push(node.val)
            // 將子節點依次推入隊列
            if (node.left) q.push(node.left)
            if (node.right ) q.push(node.right )
        }
        res.push(curArr)
    }
    return res.reverse()
};

結果:

  • 34/34 cases passed (64 ms)
  • Your runtime beats 89.2 % of javascript submissions
  • Your memory usage beats 32.3 % of javascript submissions (34.7 MB)
  • 時間複雜度 O(n)n 爲節點個數

查閱他人解法

沒有看到什麼特別的解法,主要都是按 BFS 和 DFS 來處理,要麼迭代,要麼遞歸等等。

這裏就介紹下別的吧,在第一種解法中咱們使用的是前序優先,固然用中序優先或後序優先也能夠,下面代碼能夠說明區別:

// 先序,順序爲 根 -> 左 -> 右
function levelOrder(node, floor, arr) {
    if(arr[floor]) {
        arr[floor].push(node.val)
    } else {
        arr[floor] = [node.val]
    }

    levelOrder(node.left, floor + 1, arr)
    levelOrder(node.right, floor + 1, arr)
    return arr
}
// 中序,順序爲 左 -> 根 -> 右
function levelOrder(node, floor, arr) {
    levelOrder(node.left, floor + 1, arr)

   if(arr[floor]) {
       arr[floor].push(node.val)
   } else {
       arr[floor] = [node.val]
   }

    levelOrder(node.right, floor + 1, arr)
    return arr
}
// 後序,順序爲 左 -> 右 -> 根
function levelOrder(node, floor, arr) {
    levelOrder(node.left, floor + 1, arr)
    levelOrder(node.right, floor + 1, arr)

    if(arr[floor]) {
        arr[floor].push(node.val)
    } else {
        arr[floor] = [node.val]
    }
    return arr
}

思考總結

二叉樹的題目就根據狀況在深度優先和廣度優先中擇優選擇便可,基本不會有太大的問題。

108.將有序數組轉換爲二叉搜索樹

題目地址

題目描述

將一個按照升序排列的有序數組,轉換爲一棵高度平衡二叉搜索樹。

本題中,一個高度平衡二叉樹是指一個二叉樹每一個節點 的左右兩個子樹的高度差的絕對值不超過 1。

示例:

給定有序數組: [-10,-3,0,5,9],

一個可能的答案是:[0,-3,9,-10,null,5],它能夠表示下面這個高度平衡二叉搜索樹:

      0
     / \
   -3   9
   /   /
 -10  5

題目分析設想

這裏有兩點要注意的:高度平衡二叉樹要求每一個節點的左右兩個子樹的高度差的絕對值不超過 1;而二叉搜索樹要求左子樹上全部節點值小於根節點,右子樹上全部節點值大於根節點。

而題目給出的是一個有序的數組,因此能夠直接考慮二分後進行處理,我這就直接遞歸做答:找到根節點,遞歸生成左右子樹。

編寫代碼驗證

Ⅰ.遞歸

代碼:

/**
 * @param {number[]} nums
 * @return {TreeNode}
 */
var sortedArrayToBST = function(nums) {
    if (!nums.length) return null
    // 中位數,用偏移避免溢出
    const mid = nums.length >>> 1
    const root = new TreeNode(nums[mid])
    root.left = sortedArrayToBST(nums.slice(0, mid))
    root.right = sortedArrayToBST(nums.slice(mid + 1))
    return root
};

結果:

  • 32/32 cases passed (80 ms)
  • Your runtime beats 70.72 % of javascript submissions
  • Your memory usage beats 29.79 % of javascript submissions (37.8 MB)
  • 時間複雜度 O(n)

查閱他人解法

這裏看到另一種解法,先建立一個平衡二叉樹,而後中序遍歷樹同時遍歷數組便可,由於中序遍歷出來的恰好是有序數組。

Ⅰ.建立樹後中序遍歷數組賦值

代碼:

/**
 * @param {number[]} nums
 * @return {TreeNode}
 */
var sortedArrayToBST = function(nums) {
    if (!nums.length) return null

    // 節點總數
    let len = nums.length
    let root = new TreeNode(-1);
    let q = [root]
    // 已經建立了根節點
    len--
    while(len) {
        const node = q.shift()
        // 左子樹
        const l = new TreeNode(-1)
        q.push(l)
        node.left = l
        len--
        if (len) {
            // 右子樹
            const r = new TreeNode(-1)
            q.push(r)
            node.right = r
            len--
        }
    }

    let i = 0
    inorder(root)
    function inorder(node) {
        if (node === null) return
        inorder(node.left)
        node.val = nums[i++]
        inorder(node.right)
    }

    return root
};

結果:

  • 32/32 cases passed (72 ms)
  • Your runtime beats 93.4 % of javascript submissions
  • Your memory usage beats 24.12 % of javascript submissions (37.8 MB)
  • 時間複雜度 O(n)

思考總結

這裏實際上是個逆向思惟,以前是二叉樹輸出數組,如今變成數組轉成二叉樹。恰好能夠翻一下前序中序和後序的區別,這裏中序就能夠了。不過這道題我仍是更推薦遞歸二分求解。

(完)


本文爲原創文章,可能會更新知識點及修正錯誤,所以轉載請保留原出處,方便溯源,避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗
若是能給您帶去些許幫助,歡迎 ⭐️star 或 ✏️ fork (轉載請註明出處:https://chenjiahao.xyz)

相關文章
相關標籤/搜索