前面已經學習了劍指offer(一),今天來學習如下幾道《劍指offer》裏有關數據結構的算法題node
從尾到頭打印鏈表
用兩個棧實現隊列
重建二叉樹
二叉樹的下一個節點
刪除鏈表的節點
刪除鏈表中重複的節點
輸入個鏈表的頭結點,從尾到頭反過來打印出每一個結點的值。
很容易能夠想到使用棧
或者遞歸
來實現算法
public class Test { public static class ListNode{ int val; ListNode next; } //—---------使用棧---------- public static void printListReverseUsStack(ListNode root){ Stack<ListNode> stack=new Stack<>(); while(root!=null){ stack.push(root); root=root.next; } ListNode temp; while(!stack.isEmpty()){ temp=stack.pop(); System.out.print(temp.val+" "); } } //—---------使用遞歸------------- public static void printListReverseUsRecursion(ListNode root){ if (root!=null) { if (root.next!=null) { printListReverseUsDiGui(root.next); } } System.out.print(root.val+" "); } }
用兩個棧實現一個隊列。隊列的聲明以下,請實現它的兩個函數appendTail 和deleteHead, 分別完成在隊列尾部插入結點和在隊列頭部刪除結點的功能。
解題思路
(1)插入:將元素壓入到stack1中。
(2)刪除:當stack2不爲空時,彈出stack2的棧頂元素。 當stack2爲空時,將stack1的元素逐個彈出並壓入到stack2中,而後彈出棧頂元素。segmentfault
public class CQueue<T>{ private Stack<T> stack1=new Stack<>(); private Stack<T> stack2=new Stack<>(); //尾部添加 public void appendTail(T t){ stack1.push(t); } //刪除頭部 public T deleteHead(){ if (stack2.isEmpty()){ while (!stack1.isEmpty()){ stack2.push(stack1.pop()); } } if (stack2.isEmpty()) throw new RuntimeException("No Data!"); return stack2.pop(); } }
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。
解題思路
(1)前序遍歷的第一個數字就是根結點的值。
(2)掃描中序遍歷中根結點的位置,根結點左邊的值爲左子樹結點的值,右邊的爲右子樹結點的值。
(3)既然咱們已經分別找到了左、右子樹的前序遍歷和中序遍歷,接下來能夠遞歸地構建左、右子樹。數組
public class BinaryTree { public static class BinaryTreeNode { int value; BinaryTreeNode left; BinaryTreeNode right; } //preorder和inorder分別爲前序遍歷和後序遍歷 public static BinaryTreeNode construct(int[] preorder, int[] inorder) { // 輸入的合法性判斷,兩個數組都不能爲空,而且都有數據,並且數據的數目相同 if (preorder == null || inorder == null || preorder.length != inorder.length || inorder.length < 1) { return null; } return construct(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); } /** * @param preorder 前序遍歷 * @param ps 前序遍歷的開始位置 * @param pe 前序遍歷的結束位置 * @param inorder 中序遍歷 * @param is 中序遍歷的開始位置 * @param ie 中序遍歷的結束位置 * @return 樹的根結點 */ public static BinaryTreeNode construct(int[] preorder, int ps, int pe, int[] inorder, int is, int ie) { // 開始位置大於結束位置說明已經沒有須要處理的元素了 if (ps > pe) { return null; } // 取前序遍歷的第一個數字,就是當前的根結點 int value = preorder[ps]; int index = is; // 在中序遍歷的數組中找根結點的位置 while (index <= ie && inorder[index] != value) { index++; } // 若是在整個中序遍歷的數組中沒有找到,說明輸入的參數是不合法的,拋出異常 if (index > ie) { throw new RuntimeException("Invalid input"); } // 建立當前的根結點,而且爲結點賦值 BinaryTreeNode node = new BinaryTreeNode(); node.value = value; // 遞歸構建當前根結點的左子樹,左子樹的元素個數:index-is個 // 左子樹對應的前序遍歷的位置在[ps+1, ps+index-is] // 左子樹對應的中序遍歷的位置在[is, index-1] node.left = construct(preorder, ps + 1, ps + index - is, inorder, is, index - 1); // 遞歸構建當前根結點的右子樹,右子樹的元素個數:ie-index個 // 右子樹對應的前序遍歷的位置在[ps+index-is+1, pe] // 右子樹對應的中序遍歷的位置在[index+1, ie] node.right = construct(preorder, ps + index - is + 1, pe, inorder, index + 1, ie); return node; } }
給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點而且返回。 注意,樹中的結點不只包含左右子結點,同時包含指向父結點的指針。
解題思路
(1)若是一個結點有右子樹,那麼它的下一個結點就是它的右子樹的最左子結點。 例如,圖中結點b的下一個結點是h。
(2)接着咱們分析一下結點沒有右子樹的情形。若是結點是它父結點的左子結點,那麼它的下一個結點就是它的父結點。 例如,結點d的下一個結點是b。
(3)若是一個結點既沒有右子樹,而且它仍是父結點的右子結點,這種情形就比較複雜。 咱們能夠沿着指向父結點的指針一直向上遍歷,直到找到一個是它父結點的左子結點的結點。 若是這樣的結點存在,那麼這個結點的父結點就是咱們要找的下一個結點。 例如結點i的下一個結點是a。數據結構
public class Solution { public class BinaryTreeNode { int val; TreeLinkNode left; TreeLinkNode right; TreeLinkNode parent; } public TreeLinkNode getNext(BinaryTreeNode pNode){ BinaryTreeNode curNode; //第一步:判斷是否有右孩子 if(pNode.right != null){ curNode = pNode.right; while(curNode.left != null) curNode = curNode.left; return curNode; } //第二步:判斷是不是其父節點的左孩子 if(pNode.parent == null) return null; if(pNode == pNode.parent.left){ return pNode.parent; } //第三步:向上找其父節點,直到父節點是其父節點的的左子節點 curNode = pNode.parent; while(curNode.parent != null){ if(curNode == curNode.parent.left){ return curNode.parent; } //繼續向上找父節點 curNode = curNode.parent; } return null; } }
給定單向鏈表的頭指針和一個節點指針,定義一個函數在O(1)時間內刪除該節點。
在單向鏈表中刪除節點最常規的作法無疑是從頭節點開始,順序遍歷查找要刪除的節點,而後刪除該節點,這種解法須要O(N)時間。
問題來了?刪除節點難道必須知道待刪除節點的前一個節點才行嗎?
其實只要知道待刪除節點的下一個節點,咱們就能夠刪除節點。app
解題思路
假如咱們要刪除節點i,先把i的下一個節點j的內容複製到i,而後把i的指針指向節點j的下一個節點。此時再刪除節點j,其效果恰好是把節點i刪除了。函數
public class Test18 { //節點類 public static class ListNode { int value; ListNode next; } public static void deleteNode(ListNode head, ListNode target) { if (head == null || target == null) return; //要刪除的節點不是尾節點 if (target.next != null) { ListNode next = target.next; target.value = next.value; target.next = next.next; next.next = null; } //鏈表只有一個節點 else if (head == target) { head = null; target = null; } //鏈表中有多個節點,刪除尾節點 else { ListNode node = head; while (node.next != target) { node = node.next; } node.next = null; target = null; } } }
在一個排序的鏈表中刪除重複的節點,好比1->2->3->3->4->4->5刪除後變成1->2->5。
解題思路
解決這個問題的第一步是肯定刪除的參數。固然這個函數須要輸入待刪除鏈表的頭結點。頭結點可能與後面的結點重複,也就是說頭結點也可能被刪除,因此在鏈表頭添加一個頭指針
(虛擬頭結點)。學習
接下來咱們從頭遍歷整個鏈表。若是當前結點的值與下一個結點的值相同,那麼它們就是重複的結點,均可以被刪除。爲了保證刪除以後的鏈表仍然是相連的而沒有中間斷開,咱們要把當前的前一個結點和後面值比當前結點的值要大的結點相連。咱們要確保前一個節點要始終與下一個沒有重複的結點鏈接在一塊兒。ui
public class Test18 { public static class ListNode { int val; ListNode next; } public ListNode deleteDuplication(ListNode head) { if (head == null) return null; //生成頭指針 ListNode root = new ListNode(); root.next = head; //記錄前驅結點 ListNode prev = root; //記錄當前處理的結點 ListNode node = head; while (node != null && node.next != null) { //有重複結點,與node值相同的結點都要刪除 if (node.val == node.next.val) { //這裏的node指向最後一個重複的節點 while (node.next != null && node.next.val == node.val) { node = node.next; } //前一個節點要始終與下一個沒有重複的結點鏈接在一塊兒 prev.next = node.next; } //不重複則向後遍歷 else { prev.next = node; prev = prev.next; } node = node.next; } //返回頭結點 return root.next; } }