牛客算法–第七章
題目一
分別用遞歸和非遞歸方式實現二叉樹先序、中序和後序遍歷
【題目】
用遞歸和非遞歸方式,分別按照二叉樹先序、中序和後序打印所有的節點。我們約定:先序遍歷順序爲根、左、右;中序遍歷順序爲左、根、右;後序遍歷順序爲左、右、根。
前序遍歷爲“根左右”:
遞歸C++版本:
void preorder(node* root)
{
if(root==NULL)
{
return; //到達空樹,遞歸邊界
}
//訪問根結點root,例如將其數據域輸出
printf("%d\n",root->value);
//訪問左子樹
preorder(root->lchild);
//訪問右子樹
preorder(root->rchild);
}
非遞歸JAVA版本:
public static void preOrderUnRecur(Node head){
System.out.print("preorder: ");
if(head != null)
{
Stack<Node> stack = new Stack<Node>();
stack.add(head);
while(!stack.isEmpty()){
head = stack.pop();
Systemp.out.print(head.value + " ");
if(head.right != null)
{
stack.push(head.right);
}
if(head.left != null)
{
stack.push(head.left)
}
}
}
System.out.println();
}
非遞歸版本核心思想就是用棧。
因爲先序遍歷的要求是“根左右”,而棧的結構是完成數據先進後出的。所以我們應該先判斷一個結點右子樹是否爲空,如果不爲空,就將其壓入棧中;然後再判斷一個結點左子樹是否爲空,如果不爲空,就將其壓入棧中。這樣就保證了一個結點的左子樹先於右子樹彈出棧。我們遍歷的過程就是彈出棧的過程,我們總是彈出棧頂最高的結點,如果該結點有左右子結點,就將其子結點按“先右後左”的順序壓入棧中;如果沒有的話,也就是葉子結點,因爲有if語句的判斷作用,它沒有子結點會在這一回合壓入,並且會將其自身從棧中彈出。
中序遍歷爲“左根右”:
遞歸C++版本:
void inorder(node* root)
{
if(root == NULL)
{
return;
}
inorder(root->lchild);
printf("%d\n",root->value);
inorder(root->rchild);
}
非遞歸JAVA版本:
public static void inOrderUnRecur(Node head){
System.out.print("inorder: ");
if(head != null)
{
Stack<Node> stack = new Stack<Node>();
while(!stack.isEmpty() || head != null ){
if(head != null){
stack.push(head);
head = head.left;
}else{
head = stack.pop();
System.out.print(head.value + " " );
head = head.right;
}
}
}
System.out.println();
}
非遞歸中序遍歷的順序因爲是“左根右”,我們實際上把一顆樹想成下面:
/
/ /
/ / /
/ / /
就是想成一排排左斜線的樣子。
就是有一絲絲“左傾”的分子都要讓他入棧。啥意思呢?就是第一優先級是“左左”,第二優先級是“右左”。
後序遍歷爲“左右根”:
遞歸C++版本:
void postorder(node* root)
{
if(root == NULL)
{
return;
}
postorder(root->lchild);
postorder(root->rchild);
printf("%d\n",root->value);
}
非遞歸JAVA版本:
非遞歸後序遍歷的核心思想是用兩個棧stack1和stack2.後序遍歷要求的是“左右根”,我們看一下它的逆序,就是“根右左”,那麼,如何做呢?就是前面非遞歸先序遍歷中,棧中先壓右後壓左,最終實現了“根左右”,現在我們棧中先壓左後壓右,自然就可以得到“根右左”了。接下來第二個stack2的作用實際上就是起一個逆序的作用,最終就可以得到“左右根”的效果了。當然,實現逆序,也不一定用棧,我們可以存儲在容器中,然後進行reverse就可以了。
提前預告,下面的題目二和題目三都是套路題,要掌握。
題目二
找到二叉樹中的最大搜索二叉子樹
【題目】
給定一棵二叉樹的頭節點 head,已知其中所有節點的值都不一樣,找到含有節點最多的搜索二叉子
樹,並返回這棵子樹的頭節點。
【要求】
如果節點數爲 N,要求時間複雜度爲 O(N),額外空間複雜度爲 O(h),h 爲二叉樹的高度。
首先,要弄明白最大子樹和最大拓撲結構的**區別。**一個點包括左右子樹都滿足條件,這個點爲根結點的樹纔算是一顆符合條件的子樹。
假如我們有一個函數F(…)可以返回以下4個信息:
1.最大搜索子樹頭結點
2.整棵樹的min值
3.整棵樹的max值
4.整棵樹符合條件的size
public static Node biggestSubBST(Node head){
//0->size,1->min,2->max
int[] record = new int[3];
return posOrder(head,record);
}
public static Node posOrder(Node head,int[] record){
if(head == null)
{
record[0] = 0;
record[1] = Integer.MAX_VALUE;
record[2] = Integer.MIN_VALUE;
return null;
}
int value = head.value;
Node left = head.left;
Node right = head.right;
Node lBST = postOrder(left,record);
int lSize = record[0];
int lMin = record[1];
int lMax = record[2];
Node rBST = posOrder(right,record);
int rSize = record[0];
int rMin = record[1];
int rMax = record[2];
record[1] = Math.min(lMin,value);
record[2] = Math.max(rMax,value);
if( left == lBST && right == rBST
&& lMax < value && value < rMin )
{
record[0] = lSize + rSize +1;
return head;
}
record[0] = Math.max(lSize,rSize);
return lSize > rSize ? lBST : rBST;
}
題目三
二叉樹節點間的最大距離問題
【題目】
從二叉樹的節點 A 出發,可以向上或者向下走,但沿途的節點只能經過一次,當到達節點 B 時,路
徑上的節點數叫作 A 到 B 的距離。求整棵樹上節點間的最大距離。
【要求】
如果二叉樹的節點數爲 N,時間複雜度要求爲 O(N)。
public static int maxDistance(Node head)}{
int[] record = new int[1];
return posOrder(head,record);
}
public static int posOrder(Node head,int[] record){
if(head == null){
record[0] =0;
return 0;
}
int lMax = posOrder(head.left,record);
int maxFromLeft = record[0];
int rMax = posOrder(head.right,record);
int maxFromRight = record[0];
int curNodeMax = maxFromLeft + maxFromRight+1;
record[0] = Math.max(maxFromLeft, maxFromRight )+1;
return Math.max(Math.max(lMax,rMax),curNodeMax);
}
題目五
在二叉樹中找到累加和爲指定值的最長路徑長度
【題目】
給定一棵二叉樹的頭節點 head 和一個 32 位整數 sum,二叉樹節點值類型爲整型,求累加和爲 sum
的最長路徑長度。路徑是指從某個節點往下,每次最多選擇一個孩子節點或者不選所形成的節點鏈。
,額外空間複雜度爲 O(h),h 爲二叉樹的高度。
總結,解決本題需要先複習以下原來的一個算法原型,求一個數組中指定和爲k的最長子數組。
怎麼求解呢?用到哈希map。
首先在map中插入(0,-1)鍵值對,因爲數組下標是從0開始的,所以提前設置從-1位置開始的值是0。
遍歷一遍數組,在遍歷的過程中進行累加,累加值是key,如果這個key值在map中沒有出現過,就將其加入map中,對應的value值是遍歷到的下標,如果已經出現過了,就不進行理會。
算法的原理就是假設從0到i的累加和爲num1,我們要求以i結尾的累加和爲指定k的最長子數組和,就是從map中找到num1-k對應的key值,找出對應的index,符合條件的最長子數組就是index~i的數組。我們如何求長度呢?i-index+1。
同樣我們在遍歷的過程中可以得出所有以0~n結尾的累加和爲指定k的最長子數組和,所以我們在其中選出一個最大值就可以了。
迴歸本題,樹的操作,和之前的又有一些不同,需要注意數據的保存,在遞歸的過程中自然實現保存。
代碼中的sum就是指定和k,數組中的index,就是對應樹中的level:
public static int getMaxLength(Node head,int sum){
HashMap<Integer,Integer> sumMap = new HashMap<Integer,Integer>();
sumMap.put(0,0);//important
return preOrder(head,sum,0,1,0,sumMap);
}
public static int preOrder(Node head,int sum,int preSum,
int level ,int maxLen,HashMap<Integer,Integer> sumMap ){
if(head == null)
{
return maxLen;
}
int curSum = preSum + head.value;
if(!sumMap.containsKey(curSum)){
sumMap.put(curSum,level);
}
if(sumMap.containsKey(curSum - sum)){
maxLen = Math.max(level - sumMap.get(curSum - k),maxLen);
}
maxLen = preOrder(head.left,sum,curSum,level+1,maxLen,sumMap);
maxLen = preOrder(head.right,sum,curSum,level+1,maxLen,sumMap);
if(level == sumMap.get(curSum)){
sumMap.remove(curSum);
}
return maxLen;
}