前面已经学习了剑指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; } }