2019 算法面試相關(leetcode)--樹、二叉樹、二叉搜索樹

2019 iOS面試題大全---全方面剖析面試
2018 iOS面試題---算法相關
1、七種常見的數組排序算法整理(C語言版本)
2、2019 算法面試相關(leetcode)--數組和鏈表
3、2019 算法面試相關(leetcode)--字符串
4、2019 算法面試相關(leetcode)--棧和隊列
5、2019 算法面試相關(leetcode)--優先隊列
6、2019 算法面試相關(leetcode)--哈希表
7、2019 算法面試相關(leetcode)--樹、二叉樹、二叉搜索樹
8、2019 算法面試相關(leetcode)--遞歸與分治
9、2019 算法面試相關(leetcode)--貪心算法
10、2019 算法面試相關(leetcode)--動態規劃(Dynamic Programming)
11、2019 算法面試相關(leetcode)--動態規劃之背包問題


翻轉二叉樹
二叉樹的前序遍歷
二叉樹的中序遍歷
二叉樹的后序遍歷
驗證二叉搜索樹
二叉樹的最近公共祖先
二叉搜索樹的最近公共祖先

是一種數據結構,它是由n(n>=1)個有限結點組成一個具有層次關系的集合。把它叫做“樹”是因為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具有以下的特點:

每個結點有零個或多個子結點;沒有父結點的結點稱為根結點;每一個非根結點有且只有一個父結點;除了根結點外,每個子結點可以分為多個不相交的子樹

二叉樹(Binary Tree)是每個結點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。二叉樹常被用于實現二叉查找樹和二叉堆。
一棵深度為k,且有2^k-1個節點的二叉樹,稱為滿二叉樹。這種樹的特點是每一層上的節點數都是最大節點數。而在一棵二叉樹中,除最后一層外,若其余層都是滿的,并且最后一層或者是滿的,或者是在右邊缺少連續若干節點,則此二叉樹為完全二叉樹。具有n個節點的完全二叉樹的深度為floor(log2n)+1。深度為k的完全二叉樹,至少有2k-1個葉子節點,至多有2k-1個節點。

二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小于它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大于它的根結點的值; 它的左、右子樹也分別為二叉排序樹。

從二叉樹的定義來看,算是比較適用遞歸算法的數據結構了

iOS:分別用OC和swift實現二叉樹,并繪制到屏幕上

二叉樹

function TreeNode(val) {
         this.val = val;
         this.left = this.right = null;
}
一、 翻轉二叉樹

翻轉一棵二叉樹。

示例:

輸入:

    4
   /   \
  2     7
 / \   / \
1   3 6   9

輸出:

    4
   /   \
  7     2
 / \   / \
9   6 3   1

備注:
這個問題是受到 Max Howell 原問題 啟發的 :

谷歌:我們90%的工程師使用您編寫的軟件(Homebrew),但是您卻無法在面試時在白板上寫出翻轉二叉樹這道題,這太糟糕了。


又是經典的翻轉。
直接用遞歸解法,分別遞歸地交換左右子樹

var invertTree = function(root) {
   
   if(!root) return root

   let right = root.right
   
   root.right = invertTree(root.left)
   
   root.left = invertTree(right)

   return root
};
二、 二叉樹的前序遍歷

給定一個二叉樹,返回它的 前序 遍歷。

示例:

輸入: [1,null,2,3]

   1
    \
     2
    /
   3 

輸出: [1,2,3]
進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?


前序遍歷首先訪問根結點然后遍歷左子樹,最后遍歷右子樹。在遍歷左、右子樹時,仍然先訪問根結點,然后遍歷左子樹,最后遍歷右子樹。

簡單說其遍歷順序就是:根--左--右

使用遞歸可以很容易實現。

var preorderTraversal = function(root) {
    
    return helper(root,[])
};

var helper = function(root,nums){

    if(!root) return nums

    nums.push(root.val)

    helper(root.left,nums)

    helper(root.right,nums)

    return nums
}

不過題目有要求用迭代算法,這里我們可以想到之前提過的數據結構:
我們可以先將父結點的值放到數組里,然后將父節點入棧,等遍歷完左子樹,再出棧,去遍歷右子樹。

var preorderTraversal = function(root){

    let treeStack = []

    let res = []

    while(root || treeStack.length){

        while(root){

            res.push(root.val)

            treeStack.push(root)

            root = root.left
        }

        if(treeStack.length){

            root = treeStack.pop()

            root = root.right
        }
    }

    return res
}
三、 二叉樹的中序遍歷

給定一個二叉樹,返回它的中序 遍歷。

示例:

輸入: [1,null,2,3]

 1
  \
   2
  /
 3

輸出: [1,3,2]
進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?


中序遍歷首先遍歷左子樹,然后訪問根結點,最后遍歷右子樹。若二叉樹為空則結束返回,否則:

(1)中序遍歷左子樹

(2)訪問根結點

(3)中序遍歷右子樹

簡單說其遍歷順序就是:左--根--右

同理,用遞歸也可以很簡單的實現。

var inorderTraversal = function(root) {

    return helper(root,[])
};

var helper = function(root,nums){

    if(!root) return nums

    helper(root.left,nums)

    nums.push(root.val)

    helper(root.right,nums)

    return nums
}

如果不用遞歸,用迭代呢?一樣,也可以用棧去實現,先把父結點入棧,去遍歷左子樹,然后出棧將父節點放進數組,在同樣去遍歷右子樹

var inorderTraversal = function(root){

   let treeStack = []

    let res = []

    while(root || treeStack.length){

        while(root){

            treeStack.push(root)

            root = root.left
        }

        if(treeStack.length){

            root = treeStack.pop()

            res.push(root.val)

            root = root.right
        }
    }

    return res
}
四、二叉樹的后序遍歷

給定一個二叉樹,返回它的 后序 遍歷。

示例:

輸入: [1,null,2,3]

   1
    \
     2
    /
   3 

輸出: [3,2,1]
進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?


后序遍歷首先遍歷左子樹,然后遍歷右子樹,最后訪問根結點,在遍歷左、右子樹時,仍然先遍歷左子樹,然后遍歷右子樹,最后遍歷根結點。即:
若二叉樹為空則結束返回,否則:

(1)后序遍歷左子樹

(2)后序遍歷右子樹

(3)訪問根結點

簡單說其遍歷順序就是:左--右--根

和前序中序一樣,用遞歸也可以很簡單的實現。

var postorderTraversal = function(root) {
    
    return helper(root,[])
};

var helper = function(root,nums){

    if(!root) return nums

    helper(root.left,nums)

    helper(root.right,nums)

    nums.push(root.val)

    return nums
}

那如果不用遞歸用迭代呢?
leetcode上的前序中序遍歷兩道題都是標注的中等難度,而這道題卻是標注成困難難度。
因為如果還是用之前的思路即還是用棧,是沒辦法直接實現的。
我們可以以根-右-左順序入棧,然后一直向左遍歷,直至左右子樹都為空。出棧并保存到數組中,然后一直重復以上,這里需要注意的是,由于父結點已經遍歷過了,這里一直出棧會造成死循環。所以我們還需要引入一個參數pre,來記錄上次遍歷的樹,如果是棧尾的左右子樹,則說明該結點已經遍歷過,不再重復遍歷。

var postorderTraversal = function(root) {
    
    if(!root) return []
    
    let treeStack = [root]

    let res = []

    let pre

    while(treeStack.length){

        root = treeStack[treeStack.length - 1]

        if((!root.left && !root.right) || (pre &&(pre == root.left || pre == root.right))){

            res.push(root.val)

            treeStack.pop()

            pre = root

        }else{

            if(root.right) treeStack.push(root.right)

            if(root.left) treeStack.push(root.left)
        }
    }

    return res
}
五、 驗證二叉搜索樹

給定一個二叉樹,判斷其是否是一個有效的二叉搜索樹。

假設一個二叉搜索樹具有如下特征:

節點的左子樹只包含小于當前節點的數。
節點的右子樹只包含大于當前節點的數。
所有左子樹和右子樹自身必須也是二叉搜索樹。
示例 1:

輸入:

    2
   / \
  1   3

輸出: true
示例 2:

輸入:

    5
   / \
  1   4
     / \
    3   6

輸出: false
解釋: 輸入為: [5,1,4,null,null,3,6]。
根節點的值為 5 ,但是其右子節點值為 4 。


驗證二叉搜索樹,其實就是驗證其是否有序,我們可以對其進行中序遍歷,如果遍歷后的數據是有序的,則說明是二叉搜索樹,反之則不是。

var isValidBST = function(root) {

    if(!root) return true

    let nums = []

    helper(root,nums)
    
    for(let i = 0; i < nums.length - 1; i++){
        
        if(nums[i] >= nums[i + 1]) return false
    }

    return true
}
var helper = function(root,nums){

    if(!root) return

    helper(root.left,nums)

    nums.push(root.val)

    helper(root.right,nums)
}

那么能不能用o(1)的空間復雜度來解決呢?事實上我們沒必要把數據都保存下來,對于右子樹而言,只要所有右子樹都大于其父節點即可,同理,所有左子樹小于其父節點即可。
我們可以用兩個變量:min和max,對于左子樹,則判斷是否小于min;對于右子樹,則判斷是否大于max
然后不斷遞歸遍歷,同時更新min和max

var isValidBST = function(root) {

    return helper(root,undefined,undefined)
}

var helper = function(root,min,max){

    if(!root ) return true

    if(min != undefined && root.val <= min) return false

    if(max != undefined && root.val >= max) return false

    return helper(root.right,root.val,max) && helper(root.left,min,root.val)
}
六、 二叉樹的最近公共祖先

給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。

百度百科中最近公共祖先的定義為:“對于有根樹 T 的兩個結點 p、q,最近公共祖先表示為一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度盡可能大(一個節點也可以是它自己的祖先)。”

例如,給定如下二叉樹: root = [3,5,1,6,2,0,8,null,null,7,4]

image

示例 1:

輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
輸出: 3
解釋: 節點 5 和節點 1 的最近公共祖先是節點 3。

示例 2:

輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
輸出: 5
解釋: 節點 5 和節點 4 的最近公共祖先是節點 5。因為根據定義最近公共祖先節點可以為節點本身。

說明:

  • 所有節點的值都是唯一的。
  • p、q 為不同節點且均存在于給定的二叉樹中。

首先可以確定如果root為null或者p或q是根節點,那么說明最近的公共祖先就是根結點root。
我們同樣可以使用遞歸去實現,分別遞歸左右結點,將其設為根節點,如果返回null,說明公共祖先在另一側,如果都為null,說明公共結點就是root

var lowestCommonAncestor = function(root, p, q) {

    if(root == null || root == p || root == q) return root

    let left = lowestCommonAncestor(root.left,p,q)

    let right = lowestCommonAncestor(root.right,p,q)

    return !left ? right : (!right ? left : root)
}

或者我們可以同時分別求p和q的最近祖先

var findLowestAncestor = function(root,p){

    if(root == null || root == p || root.left == p || root.right == p) return root

    return findLowestAncestor(root.left,p) || findLowestAncestor(root.right,p)
}

一層一層往上遍歷,直到p和q的祖先相遇

var lowestCommonAncestor = function(root, p, q) {

    let qSet = new Set()
    
    qSet.add(q)

    while(p != q){

        if(qSet.has(p)) return p

        while(q != root){

            q = findAncestor(root,q)

            if(p == q) return p
            
            qSet.add(q)
        }
        
        p = findAncestor(root,p)
    }
    
    return p
};
七、 二叉搜索樹的最近公共祖先

給定一個二叉搜索樹, 找到該樹中兩個指定節點的最近公共祖先。

百度百科中最近公共祖先的定義為:“對于有根樹 T 的兩個結點 p、q,最近公共祖先表示為一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度盡可能大(一個節點也可以是它自己的祖先)。”

例如,給定如下二叉搜索樹: root = [6,2,8,0,4,7,9,null,null,3,5]

image

示例 1:

輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
輸出: 6
解釋: 節點 2 和節點 8 的最近公共祖先是 6。

示例 2:

輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
輸出: 2
解釋: 節點 2 和節點 4 的最近公共祖先是 2, 因為根據定義最近公共祖先節點可以為節點本身。

說明:

  • 所有節點的值都是唯一的。
  • p、q 為不同節點且均存在于給定的二叉搜索樹中。

題目中的p和q有可能兩個都在同側,在遞歸的過程中,另一側則沒有必要去遞歸了,但因為二叉樹無序,所以上邊的算法沒辦法去判斷,就算p和q不在另一側,也只能繼續遞歸下去。
而在二叉搜索樹中,上邊的那個算法就可以優化下
如果p和q都小于root的值,則說明p和q都在左側,向左邊遞歸即可;
如果p和q都大于root的值,則說明p和q都在右側,向右邊遞歸即可;
否則,就說明p和q在root的左右兩側,則其最近公共祖先就是root。
如果(root.val - p.val) 和 (root.val - q.val)的積是正的,則說明他們在同一側

var lowestCommonAncestor = function(root, p, q) {
  
   return (root.val - p.val) * (root.val - q.val) <= 0 ? root : lowestCommonAncestor(root.val >= q.val ? root.left : root.right, p, q)
};

這里當然也可以不用遞歸

var lowestCommonAncestor = function(root, p, q) {

    while ((root.val - p.val) * (root.val - q.val) > 0) root = p.val < root.val ? root.left : root.right

    return root
};
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373