二叉树的相关题目
随时找到数据流的中位数
【题目】
有一个源源不断地吐出整数的数据流,假设你有足够的空间来保存吐出的数。请设计一个名叫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);
}
该结构比普通二叉树节点结构多了一个指向父节点的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;
}
【思路】
先看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;
}