剑指Offer(61-65)

140 阅读7分钟

61、按之字形顺序打印二叉树

题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

思路1:按上一题的方式,还是用队列,用标记倒叙输出,但是有缺点

import java.util.*;

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
 /*按层序遍历分层打印的代码,添加一段判断用以倒序输出即可*/
public class Solution {   
 /**
 * 缺点:将每层的数据存进ArrayList中,偶数层时进行reverse操作,但是在海量数据时,这样效率太低了。
 */
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if(pRoot == null){
            return result;
        }
        boolean leftToRight = true;
        Queue<TreeNode> layer = new LinkedList<TreeNode>();
        ArrayList<Integer> layerList = new ArrayList<Integer>();
        layer.add(pRoot);
        int start = 0, end = 1;
        while(!layer.isEmpty()){
            TreeNode cur = layer.remove();
            layerList.add(cur.val);
            start++;
            if(cur.left!=null){
                layer.add(cur.left);          
            }
            if(cur.right!=null){
                layer.add(cur.right);
            }
            if(start == end){
                end = layer.size();
                start = 0;             
                if(!leftToRight){
                    result.add(reverse(layerList));
                }else{
                    result.add(layerList);
                }
                leftToRight = !leftToRight;
                layerList = new ArrayList<Integer>();
            }
        }
        return result;
    }

    public ArrayList reverse(ArrayList<Integer> layerList) {    
        int length = layerList.size();
        ArrayList<Integer> reverseList = new ArrayList<Integer>();
        for(int i = length-1; i >= 0;i--){
            reverseList.add(layerList.get(i));
        }
        return reverseList;
    }

}

思路1改进:用LinkedList,可以双向遍历,

Iterator iter = iter = queue.iterator();//从前往后遍历 Iterator iter = queue.descendingIterator();//从后往前遍历

import java.util.*;

public class Solution {
 /**
 * 思路:利用Java中的LinkedList的底层实现是双向链表的特点。
 *     1)可用做队列,实现树的层次遍历
 *     2)可双向遍历,奇数层时从前向后遍历,偶数层时从后向前遍历
 */
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
        if (pRoot == null) {
            return ret;
        }
        ArrayList<Integer> list = new ArrayList<>();
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.addLast(null);//层分隔符
        queue.addLast(pRoot);
        boolean leftToRight = true;

        while (queue.size() != 1) {
            TreeNode node = queue.removeFirst();//删除第一个,返回删除节点,若此节点不是分层的节点null,则添加左右子节点
            if (node == null) {//判断删除的节点是否为null,以此来判断是否到达分层隔符
                Iterator<TreeNode> iter = null;
                if (leftToRight) {
                    iter = queue.iterator();//从前往后遍历
                } else {
                    iter = queue.descendingIterator();//从后往前遍历
                }
                leftToRight = !leftToRight;
                while (iter.hasNext()) {
                    TreeNode temp = (TreeNode)iter.next();
                    list.add(temp.val);
                }
                ret.add(new ArrayList<Integer>(list));
                list.clear();
                queue.addLast(null);//添加层分隔符
                continue;//一定要continue
            }
            if (node.left != null) {
                queue.addLast(node.left);
            }
            if (node.right != null) {
                queue.addLast(node.right);
            }
        }

        return ret;
    }

}

思路2:用栈,先进后出,但是注意要用两个栈,因为如果用一个的话,本层的节点会压在最底下,此节点的子节点会放在最上边

import java.util.*;

public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
    ArrayList list=new ArrayList<ArrayList<Integer>>();
    if(pRoot==null)
        return list;
    Stack s1=new Stack();//从左到右输出,所以添加时先加右,再加左
    Stack s2=new Stack();//从右到左输出,所以添加时先加左,再加右
    s1.push(pRoot);
    ArrayList arr=new ArrayList<Integer>();      
       while(true){
           while(s1.size()!=0){
               TreeNode node=(TreeNode)s1.pop();
               arr.add(node.val);
               if(node.left!=null)
                   s2.push(node.left);
               if(node.right!=null)
                   s2.push(node.right);
           }
           list.add(arr);
           arr=new ArrayList<Integer>();
           if(s1.size()==0&&s2.size()==0)
               break;
           while(s2.size()!=0){
                TreeNode node1=(TreeNode)s2.pop();
               arr.add(node1.val);
               if(node1.right!=null)
                   s1.push(node1.right);
               if(node1.left!=null)
                   s1.push(node1.left);
           }
           list.add(arr);
           arr=new ArrayList<Integer>();
              
           if(s1.size()==0&&s2.size()==0)
               break;        
       }
           
        return list;
    }

}

62、序列化二叉树

题目描述 请实现两个函数,分别用来序列化和反序列化二叉树

思路:通过前序遍历的顺序,但是修改了一下,子节点为null的用#表示

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null; 
    public TreeNode(int val) {
        this.val = val; 
    } 
}
*/

public class Solution {
    public int index = -1;
 
     //序列化  
    public String Serialize(TreeNode root) {
        StringBuffer sb = new StringBuffer();
        if(root == null){
            sb.append("#,");
            return sb.toString();
        }
        sb.append(root.val + ",");
        sb.append(Serialize(root.left));
        sb.append(Serialize(root.right));
        return sb.toString();
  	}
    
    //反序列化 
    public TreeNode Deserialize(String str) {
        index++;//数组指数,每次移下一位
       int len = str.length();
        if(index >= len){
            return null;
        }
        String[] strr = str.split(",");
        TreeNode node = null;
        if(!strr[index].equals("#")){
            node = new TreeNode(Integer.valueOf(strr[index]));
            node.left = Deserialize(str);
            node.right = Deserialize(str);
        }        
        return node;
  }
}

63、二叉搜索树的第K个节点

题目描述 给定一颗二叉搜索树,请找出其中的第k大的结点。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。

思路:中序遍历就是二叉搜索树的排序,不用递归的程序

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;
public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        if(pRoot==null||k==0) return null;
        int t=0;
        while(pRoot!=null ||stack.size()>0){
            while(pRoot!=null){
                stack.push(pRoot);
                pRoot = pRoot.left;
            }
            if(stack.size()>0){
                pRoot= stack.pop();
                t++;
                if(t==k) return pRoot;
                pRoot= pRoot.right;
            }
        }
       return null;   
    }
}

64、数据流中的中位数

题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

import java.util.*;
public class Solution{
    private Heap maxHeap = new Heap(Heap.isMaxHeap);
    private Heap minHeap = new Heap(Heap.isMinHeap);
    /**
     * 插入有两种思路:
     * 1:直接插入大堆中,之后若两堆尺寸之差大于1(也就是2),则从大堆中弹出堆顶元素并插入到小堆中
     * 若两队之差不大于1,则直接插入大堆中即可。
     * 2:奇数个数插入到大堆中,偶数个数插入到小堆中,
     * 但是 可能会出现当前待插入的数比小堆堆顶元素大,此时需要将元素先插入到小堆,然后将小堆堆顶元素弹出并插入到大堆中
     * 对于偶数时插入小堆的情况,一样的道理。why?
     * 因为要保证最大堆的元素要比最小堆的元素都要小。
     * @param num
     */
    public void Insert(Integer num) {
        //若总尺寸为偶数,则插入大顶堆中
        if(((maxHeap.size() + minHeap.size()) & 1) == 0){
            if(minHeap.size() != 0 && num > minHeap.peek()){
                minHeap.add(num);
                maxHeap.add(minHeap.pop());
            }else{
                maxHeap.add(num);
            }
        }else{
            if(maxHeap.size() != 0 && num < maxHeap.peek()){
                maxHeap.add(num);
                minHeap.add(maxHeap.pop());
            }else{
                minHeap.add(num);
            }
        }
    }
    public Double GetMedian() {
        double res = 0.0;
        if(((maxHeap.size() + minHeap.size()) & 1) == 0){
            res = (maxHeap.peek() + minHeap.peek()) / 2.0;
        }else{
            res = maxHeap.peek();
        }
        return res;
    }
    
    //堆类,可直接设置最大堆最小堆
    class Heap {
        public List<Integer> list = null;
        public static final boolean isMaxHeap = true;
        public static final boolean isMinHeap = false;
        private boolean flag = true;  //true表示最大堆,false表示最小堆
        public Heap(){
            this.list = new ArrayList<Integer>();
        }
        public Heap(boolean flag){
            this.list = new ArrayList<Integer>();
            this.flag = flag;
        }
        //获取堆大小
        public int size(){
            return this.list.size();
        }
        //获取堆顶元素
        public int peek(){
            if(list.size() == 0) return 0;
            return list.get(0);
        }
        //插入元素,从插入点开始向上调整堆
        public void add(int val){
            this.list.add(val);
            int i = list.size() - 1, index, parent, cur;
            while(i > 0){
                index = (i - 1) / 2;
                parent = list.get(index);
                cur = list.get(i);
                if(flag == true && parent < cur){
                    swap(index, i);
                }else if(flag == false && parent > cur){
                    swap(index, i);
                }
                i = index;
            }
        }
        /**
         * 将堆顶元素取出,并重新调整堆。
         * 1>取出堆顶元素
         * 2>将最后一个元素放到堆顶
         * 3>向下调整堆
         */
        public int pop(){
            if(list.size() == 0) return -1;
            int res = list.get(0);
            list.set(0,list.get(list.size() - 1));
            list.remove(list.size()-1);
            int len = list.size() - 1 , i = 0;
            int left , right;
            while(i < len){
                left = (i << 1) + 1;
                right= (i << 1) + 2;
                int maxIndex = i;
                if(flag == true){
                    if(left < len && list.get(left) > list.get(maxIndex)) maxIndex = left;
                    if(right< len && list.get(right)> list.get(maxIndex)) maxIndex = right;
                }else{
                    if(left < len && list.get(left) < list.get(maxIndex)) maxIndex = left;
                    if(right< len && list.get(right)< list.get(maxIndex)) maxIndex = right;
                }
                if(maxIndex != i){
                    swap(maxIndex,i);
                    i = maxIndex;
                }else break;
            }
            return res;
        }
        //交换list中两个位置的元素
        public void swap(int i, int j){
            int temp = list.get(i);
            list.set(i, list.get(j));
            list.set(j,temp);
        }
    }
    
}

思路2:

import java.util.ArrayList;
import java.util.Comparator;
 
/**
 * 数据流中的中位数
 *
 * @author 过路的守望
 *
 */
public class Solution {
 
    /*
     * 最大堆
     */
    private Heap maxHeap = new Heap(new Comparator<Integer>() {
 
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }
    });
    /*
     * 最小堆
     */
    private Heap minHeap = new Heap(new Comparator<Integer>() {
 
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });
 
    /*
     * 插入元素
     */
    public void Insert(Integer num) {
        /*
         * 如果最大堆中的元素个数大于等于最小堆中的元素个数,则新元素插入到最小堆中
         */
        if (maxHeap.getSize() >= minHeap.getSize()) {
            /*
             * 如果最小堆中未插入元素,则新元素直接插入
             */
            if (minHeap.getSize() == 0) {
                minHeap.add(num);
            }
            /*
             * 若最小堆中已存在元素,则将带插入元素与最大堆中的堆顶元素相比较,若待插入元素较小,则将最大堆堆顶元素弹出后添加到最小堆中,
             * 最大堆中将待插入元素添加进去。
             */
            else if (num < maxHeap.peek()) {
                minHeap.add(maxHeap.pop());
                maxHeap.add(num);
            }
            /*
             * 待插入元素不小于最大堆堆顶元素
             */
            else {
                minHeap.add(num);
            }
        } else {
            /*
             * 如果最大堆中未插入元素,则新元素直接插入
             */
            if (maxHeap.getSize() == 0) {
                maxHeap.add(num);
            }
            /*
             * 若最大堆中已存在元素,则将带插入元素与最小堆中的堆顶元素相比较,若待插入元素较大,则将最小堆堆顶元素弹出后添加到最大堆中,
             * 最小堆中将待插入元素添加进去。
             */
            else if (num > minHeap.peek()) {
                maxHeap.add(minHeap.pop());
                minHeap.add(num);
            }
            /*
             * 待插入元素不大于最小堆堆顶元素
             */
            else {
                maxHeap.add(num);
            }
        }
    }
 
    /*
     * 得到中间值
     */
    public Double GetMedian() {
        /*
         * 判断数据数目奇偶
         */
        int size = maxHeap.getSize() + minHeap.getSize();
        if ((size & 1) == 1) {
            return (double) minHeap.peek();
        }
        return (maxHeap.peek() + minHeap.peek()) / 2.0;
    }
 
}
 
/**
 * 数据结构-堆
 *
 * @author 过路的守望
 *
 */
class Heap {
 
    /*
     * 比较器
     */
    private Comparator<Integer> comparator;
    private ArrayList<Integer> list;
 
    public Heap() {
        list = new ArrayList<Integer>();
    }
 
    /*
     * 构造器传入比较器
     */
    public Heap(Comparator<Integer> comparator) {
        this();
        this.comparator = comparator;
    }
 
    /*
     * 弹出堆顶元素
     */
    public int pop() {
        int data = list.get(0);
        list.set(0, list.remove(list.size() - 1));
        percolateDown();
        return data;
    }
 
    /*
     * 返回堆顶元素
     */
    public int peek() {
        return list.get(0);
    }
 
    /*
     * 添加元素
     */
    public void add(int element) {
        list.add(element);
        percolateUp();
        return;
    }
 
    /*
     * 堆中对象个数
     */
    public int getSize() {
        return list.size();
    }
 
    /*
     * 下滤操作
     */
    private void percolateDown() {
        int size = list.size();
        /*
         * 从下标为0的节点开始下滤
         */
        int i = 0;
        /*
         * temp保存最大堆或最小堆的堆顶值
         */
        int temp = list.get(0);
        int leftChild = getLeftChild(i);
        while (leftChild < size) {
            /*
             * 得到左右儿子中较大的下标
             */
            if (leftChild < size - 1
                    && comparator.compare(list.get(leftChild),
                            list.get(leftChild + 1)) < 0) {
                leftChild++;
            }
            /*
             * 若儿子大于父亲,就把 儿子的值赋给父亲
             */
            if (comparator.compare(temp, list.get(leftChild)) < 0) {
                list.set(i, list.get(leftChild));
                i = leftChild;
                leftChild = getLeftChild(i);
                continue;
            } else {
                break;
            }
        }
        /*
         * 下滤完成,找到temp所在下标
         */
        list.set(i, temp);
    }
 
    /*
     * 上滤操作
     */
    private void percolateUp() {
        /*
         * 从堆中最后一个元素开始上滤
         */
        int i = list.size() - 1;
        int temp = list.get(i);
        int parent = getParent(i);
        while (parent >= 0) {
            /*
             * 若父亲小于儿子,则把父亲的值赋给儿子
             */
            if (comparator.compare(temp, list.get(parent)) > 0) {
                list.set(i, list.get(parent));
                i = parent;
                parent = getParent(parent);
                continue;
            } else {
                break;
            }
        }
        /*
         * 上滤完成,找到temp所在下标
         */
        list.set(i, temp);
    }
 
    /*
     * 左儿子下标
     */
    private int getLeftChild(int i) {
        return 2 * i + 1;
    }
 
    /*
     * 父亲下标
     */
    private int getParent(int i) {
        return (int) Math.floor((i - 1) / 2.0);
    }
}

65、滑动窗口的最大值

题目描述 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

import java.util.*;
/**
 * 题目:滑动窗口的最大值
 * 思路:滑动窗口应当是队列,但为了得到滑动窗口的最大值,队列序可以从两端删除元素,因此使用双端队列。
 *     原则:
 *     对新来的元素k,将其与双端队列中的元素相比较
 *     1)前面比k小的,直接移出队列(因为不再可能成为后面滑动窗口的最大值了!),
 *     2)前面比k大的X,比较两者下标,判断X是否已不在窗口之内,不在了,直接移出队列
 *     队列的第一个元素是滑动窗口中的最大值
 */
public class Solution {
     
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
         
        ArrayList<Integer> ret = new ArrayList<>();
        if (num == null) {
            return ret;
        }
        if (num.length < size || size < 1) {
            return ret;
        }
        // 用来保存可能是滑动窗口最大值的数字的下标 
        LinkedList<Integer> indexDeque = new LinkedList<>();
        for (int i = 0; i < size - 1; i++) {
            // 如果已有数字小于待存入的数据,
                // 这些数字已经不可能是滑动窗口的最大值
                // 因此它们将会依次地从队尾删除
            while (!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(i);
        }
         
        for (int i = size - 1; i < num.length; i++) {
            // 如果已有数字小于待存入的数据,
                // 这些数字已经不可能是滑动窗口的最大值
                // 因此它们将会依次地从队尾删除
            while (!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(i);
            if (i - indexDeque.getFirst() + 1 > size) {
                indexDeque.removeFirst();
            }
            ret.add(num[indexDeque.getFirst()]);
        }
        return ret;
    }
}

声明:此文章为本人原创,如有转载,请注明出处

AI探索之路