接着第二課的內容和帶點第三課的內容。node
(回顧)準備一個棧,從大到小排列,具體參考上一課....面試
【題目】數組
定義二叉樹以下:app
public class Node{函數
public int value;this
public Node left;spa
public Node right;3d
public Node(int data){指針
this.value=data;code
}
}
一個數組的MaxTree定義以下:
◆ 數組必須沒有重複元素
◆ MaxTree是一顆二叉樹,數組的每個值對應一個二叉樹節點
◆ 包括MaxTree樹在內且在其中的每一顆子樹上,值最大的節點都是樹的頭。
給定一個沒有重複元素arr,寫出生成這個數組的MaxTree函數,要求若是數組長度爲N,則時間複雜度爲O(N),額外空間複雜度爲O(N)。
解法一:建出大根堆再重連成徹底二叉樹,o(n)
解法二:單調棧
一、左右都沒比他大的節點就是頭節點
二、對於有左右其中一邊的,這個數就串在有的底下,3在4底下
三、對於左右都有的,掛在左右中較小的數下,2在3底下
爲何可行?
爲何不會造成森林?
數組中沒有重複值,最大值確定是頭節點,任何節點都串在比他大的,都有歸屬,最終以最大值爲頭部,因此是一棵樹。
會不會出現一個節點多個孩子的狀況?
首先先證實在任意一側只有一棵樹掛在他底下。
反證法:假設b、c都掛在a底下。
b>c的話,不可能c還會掛在a底下,存在矛盾。
c>b的話,b也不可能掛在a下,會掛在c底下。
老師版本(分開設置左右臨近的最大值)
//構造數組的MaxTree public class Code_02_MaxTree { //二叉樹結點的定義以下 public static class Node { public int value; public Node left; public Node right; //結點的初始化 public Node(int data) { this.value = data; } } //構造數組的MaxTree函數 public static Node getMaxTree(int[] arr) { Node[] nArr = new Node[arr.length]; //存放二叉樹結點的數組 for (int i = 0; i != arr.length; i++) { nArr[i] = new Node(arr[i]); } Stack<Node> stack = new Stack<Node>();//利用棧找出左右邊第一個比自身大的數 HashMap<Node, Node> lBigMap = new HashMap<Node, Node>(); HashMap<Node, Node> rBigMap = new HashMap<Node, Node>(); //從第一個開始 // 找出全部數,左邊第一個比自身大的數 for (int i = 0; i != nArr.length; i++) { Node curNode = nArr[i]; while ((!stack.isEmpty()) && stack.peek().value < curNode.value) { popStackSetMap(stack, lBigMap);//棧的一系列操做 } stack.push(curNode); } while (!stack.isEmpty()) { popStackSetMap(stack, lBigMap); } //從最後一個開始 // 找出全部數,右邊第一個比自身大的數 for (int i = nArr.length - 1; i != -1; i--) { Node curNode = nArr[i]; while ((!stack.isEmpty()) && stack.peek().value < curNode.value) { popStackSetMap(stack, rBigMap);//棧的一系列操做 } stack.push(curNode); } while (!stack.isEmpty()) { popStackSetMap(stack, rBigMap); } Node head = null; //聲明頭結點 for (int i = 0; i != nArr.length; i++) { Node curNode = nArr[i]; Node left = lBigMap.get(curNode); Node right = rBigMap.get(curNode); //左右都空,證實他是最大的頭節點 if (left == null && right == null) { head = curNode; } else if (left == null) {//左爲空,就串在右的底下 if (right.left == null) {//從左到右 right.left = curNode; } else { right.right = curNode; } } else if (right == null) {//右爲空,就串在左的底下 if (left.left == null) {//從左到右 left.left = curNode; } else { left.right = curNode; } } else { //選擇左右較小的數爲父節點 Node parent = left.value < right.value ? left : right; if (parent.left == null) {//從左到右 parent.left = curNode; } else { parent.right = curNode; } } } return head; } //棧的一系列操做 public static void popStackSetMap(Stack<Node> stack, HashMap<Node, Node> map) { Node popNode = stack.pop(); if (stack.isEmpty()) { map.put(popNode, null); } else { map.put(popNode, stack.peek()); //構造二叉樹操做 } } //二叉樹的先序遍歷 public static void printPreOrder(Node head) { if (head == null) { return; } System.out.print(head.value + " "); printPreOrder(head.left); //遞歸調用遍歷二叉樹 printPreOrder(head.right); } //二叉樹的中序遍歷 public static void printInOrder(Node head) { if (head == null) { return; } printInOrder(head.left); System.out.print(head.value + " "); printInOrder(head.right); } public static void main(String[] args) { int[] arr = {3, 4, 5, 1, 2}; Node head = getMaxTree(arr); printPreOrder(head); System.out.println(); printInOrder(head); } }
本身碼的版本(彈出一個數,同時設置其左右臨近的最大值)
public class a03_01MaxTree { public static class Node { public int value; public Node left; public Node right; public Node(int data) { this.value = data; } } public static Node getMaxTree(int[] nodes) { Stack<Integer> stack = new Stack<Integer>(); HashMap<Integer, Integer> lLarge = new HashMap<Integer, Integer>(); HashMap<Integer, Integer> rLarge = new HashMap<Integer, Integer>(); for (int i = 0; i != nodes.length; i++) { while (!stack.isEmpty() && nodes[i] > nodes[stack.peek()]) { int index = stack.pop(); //誰讓它彈出誰就是右邊比他大的 rLarge.put(index, i); //再經過棧來設置左邊的狀況 if (stack.isEmpty()) { lLarge.put(index, null); } else { lLarge.put(index, stack.peek()); } } stack.push(i); } //把棧裏面剩下的元素處理下 while (!stack.isEmpty()) { int index = stack.pop(); rLarge.put(index, null); if (stack.isEmpty()) { lLarge.put(index, null); } else { lLarge.put(index, stack.peek()); } } //造成Node數組 Node[] treeNodes = new Node[nodes.length]; for (int i = 0; i != nodes.length; i++) { treeNodes[i] = new Node(nodes[i]); } //建樹 Node head = null; for (int i = 0; i != nodes.length; i++) { Integer lLargeIndex = lLarge.get(i); Integer rLargeIndex = rLarge.get(i); if (lLargeIndex == null && rLargeIndex == null) { head = treeNodes[i]; } else if (lLargeIndex == null) { if (treeNodes[rLargeIndex].left == null) { treeNodes[rLargeIndex].left = treeNodes[i]; } else { treeNodes[rLargeIndex].right = treeNodes[i]; } } else if (rLargeIndex == null) { if (treeNodes[lLargeIndex].left == null) { treeNodes[lLargeIndex].left = treeNodes[i]; } else { treeNodes[lLargeIndex].right = treeNodes[i]; } } else {//對比左右大小 Node parent = nodes[lLargeIndex] > nodes[rLargeIndex] ? treeNodes[rLargeIndex] : treeNodes[lLargeIndex]; if (parent.left == null) { parent.left = treeNodes[i]; } else { parent.right = treeNodes[i]; } } } return head; } //二叉樹的先序遍歷 public static void printPreOrder(Node head) { if (head == null) { return; } System.out.print(head.value + " "); printPreOrder(head.left); //遞歸調用遍歷二叉樹 printPreOrder(head.right); } //二叉樹的中序遍歷 public static void printInOrder(Node head) { if (head == null) { return; } printInOrder(head.left); System.out.print(head.value + " "); printInOrder(head.right); } public static void main(String[] args) { int[] arr = {3, 4, 5, 1, 2}; Node head = getMaxTree(arr); printPreOrder(head); System.out.println(); printInOrder(head); } }
給定一個整型矩陣map,其中的值只有0,1兩種,求其中全是1 的全部矩陣區域中,最大的矩形區域爲1的數量。
例如:
1 1 1 0
其中最大的矩形區域有3個1,因此返回3
例如:
1 0 1 1
1 1 1 1
1 1 1 0
其中,最大的矩形區域有6個1,因此返回6
跳出這道題目,爲了解決這個問題的一個引子題。
直方圖找最大矩形
每一個數都表明一個矩形,從第一個數開始嘗試左右移動,碰到小的就停,例如4,面積就是四、3面積就是6....依次確定會計算出最大的矩形。
利用單調棧,找到最近比他小的元素,彈出的時候,就能夠計算出面積。
若是不是由於碰到比本身小的,所有元素事後,棧剩下的,就證實他們都能到達右邊界(擴到5停)。
相等的狀況:
原始問題,徹底借用了直方圖的結論。
先看必須由第0行做爲底,全部矩形中,哪一個含有1是最多的。
接着看由第1行爲底,每一個位置,上面有多少個連續的1,而後經過這個計算結果,計算出哪一個位置含1最多。
本次爲0必爲0,不然是以前的數再+1
依次計算....
矩陣遍歷一遍,o(n*m)搞定
代碼解說:
public class Code_04_MaximalRectangle { //height數組表示在以當前行做爲底的狀況下,每一個位置往上的連續的 1 的數量 public static int maxRecSize(int[][] map) { if (map == null || map.length == 0 || map[0].length == 0) { return 0; } int maxArea = 0; //輔助數組 int[] height = new int[map[0].length]; for (int i = 0; i < map.length; i++) { for (int j = 0; j < map[0].length; j++) { height[j] = map[i][j] == 0 ? 0 : height[j] + 1; } maxArea = Math.max(maxRecFromBottom(height), maxArea); } return maxArea; } //找到柱子左邊剛比它小的柱子位置,以及右邊剛比它大的柱子位置,用棧計算最快 //最基本的方法,若是一個數組表示直方圖的話,在其中找到最大正方形 //例如:[4,3,2,5,6]表明一個直方圖 private static int maxRecFromBottom(int[] height) { if (height == null || height.length == 0) { return 0; } int maxArea = 0; Stack<Integer> stack = new Stack<Integer>(); //遍歷數組中的每個數 for (int i = 0; i < height.length; i++) { //不斷循環,直到當前位置的值小於棧頂元素 while (!stack.isEmpty() && height[i] <= height[stack.peek()]) { int j = stack.pop();//值 int k = stack.isEmpty() ? -1 : stack.peek();//左邊界 int curArea = (i - k - 1) * height[j]; maxArea = Math.max(curArea, maxArea); } stack.push(i); } // 針對的是從棧底到棧頂一直遞增的狀況 //結算棧中剩下的東西 while (!stack.isEmpty()) { int j = stack.pop();//值 int k = stack.isEmpty() ? -1 : stack.peek();//左邊界 int curArea = (height.length - k - 1) * height[j]; maxArea = Math.max(curArea, maxArea); } return maxArea; } public static void main(String[] args) { int[][] num = new int[][]{ {0,1,0,0,0,0}, {1,1,1,1,1,1}, {1,1,1,1,1,1}, {1,1,1,1,1,1}, {1,1,1,1,1,1} }; System.out.println(maxRecSize(num)); } }
給一個數組,表明環形的山
一、每座山上會放烽火,相鄰可看見烽火
二、不相鄰的山峯,兩條路中其中一條路上的烽火都不大於他們之間的最小值,就能看見
返回能相互看見的山峯有多少對?
簡單問法:數組值都不同,o(1)。
2*i-3對
證實:
把找法規定爲:永遠是小去找大。
3個數以及上,找到最高和次高,假設中間有一個值i,從i出發找大的,左邊找到比i大的X停,右邊找到Y停,就是兩對。(最高和次高之間的每一個數都能找到兩個山峯對)
因此就是((n-(最高+次高))*2)+1(最高和次高那條)
((n-2)*2)+1 = 2n-4+1 = 2n-3條
有重複值的狀況:單調棧
找到第一個最大值,開始日後遍歷
加入碰到4,入棧,碰到5,開始出棧,由於底部是5,1(表明5出現了1次),因此對於4來講找到兩對能夠看見的山峯。接着底部5+1,變成5,2。
大致思路仍是小的找大的。(換成4也同理)
若是連續都是4,直到4,4碰上5,會產生兩種互相看見的狀況。
一、4和4以前相互看見,任意兩個4都能相互看見,C42+4*2。
二、每一個4左右邊都能看見5,4*2。
用最大值打底的緣由:在結算的過程當中由於有這個最大值的出現,能夠肯定這個數的順時針方向能夠找到比他大的。
最後棧裏面還有東西,須要結算。
三條及以上的仍是用那個通式
若是底下是2以上,依舊是通式
若是底下只有1個5(兩邊遇到最大的是同一個5),計算變成:
因此,倒數第二的數要考慮,最大值的數量
最後一個數,比一大就是單純的Ck2,若是隻有一個就是0對。
public class Code_05_MountainsAndFlame { public static void main(String[] args) { Scanner in = new Scanner(System.in); while (in.hasNextInt()) { int size = in.nextInt(); int[] arr = new int[size]; for (int i = 0; i < size; i++) { arr[i] = in.nextInt(); } System.out.println(communications(arr)); } in.close(); } public static class Pair { public int value; public int times; public Pair(int v) { this.value = v; this.times = 1; } } private static long communications(int[] arr) { if (arr == null || arr.length < 2) { return 0; } int size = arr.length; int maxIndex = 0; for (int i = 0; i < size; i++) {//得到最大值的下標 maxIndex = arr[maxIndex] < arr[i] ? i : maxIndex; } int value = arr[maxIndex];//最大值 int index = nextIndex(size, maxIndex);//最大值位置的下一個 long res = 0L;//能互相看見的山峯數 Stack<Pair> stack = new Stack<Pair>(); stack.push(new Pair(value));//以最大值爲底 while (index != maxIndex) {//轉一圈 value = arr[index]; while (!stack.isEmpty() && stack.peek().value < value) { int times = stack.pop().times; // res += getInternalSum(times) + times; // res += stack.isEmpty() ? 0 : times; res += getInternalSum(times) + 2 * times; } if (!stack.isEmpty() && stack.peek().value == value) { stack.peek().times++;//相等的話累加 } else { stack.push(new Pair(value)); } index = nextIndex(size, index); } //跑完一圈後,結算棧裏面剩下的 while (!stack.isEmpty()) { int times = stack.pop().times; //無論什麼,確定會內部產生山峯對 res += getInternalSum(times); if (!stack.isEmpty()) {//沒到最後一個 res += times; if (stack.size() > 1) {//3個及以上的狀況 res += times; } else {//第2個的狀況,要結合最後一個數來判斷 res += stack.peek().times > 1 ? times : 0; } } } return res; } //簡單Ck(下標)2(上標) private static long getInternalSum(int n) { return n == 1L ? 0L : (long) n * (long) (n - 1) / 2L; } private static int nextIndex(int size, int i) { return i < (size - 1) ? (i + 1) : 0; } }
作這些就是爲了進本身想進的任何公司,加油!
筆試分數會決定是否籤合同,按部門挑人。
經典二叉樹,不管遞歸仍是非遞歸都逃不過,o(h)的額外空間複雜度,h爲高度。
利用了底層空閒的空間,在學術上稱爲線索二叉樹
Morris遍歷過程當中的標準:
②...指向cur,讓其指向空,cur向右移動
首先先忘掉什麼前序中序後序遍歷,這就叫Morris序
按標準走徹底部流程。
看一下本質:
只要這個節點有左子樹,就能回到這個節點兩次,若是沒有左子樹,只到達這個節點一次。
當第二次來到這個節點的時候,左子樹的節點必定所有遍歷完。
使用最右節點的右指針來標記,是第一次來到節點仍是第二次來到。
不是徹底二叉樹也能夠。
遞歸版本的遍歷,會通過節點三次,打印時機在第一次就是先序,第二次中序,第三次後序。(最本質的是下面的遍歷)
Morris,把第一次來到節點的時候打印,就是先序遍歷。
打印時機放在最後一次來到這個節點的時候,就是中序遍歷,例如一個節點有左子樹,先把左子樹處理完再打印,就是中序,當沒有左子樹的時候就直接打印(第一次和第二次重疊)。
怎麼作到後序?
後序遍歷只關心能到本身兩次的節點,第二次到達節點的時候逆序打印左子樹的右邊界,打印完成後,在整個函數退出前,單獨打印整課樹的右邊界,就是後序遍歷。
怎麼逆序打印整棵樹右邊界?
用相似逆轉鏈表的方式,把指針修改,打印完,再調整回去。
時間複雜度:
每次通過節點都打印右子樹,都是有限的幾回,全部複雜度仍是O(N)
例子:
整顆左子樹能夠被右邊界分解。每條右邊界被遍歷了兩遍。右邊界總體節點個數N個,被遍歷有限幾遍,一共O(N)。
public class Code03_01_MorrisTraversal { //幫助理解Morris遍歷 public static void process(Node head) { if (head == null) { return; } // 1 //System.out.println(head.value); process(head.left); // 2 //System.out.println(head.value); process(head.right); // 3 //System.out.println(head.value); } public static class Node { public int value; Node left; Node right; public Node(int data) { this.value = data; } } //中序遍歷 public static void morrisIn(Node head) { if (head == null) { return; } Node cur = head; Node mostRight = null; while (cur != null) { mostRight = cur.left; if (mostRight != null) {//若是左孩子不等於空 //找到最右的節點,排除了null和cur的狀況,能一直找下去 while (mostRight.right != null && mostRight.right != cur) { mostRight = mostRight.right; } if (mostRight.right == null) { mostRight.right = cur; cur = cur.left; continue; } else { mostRight.right = null; } } System.out.print(cur.value + " "); cur = cur.right; } System.out.println(); } //先序遍歷 public static void morrisPre(Node head) { if (head == null) { return; } Node cur = head; Node mostRight = null; while (cur != null) { mostRight = cur.left; if (mostRight != null) { while (mostRight.right != null && mostRight.right != cur) { mostRight = mostRight.right; } if (mostRight.right == null) { mostRight.right = cur; System.out.print(cur.value + " "); cur = cur.left; continue; } else { mostRight.right = null; } } else {//當前節點沒有左子樹的時候,直接打印(中,左)再往右 System.out.print(cur.value + " "); } cur = cur.right; } System.out.println(); } //後序遍歷 public static void morrisPos(Node head) { if (head == null) { return; } Node cur1 = head; Node cur2 = null; while (cur1 != null) { cur2 = cur1.left; if (cur2 != null) { while (cur2.right != null && cur2.right != cur1) { cur2 = cur2.right; } if (cur2.right == null) { cur2.right = cur1; cur1 = cur1.left; continue; } else {//第二次來的本身的時候 cur2.right = null; printEdge(cur1.left);//逆序打印左子樹右邊界 } } cur1 = cur1.right; } printEdge(head); System.out.println(); } public static void printEdge(Node head) { Node tail = reverseEdge(head); Node cur = tail; while (cur != null) { System.out.print(cur.value + " "); cur = cur.right; } reverseEdge(tail); } public static Node reverseEdge(Node from) { Node pre = null; Node next = null; while (from != null) { next = from.right; from.right = pre; pre = from; from = next; } return pre; } // for test -- print tree public static void printTree(Node head) { System.out.println("Binary Tree:"); printInOrder(head, 0, "H", 17); System.out.println(); } public static void printInOrder(Node head, int height, String to, int len) { if (head == null) { return; } printInOrder(head.right, height + 1, "v", len); String val = to + head.value + to; int lenM = val.length(); int lenL = (len - lenM) / 2; int lenR = len - lenM - lenL; val = getSpace(lenL) + val + getSpace(lenR); System.out.println(getSpace(height * len) + val); printInOrder(head.left, height + 1, "^", len); } public static String getSpace(int num) { String space = " "; StringBuffer buf = new StringBuffer(""); for (int i = 0; i < num; i++) { buf.append(space); } return buf.toString(); } public static void main(String[] args) { Node head = new Node(4); head.left = new Node(2); head.right = new Node(6); head.left.left = new Node(1); head.left.right = new Node(3); head.right.left = new Node(5); head.right.right = new Node(7); printTree(head); morrisIn(head); morrisPre(head); morrisPos(head); printTree(head); } }
之後但凡碰見能用遍歷解的面試題,都是扯Morris遍歷,就會讓面試官高看你一眼!(把別人弄掉,你本身上)