二叉树的相关题目

随时找到数据流的中位数

【题目】

有一个源源不断地吐出整数的数据流,假设你有足够的空间来保存吐出的数。请设计一个名叫MedianHolder的结构,MedianHolder可以随时取得之前吐出所有数的中位数。

【要求】

1.如果MedianHolder已经保存了吐出的N个数,那么任意时刻将一个新数加入到MedianHolder的过程,其时间复杂度是O(logN)。

2.取得已经吐出的N个数整体的中位数的过程,时间复杂度为O(1)。

优先级队列PriorityQueue:底层实现为堆结构,默认为小根堆,变成大根堆需要手写比较器。增删都是logN,弹出元素会弹出最小值。

【思路】

使用优先级队列这个系统提供的数据结构(其实就是大根堆和小根堆),创建一个大根堆和一个小根堆,目标是让整个数据流,在有序状态下的前n/2个数进大根堆,后n/2个数进小根堆,这样将两堆顶元素弹出,两堆顶元素必是有序数据流的中间位置。可以直接获取或计算中位数。

怎么做到?进来一个数默认加入大根堆,当后续数据比当前大根堆顶元素小时进大根堆,否则进小根堆,进完后计算两堆的数据量差值,如果超过1(不包含1),则将储量较大的堆出堆顶元素进入另一个堆。如此循环即可。

【Code】

public static class Median
    {
        private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new MaxHeapComparator());
        private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(new MinHeapComparator());
        //调整两堆的大小。
        private void modifyTwoHeapSize() {
            //如果大根堆较大,则出堆顶元素进入小根堆,反之亦然
            if(this.maxHeap.size()==this.minHeap.size()+2)
                this.minHeap.add(this.maxHeap.poll());
            if(this.minHeap.size()==this.maxHeap.size()+2)
                this.maxHeap.add(this.minHeap.poll());
        }
        public void addNum(int num)
        {
            //一开始默认将数丢进大根堆
            if(this.maxHeap.isEmpty())
            {
                this.maxHeap.add(num);
                return;
            }
            if(this.maxHeap.peek()>=num)
            {
                this.maxHeap.add(num);
            }
            else {
                if(this.minHeap.isEmpty())
                    this.minHeap.add(num);
                else {//丢进小根堆之前要与小根堆堆顶元素对比
                    if(this.minHeap.peek() > num)
                        this.maxHeap.add(num);
                    else {
                        this.minHeap.add(num);
                    }
                }
            }
            modifyTwoHeapSize();
        }
        
        public Integer GetMedian()
        {
            int maxSize = this.maxHeap.size();
            int minSize = this.minHeap.size();
            if(maxSize+minSize==0)
                return null;
            Integer maxMedian = this.maxHeap.peek();
            Integer minMedian = this.minHeap.peek();
            if ( (maxMedian+minSize) %1==0 )//序列是否为偶数
                return (maxMedian+minMedian)/2;
            return maxSize>minSize?maxMedian:minMedian;
        }
    }
    //写优先级队列排序的比较函数
    public static class MaxHeapComparator implements Comparator<Integer> {
        public int compare(Integer o1,Integer o2)
        {
            if(o2>o1)
                return 1;
            else {
                return-1;
            }
        }
    }
    public static class MinHeapComparator implements Comparator<Integer> {
        public int compare(Integer o1,Integer o2) {
            if(o2<o1)
                return 1;
            else {
                return -1;
            }
        }
    }

哈夫曼编码问题:

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60.金条要分成10,20,30三个部分。如果,先把长度60的金条分成10和50,花费60再把长度50的金条分成20和30,花费50一共花费110铜板。但是如果,先把长度60的金条分成30和30,花费60再把长度30金条分成10和20,花费30一共花费90铜板。输入一个数组,返回分割的最小代价。

【思路】

使用小根堆结构,将数组元素都丢进小根堆容器,形成小根堆,每次从小根堆里弹出两个元素,分别为最小元素和次小元素,合成一个新的节点再压入小根堆中。此时,将两元素的和进行累加,最终该累加结果就是分割金条的最小代价。循环往复,直到小根堆里只有一个节点即达到中止条件。

【Code】

public static int lessSpend(int[] arr)
    {
        PriorityQueue<Integer> p = new PriorityQueue<Integer>(new MinHeapComparator());
        for(int i = 0;i<arr.length;i++)
        {
            p.add(arr[i]);
        }
        int sum = 0,cur = 0;
        while(p.size()>1)
        {
            //每次从小根堆弹出两元素合并成一个新元素再压回小根堆,累加其和
            cur = p.poll()+p.poll();
            sum = sum+cur;
            p.add(cur);
        }
        return sum;
    }

输入:

参数1,正数数组costs

参数2,正数数组profits

参数3,正数k

参数4,正数m

costs[i]表示i号项目的花费

profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润)

k表示你不能并行、只能串行的最多做k个项目

m表示你初始的资金

说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。

输出:

你最后获得的最大钱数。

【思路】

使用一个大根堆和一个小根堆,将每个项目的花费都丢进小根堆,依次从小根堆堆顶取元素与当前资金进行比较,如小于当前资金证明该项目可做,将该项目对应的利润丢入大根堆。取到小根堆顶不比当前资金小时停止,此时取大根堆顶元素,将利润加入当前资金,再重复从小根堆顶取元素与当前元素比较的操作,直至做到k个项目为止。

【Code】

public static int GetMaxProfit(int k,int m,int[] costs,int[] profits)
    {
        //将每个方案的花费和利润组合放入指针数组存放
        Node[] nodes = new Node[costs.length];
        for(int i = 0;i<costs.length;i++)
        {
            nodes[i] = new Node(costs[i], profits[i]);
        }
        PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinHeapComparatorByThree());
        PriorityQueue<Node>maxProfitsHeap = new PriorityQueue<>(new MaxHeapComparatorByThree());
        //先将全部方案按花费最小排序方式压入小根堆,此时堆顶会是花费最小的那个方案指针
        for(int i =0;i<nodes.length;i++)
            minCostQ.add(nodes[i]);
        //最多做K个项目,循环K次
        for(int i = 0;i<k;i++)
        {
            //当待选方案堆里不为空且堆顶花费小于当前资金量,即当前资金能够做的项目都将其出小根堆压入利润大根堆
            while(!minCostQ.isEmpty()&&minCostQ.peek().c<=m)
            {
                maxProfitsHeap.add(minCostQ.poll());
            }
            //当利润大根堆为空,即此时的资金量无任何方案可做时终止
            if(maxProfitsHeap.isEmpty())
                return m;
            //选择可做方案内利润最大的方案,他已自动存放在大根堆堆顶
            m+=maxProfitsHeap.poll().p;
        }
        return m;
    }
    public static class MaxHeapComparatorByThree implements Comparator<Node> {
        public int compare(Node o1,Node o2)
        {
            if(o2.p>o1.p)
                return 1;
            else {
                return-1;
            }
        }
    }
    public static class MinHeapComparatorByThree implements Comparator<Node> {
        public int compare(Node o1,Node o2) {
            if(o2.c<o1.c)
                return 1;
            else {
                return -1;
            }
        }
    }

折纸问题

【题目】

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。给定一个输入参数N,代表纸条都从下边向上方连续对折N次,请从上到下打印所有折痕的方向。

例如:N=1时,打印:down

N=2时,打印:down down Up 

【思路】

二叉树的基础内容。第一次折时折痕是下,第二次折时该折痕上边是下,下边是上。再折时,都会对每个已有折痕产生下、上两新折痕。可构建二叉树如下。

  下

     /  \

    下 上

   /\  /\

 下上下上

观察可以发现,构建出的二叉树的中序遍历即为答案。

【Code】 

public static void printPaperFolds(int N)
    {
        print(1,N,true);
    }
    public static void print(int i,int N,boolean down)
    {
        if(i>N)
            return;
        //类似中序遍历 true代表折痕为down false代表折痕为up
        print(i+1, N, true);
        System.out.println(down?"down":"up");
        print(i+1, N, false);
    }
在二叉树中找到一个节点的后继节点
【题目】
现在有一种新的二叉树节点类型如下:
public class Node {
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int data) {
this.value = data;
}
}

该结构比普通二叉树节点结构多了一个指向父节点的parent指针。假设有一Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向null。只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数。在二叉树的中序遍历的序列中,node的下一个节点叫作node的后继节点。

【思路】

想找某个节点的后继:

如果该节点有右子树,则其后继一定是其右子树下最左的左子树。

如果该节点没有右子树:

  且是父节点的左孩子时,父节点就是他的后继。

  是父节点的右孩子时,往上找节点,判断该找到的节点如果是其父节点的左子树,其父节点就是想要找的后继。

【Code】 

public static class nodeParent
    {
        public int value;
        public nodeParent parent;
        public nodeParent left;
        public nodeParent right;
        public nodeParent(int value)
        {
            this.value = value;
        }
    }
    public static nodeParent getNextNode(nodeParent node)
    {
        if(node==null)
            return null;
        //如果该节点有右子树,则其后继一定是右子树的最左节点
        if(node.right!=null)
            return getLeftMost(node.right);
        //如果该节点没有右子树
        else {
            nodeParent parent = node.parent;
            //往上找到某节点是其父节点的左孩子时,该父节点即为所求后继
            while(parent!=null && parent.left!=node)
            {
                node=parent;
                parent = node.parent;
            }
            return parent;
        }
        
    }
    //找到传入节点的最左子树
    public static nodeParent getLeftMost(nodeParent node)
    {
        if(node == null)
            return node;
        while(node.left!=null)
        {
            node=node.left;
        }
        return node;
    }
在数组中找到一个局部最小的位置
【题目】
定义局部最小的概念。arr长度为1时,arr[0]是局部最小。arr的长度为N(N>1)时,如果arr[0]<arr[1],那么arr[0]是局部最小;如果arr[N-1]<arr[N-2],那么arr[N-1]是局部最小;如果0<i<N-1,既有
arr[i]<arr[i-1],又有arr[i]<arr[i+1],那么arr[i]是局部最小。给定无序数组arr,已知arr中任意两个相邻的数都不相等。写一个函数,只需返回arr中任意一个局部最小出现的位置即可。

【思路】

  先看0位置和n-1位置是否满足局部最小,如果是则直接返回,如无,则二分,判断中间位置,如果不满足且如果中间位置比左边大的话对0-中间进行二分,比右边大的话就对中间-n-1二分,总能找到满足题意的局部最小点。

  二分不一定要在有序下才能使用,只要存在某一边有你想要的数据,都可以二分去搜查。

【Code】

public int findlessMin(int[] arr) {
        if(arr.length==0||arr==null)
            return -1;
        //查头尾元素是否满足
        if(arr.length==1||arr[0]<arr[1])
            return 0;
        if(arr[arr.length-1]<arr[arr.length-2])
            return arr.length-1;
        int left = 1;
        int right = arr.length-2;
        int mid = 0;
        while(left<right)
        {
            mid = (left+right)/2;
            if(arr[mid]>arr[mid-1])
                right = mid-1;
            else if (arr[mid]>arr[mid+1]) {
                left = mid+1;
            }
            else {
                return mid;
            }
        }
        return left;
    }
posted @ 2021-02-03 21:36  γGama  阅读(100)  评论(0编辑  收藏  举报