1. 前言
除了可以直接使用遞歸處理的二叉樹問題,二叉樹中還有一些典型的算法問題,需要和其他的數(shù)據(jù)結(jié)構(gòu)配合解決,例如使用堆、棧以及隊列數(shù)據(jù)結(jié)構(gòu)配合查找的過程,可以避免使用遞歸算法。
2. 二叉樹視圖
2.1 二叉樹右視圖
面試官提問:給定一顆二叉樹,求出從二叉樹右邊看到的所有節(jié)點結(jié)果集合。
題目解析:
求解二叉樹右視圖問題是來源于算法網(wǎng)站LeetCode的經(jīng)典題目,題目鏈接:https://leetcode.com/problems/binary-tree-right-side-view/。
首先給出二叉樹右視圖的定義:從二叉樹的右邊看到的第一個節(jié)點,即是當(dāng)前層數(shù)的結(jié)果節(jié)點,把所有的結(jié)果節(jié)點添加到集合,即是結(jié)果集合。
舉例來說,對于上圖中的二叉樹結(jié)果,其右視圖的結(jié)果集是[3,20,8]
。
根據(jù)題意,最明顯的方法是層次遍歷二叉樹,也就是BFS廣度優(yōu)先搜索算法,按照這個思路。
我們使用雙向隊列作為數(shù)據(jù)結(jié)構(gòu)存儲層次遍歷的結(jié)果,我們要解決兩個問題,一是如何實現(xiàn)層次遍歷,二是如何判斷是否是每層的最右邊的節(jié)點。
(1)初始條件:首先把根節(jié)點放入雙向隊列尾部;
(2)最右判斷:每次首先得到雙向隊列當(dāng)前的長度 size,這是一個固定值,對于第一層來說,因為只有一個根節(jié)點,所以 size-1 的位置就是最右邊的節(jié)點;
(3)循環(huán)處理:以size作為循環(huán)次數(shù),從雙向隊列頭部開始,不斷彈出節(jié)點,將每個節(jié)點的非空左右子樹加入雙向隊列尾部。當(dāng)遍歷到 size-1 的位置時,就是當(dāng)前層數(shù)的最右節(jié)點。一直循環(huán),直到雙向隊列為空,即處理完所有的二叉樹節(jié)點。
在 Java中 使用 ArrayDeque 數(shù)據(jù)結(jié)構(gòu)實現(xiàn)雙向隊列,下面給出 Java 算法的源碼以及注解,示例:
public List<Integer> rightSideView(TreeNode root) {
Queue<TreeNode> q= new ArrayDeque<>();
List<Integer> res = new ArrayList<>();
//如果是空節(jié)點,直接返回空結(jié)果集
if(root == null)
return res;
//將根節(jié)點添加到隊列
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
for(int i = 0; i< size; i++){
//從雙向隊列頭部彈出第一個節(jié)點
TreeNode node = q.poll();
//如果是當(dāng)前層數(shù)的最后一個節(jié)點,就是需要的右視圖節(jié)點
if(i == size-1)
res.add(node.val);
//彈出節(jié)點的有效左節(jié)點加入隊列
if(node.left != null)
q.offer(node.left);
//彈出節(jié)點的有效右節(jié)點加入隊列
if(node.right != null)
q.offer(node.right);
}
}
//返回結(jié)果集
return res;
}
當(dāng)然除了 BFS 算法外,本題目也可以使用 DFS 算法,即通過前序遍歷獲取需要的最右節(jié)點,這里不再贅述。
2.2 二叉樹左視圖、二叉樹層次遍歷
求解二叉樹左視圖、二叉樹右視圖、二叉樹層次遍歷問題,都可以使用雙向隊列求解。
(1)二叉樹右視圖:每次獲取當(dāng)前層雙向隊列的最后一個節(jié)點;
(2)二叉樹左視圖:每次獲取當(dāng)前層雙向隊列的第一個節(jié)點;
(3)二叉樹層次遍歷:對于每遍歷一次 size ,結(jié)果添加到一個單獨的列表。
3. 小結(jié)
候選人在學(xué)習(xí)算法問題時,需要注意舉一反三,目前算法網(wǎng)站的面試題題庫已經(jīng)接近 2000 題,如果企圖通過刷遍題庫的方式提高算法能力,是非常消耗精力的。所以要做到精簡問題和總結(jié)算法套路,例如本章中介紹的二叉樹和雙向隊列配合使用,實現(xiàn)廣度優(yōu)先搜索,就是一個非常典型的算法模板,可以多加熟悉。