剑指offer6&7&8&9-链表&树&栈
题目6
输入一个链表的头节点,从尾到头反过来打印出每个结点的值
思路
1.使用递归。逆序打印a->b->c->d,可以先逆序打印b->c->d(看成新的链表),再打印a;那么同样逆序可以先逆序打印c->d,再打印b;直到打印到尾节点。
2.使用栈。栈具有后进先出的特点,刚好符合逆序要求。遍历链表时将值按顺序放入栈中,最后依次出栈。
解法
class ListNode{
int m_nKey;
ListNode m_pNext;
}
//递归法
public void PrintListReversingByRecursive(ListNode listNode) {
if(listNode != null) {
if(listNode.m_pNext!=null) {
PrintListReversingByRecursive(listNode.m_pNext);
}
System.out.print(listNode.m_nKey+"\t");
}
}
//栈
public void PrintListReversingByStack(ListNode listNode) {
Stack<ListNode> stack=new Stack<ListNode>();
while(listNode != null) {
stack.push(listNode);
listNode=listNode.m_pNext;
}
while(!stack.empty()) {
System.out.print(stack.pop().m_nKey+"\t");
}
}
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> array = new ArrayList<Integer>();
if(listNode != null){
//addAll(Collection c):将集合中的所有元素添加到此列表的末尾
array.addAll(printListFromTailToHead(listNode.m_pNext));
array.add(listNode.m_nKey);
}
return array;
}
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> array = new ArrayList<Integer>();
Stack<ListNode> stack = new Stack<ListNode>();
while(listNode != null){
stack.push(listNode);
listNode = listNode.next;
}
while(!stack.isEmpty()){
array.add(stack.pop().val);
}
return array;
}
题目7
根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路
前序遍历-根左右
中序遍历-左根右
后序遍历-左右根
前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分(即在中序遍历中找到这个值,从而将中序遍历分成两部分),左部分为树的左子树中序遍历结果,右部分为右子树中序遍历结果。
然后不断根据左子树的中序和前序遍历结果构造左子树,右子树同理。
不断递归,得到结果。
解法
class BinaryTreeNode{ int m_nValue; BinaryTreeNode m_nLeft; BinaryTreeNode m_nRight; BinaryTreeNode(int m_nValue){ this.m_nValue=m_nValue; } } public BinaryTreeNode Construct(int preorder[], int inorder[]) { if(preorder==null || inorder==null) { return null; } return ConstructCore(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1); } public BinaryTreeNode ConstructCore(int preorder[], int startPreorder, int endPreorder, int inorder[], int startInorder, int endInorder) { //停止递归的条件 if(startPreorder>endPreorder || startInorder>endInorder) { return null; } //前序遍历的第一个数字是根节点 BinaryTreeNode root=new BinaryTreeNode(preorder[startPreorder]); for(int i=startInorder;i<=endInorder;i++) { if(preorder[startPreorder]==inorder[i]) { //根据左子树的前序和中序遍历,构建左子树。i-startInorder为中序排列中左子树节点的个数 root.m_nLeft=ConstructCore(preorder,startPreorder+1, startPreorder+(i-startInorder), inorder, startInorder, i-1); //根据右子树的前序和中序遍历,构建右子树 root.m_nRight=ConstructCore(preorder, startPreorder+1+(i-startInorder), endPreorder, inorder, i+1, endInorder); } } return root; }
题目8
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路
1.如果节点右子树不为空,那么该节点的下一个节点是右子树的最左节点。
2.如果右子树不为空,那么就是第一个左链接指向的树包含该节点的祖先节点。即沿着指向父节点的指针一路向上遍历,直到找到一个是它父节点的左子节点的节点。如果这个节点存在,那么这个节点的父节点就是下一个节点。
可以画出图来,找规律。
解法
public class TreeLinkNode{ int data; TreeLinkNode left=null; TreeLinkNode right=null; TreeLinkNode next=null; TreeLinkNode(int data){ this.data=data; } } public TreeLinkNode getNext(TreeLinkNode tNode) { if(tNode.right!=null) { TreeLinkNode node=tNode.right; while(node.left!=null) { node=node.left; } return node; } else { while(tNode.next!=null) { TreeLinkNode parent=tNode.next; if(parent.left==tNode) { return parent; } tNode=tNode.next; } } return null; }
题目9
用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。
思路
栈:后进先出
队列:先进先出
设置两个栈,分别用来处理入栈push操作和出栈pop操作。一个元素进入in栈后,出栈顺序被反转。那么需要保证正序出栈,就借助out栈,进行一次存放。
输入a1,a2,a3,则in栈:(栈底)a1,a2,a3(栈顶)
out栈:(栈底)a3,a2,a1(栈顶)
相当于“负负得正”-“逆逆得顺”。
解答
Stack<Integer> in=new Stack<Integer>(); Stack<Integer> out=new Stack<Integer>(); //处理入栈,直接入栈即可 public void push(int node){ in.push(node); } //处理出栈 public int pop() throws Exception{ //先将数字送入out栈,逆序一次 if(out.isEmpty()) { while(!in.isEmpty()) out.push(in.pop()); } if(out.isEmpty()) throw new Exception("queue is empty"); //从out栈出栈 return out.pop(); }
扩展
堆
堆是程序运行时申请的动态内存,而栈只是指一种使用堆的方法。
堆总是一棵完全二叉树。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
栈/堆栈
只能在栈顶一端进行插入或删除操作,top()取栈顶操作
队列
允许在列表的队尾一端进行插入,队首一端进行删除。先进先出
链表
链表中每一个元素都是一个对象,每个对象称为一个节点,包含有数据域key和指向下一个节点的指针next。通过各个节点之间的相互连接,最终串联成一个链表。