剑指offer3-5—Java版

3.从尾到头打印链表

3.1题目描述:

  输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

1.2思路介绍:

1.2.1非递归实现:

  listNode 是链表,只能从头遍历到尾,但是输出却要求从尾到头,这是典型的"先进后出",我们可以想到栈!
ArrayList 中有个方法是 add(index,value),可以指定 index 位置插入 value 值
  所以我们在遍历 listNode 的同时将每个遇到的值插入到 list 的 0 位置,最后输出 listNode 即可得到逆序链表

1.2.2代码展示:
import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        ListNode tmp = listNode;
        while(tmp!=null){
            list.add(0,tmp.val);
            tmp = tmp.next;
        }
        return list;
    }
}
1.2.3复杂度分析:

时间复杂度:O(n)
空间复杂度:O(n)

1.2.4递归实现:

可以利用递归,借助系统的"栈"帮忙打印

1.2.5代码展示:
import java.util.*;
public class Solution {
    ArrayList<Integer> list = new ArrayList();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode!=null){
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }
        return list;
    }
}
1.2.6复杂度分析:

时间复杂度:O(n)
空间复杂度:O(n)

4.重建二叉树

4.1题目描述:

  输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回

4.2思路介绍:

  根据中序遍历和前序遍历可以确定二叉树,具体过程为:

  1.根据前序序列第一个结点确定根结点
  2.根据根结点在中序序列中的位置分割出左右两个子序列
  3.对左子树和右子树分别递归使用同样的方法继续分解

  例如:
  前序序列{1,2,4,7,3,5,6,8} = pre
  中序序列{4,7,2,1,5,3,8,6} = in
  1.根据当前前序序列的第一个结点确定根结点,为 1
  2.找到 1 在中序遍历序列中的位置,为 in[3]
  3.切割左右子树,则 in[3] 前面的为左子树, in[3] 后面的为右子树,则切割后的左子树前序序列为:{2,4,7},切割后的左子树中序序列为:{4,7,2};切割后的右子树前序序列为:{3,5,6,8},切割后的右子树中序序列为:{5,3,8,6}
  4.对子树分别使用同样的方法分解

4.3代码展示:

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
import java.util.Arrays;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if (pre.length == 0 || in.length == 0) {
            return null;
        }
//前序遍历的第一个节点就是二叉树的根节点
        TreeNode root = new TreeNode(pre[0]);
        // 在中序中找到前序的根
        for (int i = 0; i < in.length; i++) {
            if (in[i] == pre[0]) {
                // 左子树,注意 copyOfRange 函数,左闭右开
                root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
                // 右子树,注意 copyOfRange 函数,左闭右开
                root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
                break;
            }
        }
        return root;
    }
}

4.4复杂度分析:

时间复杂度:O(n)
空间复杂度:O(n)

5.用两个栈实现队列

5.1题目描述:

  用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

5.2思路介绍:

  listNode 队列的特性是:“先入先出”,栈的特性是:“先入后出”

  当我们向模拟的队列插入数 a,b,c 时,假设插入的是 stack1,此时的栈情况为:

  • 栈stack1:{a,b,c}
  • 栈 stack2:{}
      当需要弹出一个数,根据队列的"先进先出"原则,a 先进入,则 a 应该先弹出。但是此时 a 在 stack1 的最下面,将 stack1 中全部元素逐个弹出压入 stack2,现在可以正确的从 stack2 中弹出 a,此时的栈情况为:
  • 栈 stack1:{}
  • 栈 stack2:{c,b}

  继续弹出一个数,b 比 c 先进入"队列",b 弹出,注意此时 b 在 stack2 的栈顶,可直接弹出,此时的栈情况为:

  • 栈 stack1:{}
  • 栈 stack2:{c}

此时向模拟队列插入一个数 d,还是插入 stack1,此时的栈情况为:

  • 栈 stack1:{d}
  • 栈 stack2:{c}

弹出一个数,c 比 d 先进入,c 弹出,注意此时 c 在 stack2 的栈顶,可直接弹出,此时的栈情况为:

  • 栈 stack1:{d}
  • 栈 stack2:{c}

根据上述例子可得出结论:

  当插入时,直接插入 stack1
  当弹出时,当 stack2 不为空,弹出 stack2 栈顶元素,如果 stack2 为空,将 stack1 中的全部数逐个出栈入栈 stack2,再弹出 stack2 栈顶元素

5.3代码展示:

import java.util.Stack;
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

    public void push(int node) {
        stack1.push(node);
    }

    public int pop() {
        if (stack2.size() <= 0) {
            while (stack1.size() != 0) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

5.4复杂度分析:

时间复杂度:O(1)
空间复杂度:O(1)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。