一、随时找到数据流的中位数
有一个源源不断地吐出整数的数据流,假设你有足够的空间来保存吐出的数。请设计一个名叫MedianHolder的结构,MedianHolder可以随时取得之前吐出所有数的中位数。
package FaceQuestion.二叉树; import java.util.Comparator; import java.util.PriorityQueue; /** * 随时找到数据流的中位数 * 思路: * 利用一个大根堆和一个小根堆去保存数据,保证前一半的数放在大根堆,后一半的数据放在小根堆中 * 在添加数据的时候,不断的调整两个堆的大小,使两者保持平衡 * 要取得的中位数就是两个堆堆顶的元素 */ public class MadianQuick { public static class MedianHolder { private PriorityQueue<Integer> maxQueue=new PriorityQueue<>(new MaxComparator()); private PriorityQueue<Integer> minQueue=new PriorityQueue<>(new MinComparator()); /** * 调整堆的大小 * 当两个堆的大小差值变大时,从数据多的堆中弹出一个数据进入另一个堆中 */ public void modifyHeapSize(){ if (maxQueue.size()==minQueue.size()+2){ minQueue.add(maxQueue.poll()); } if (minQueue.size()==maxQueue.size()+2){ maxQueue.add(minQueue.poll()); } } /** * 添加数据的过程 * @param num */ public void addNum(Integer num){ if (maxQueue.isEmpty()){ maxQueue.add(num); } if (this.maxQueue.peek() >= num) { this.maxQueue.add(num); } else { if (this.minQueue.isEmpty()) { this.minQueue.add(num); return; } if (this.minQueue.peek() > num) { this.maxQueue.add(num); } else { this.minQueue.add(num); } } modifyHeapSize(); } /** * 获取中位数 * @return */ public Integer getMedian(){ int maxHeapSize=this.maxQueue.size(); int minHeapSize=this.minQueue.size(); if (maxHeapSize+minHeapSize==0){ return null; } Integer maxHeapHead=this.maxQueue.peek(); Integer minHeapHead=this.minQueue.peek(); if (((maxHeapHead+minHeapHead)&1)==0){ return (maxHeapHead+minHeapHead)/2; } return maxHeapSize>minHeapSize?maxHeapHead:minHeapHead; } /** * 大根堆比较器 */ class MaxComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { if (o2>o1){ return 1; }else { return -1; } } } /** * 小根堆比较器 */ class MinComparator implements Comparator<Integer>{ @Override 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铜板。
package FaceQuestion.二叉树; import java.util.Comparator; import java.util.PriorityQueue; /** * 一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的 * 金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金 * 条,怎么分最省铜板? * 思路: * 利用小根堆,将需要划分的范围存储到小根堆中,每次从小根堆中弹出两个元素,将它们相加之后记录下 * 然后将记录值再扔回堆中,再次返回两个值,一直递归,直到堆中无元素,记录值的和就是答案 */ public class LessMoney { public static int lessMoney(int[] arr){ PriorityQueue<Integer> pQ=new PriorityQueue<>(new MinheapComparator()); for (int i=0;i<arr.length;i++){ pQ.add(arr[i]); } int sum=0; int cur=0; while (pQ.size()>1){ cur=pQ.poll()+pQ.poll(); sum+=cur; pQ.add(cur); } return sum; } public static class MinheapComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } } }
三、输入:
参数1,正数数组costs,参数2,正数数组profits,参数3,正数k,参数4,正数m,costs[i]表示i号项目的花费,profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润),k表示你不能并行、只能串行的最多做k个项目,m表示你初始的资金
说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。
输出:你最后获得的最大钱数。
package FaceQuestion.二叉树; import java.util.Comparator; import java.util.PriorityQueue; /** * 做项目问题,你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。 * 思路: * 创建一个大根堆和一个小根堆; * 首先将每个项目按照成本存储到小根堆中,和初始成本比较,比初始成本小的项目弹出 * 然后按照利润存储到大根堆中,然后每次选择从大根堆中弹出元素,将利润加到初始成本中 * 初始成本改变之后,再次从小根堆中选择元素弹出 */ public class IPO { /** * 项目类 * p:项目利润 * c:项目成本 */ public static class Node{ public int p; public int c; public Node(int p,int c){ this.p=p; this.c=c; } } /** * 成本小根堆比较器 */ public static class MinCostComparator implements Comparator<Node> { @Override public int compare(Node o1, Node o2) { return o1.c - o2.c; } } /** * 利润大根堆比较器 */ public static class MaxProfitComparator implements Comparator<Node> { @Override public int compare(Node o1, Node o2) { return o2.p - o1.p; } } /** * 找到利润最大的方案 * @param k 规定完成的项目数 * @param W 初始资金 * @param profits 利润数组 * @param Capital 成本数组 * @return */ public static int findMaximizedCapital(int k,int W,int[] profits,int[] Capital){ Node[] nodes=new Node[profits.length]; //初始化所有项目 for (int i=0;i<profits.length;i++){ nodes[i]=new Node(profits[i],Capital[i]); } //成本小根堆 PriorityQueue<Node> minCosts=new PriorityQueue<>(new MinCostComparator()); //利润大根堆 PriorityQueue<Node> maxProfits=new PriorityQueue<>(new MaxProfitComparator()); //一开始将所有元素存储到成本小根堆中 for (int i=0;i<nodes.length;i++){ minCosts.add(nodes[i]); } //找到成本符合要求的项目,按照利润存储到大根堆中 for (int i=0;i<k;i++){ while (!minCosts.isEmpty()&&minCosts.peek().c<=W){ maxProfits.add(minCosts.poll()); } if (maxProfits.isEmpty()){ return W; } W+=maxProfits.poll().p; } return W; } }四、折纸问题
【题目】
请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。给定一个输入参数N,代表纸条都从下边向上方连续对折N次,请从上到下打印所有折痕的方向。
package FaceQuestion.二叉树; /** * 折纸问题 * 请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次, * 压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。 * 如果从纸条的下边向上方连续对折2次,压出折痕后展开, * 此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。 * 给定一个输入参数N,代表纸条都从下边向上方连续对折N次,请从上到下打印所有折痕的方向。 * * 思路:纸的折痕规律符合二叉树的中序遍历 */ public class PaperFolding { /** * 打印所有折痕 * @param N */ public static void printAllFolds(int N) { printProcess(1, N, true); } public static void printProcess(int i, int N, boolean down) { if (i > N) { return; } printProcess(i + 1, N, true); System.out.println(down ? "down " : "up "); printProcess(i + 1, N, false); } public static void main(String[] args) { int N = 4; printAllFolds(N); } }
五、二叉树先序、中序,后序遍历的非递归实现
package FaceQuestion.二叉树; import java.util.Stack; /** * 二叉树先序、中序,后序遍历的非递归实现 */ public class PreInPosTraversal { /** * 二叉树节点类 */ public static class Node{ public int value; public Node left; public Node right; public Node(int data){ this.value=data; } } /** * 二叉树前序遍历递归实现 * @param head */ public static void preOrderRecur(Node head){ if (head==null){ return; } System.out.println(head.value+" "); preOrderRecur(head.left); preOrderRecur(head.right); } /** * 二叉树中序遍历递归实现 * @param head */ public static void inOrderRecur(Node head) { if (head == null) { return; } inOrderRecur(head.left); System.out.print(head.value + " "); inOrderRecur(head.right); } /** * 二叉树后序遍历递归实现 * @param head */ public static void posOrderRecur(Node head) { if (head == null) { return; } posOrderRecur(head.left); posOrderRecur(head.right); System.out.print(head.value + " "); } /** * 二叉树前序遍历非递归实现 * 使用栈实现,先弹出打印头节点 * 然后先将右子树压栈,然后压入左子树 * 然后遍历打印 * @param head */ public static void preOrderUnRecur(Node head){ System.out.println("pre-order"); if (head!=null){ Stack<Node> stack=new Stack<>(); stack.add(head); while (!stack.isEmpty()){ head=stack.pop(); System.out.print(head.value+" "); if (head.right!=null){ stack.push(head.right); } if (head.left!=null){ stack.push(head.left); } } } } /** * 二叉树的中序遍历非递归实现 * @param head */ public static void inOrderUnRecur(Node head) { System.out.print("in-order: "); if (head != null) { Stack<Node> stack = new Stack<Node>(); while (!stack.isEmpty() || head != null) { if (head != null) { stack.push(head); head = head.left; } else { head = stack.pop(); System.out.print(head.value + " "); head = head.right; } } } System.out.println(); } /** * 后序遍历非递归实现 * @param head */ public static void posOrderUnRecur1(Node head) { System.out.print("pos-order: "); if (head != null) { Stack<Node> s1 = new Stack<Node>(); Stack<Node> s2 = new Stack<Node>(); s1.push(head); while (!s1.isEmpty()) { head = s1.pop(); s2.push(head); if (head.left != null) { s1.push(head.left); } if (head.right != null) { s1.push(head.right); } } while (!s2.isEmpty()) { System.out.print(s2.pop().value + " "); } } System.out.println(); } }
六、较为直观的打印二叉树
package FaceQuestion.二叉树; /** * 较为直观的打印二叉树 */ public class PrintBinaryTree { public static class Node { public int value; public Node left; public Node right; public Node(int data) { this.value = data; } } public static void printTree(Node head) { System.out.println("Binary Tree:"); printInOrder(head, 0, "H", 17); System.out.println(); } public static void printInOrder(Node head, int height, String to, int len) { if (head == null) { return; } printInOrder(head.right, height + 1, "v", len); String val = to + head.value + to; int lenM = val.length(); int lenL = (len - lenM) / 2; int lenR = len - lenM - lenL; val = getSpace(lenL) + val + getSpace(lenR); System.out.println(getSpace(height * len) + val); printInOrder(head.left, height + 1, "^", len); } public static String getSpace(int num) { String space = " "; StringBuffer buf = new StringBuffer(""); for (int i = 0; i < num; i++) { buf.append(space); } return buf.toString(); } public static void main(String[] args) { Node head = new Node(1); head.left = new Node(-222222222); head.right = new Node(3); head.left.left = new Node(Integer.MIN_VALUE); head.right.left = new Node(55555555); head.right.right = new Node(66); head.left.left.right = new Node(777); printTree(head); head = new Node(1); head.left = new Node(2); head.right = new Node(3); head.left.left = new Node(4); head.right.left = new Node(5); head.right.right = new Node(6); head.left.left.right = new Node(7); printTree(head); head = new Node(1); head.left = new Node(1); head.right = new Node(1); head.left.left = new Node(1); head.right.left = new Node(1); head.right.right = new Node(1); head.left.left.right = new Node(1); printTree(head); } }
打印效果:
Binary Tree:
v66v
v3v
^55555555^
H1H
^-222222222^
v777v
^-2147483648^
Binary Tree:
v6v
v3v
^5^
H1H
^2^
v7v
^4^
Binary Tree:
v1v
v1v
^1^
H1H
^1^
v1v
^1^
七、在二叉树中找到一个节点的后继节点
package FaceQuestion.二叉树; /** * 找到二叉树的后继节点 * 1.当一个节点存在右子树时,他的最左边的节点就是后继节点 * 2.当一个节点不存在右子树时,将其不断向上移动,当发现他的父节点的左孩子是node时,parent就是其后继节点 */ public class DescendantNode { public static class Node { public int value; public Node left; public Node right; public Node parent; public Node(int data) { this.value = data; } } /** * 找到后继节点 * @param node 当前节点 * @return */ public static Node getNextNode(Node node) { if (node == null) { return node; } //node有右子树,右子树最左的节点就是后继节点 if (node.right != null) { return getLeftMost(node.right); } else {//node没有右子树,node不断向上跳,当发现当前node是parent的左孩子,那么parent就是后继节点 Node parent = node.parent; while (parent != null && parent.left != node) { node = parent; parent = node.parent; } return parent; } } /** * 找到一个节点的最左边的节点 * @param node * @return */ public static Node getLeftMost(Node 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中任意一个局部最小出现的位置即可。
package FaceQuestion.二叉树; /** * 在数组中找到一个局部最小的位置 * 假设有N个数,先判断两段的数,0和N-1 * 0如果比1位置上的数小,那么就是局部最小直接返回 * N-1如果比N-2位置上的数小,那么就是局部最小,直接返回 * 如果两段不存在局部最小,那么数组向右方向是递减,向左方向也是递减 * 那么在0-N-1之间必存在局部最小,此时使用折半查找去寻找 * 先找到中间位置,判断中间位置是否是局部最小,如果不是,那么就再次使用折半去查找 */ public class FindOneLessValueIndex { public static int getLessIndex(int[] arr){ if (arr==null||arr.length==0){ 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; } }
九、前缀树:
Trie树,又称字典树、前缀树,是一种树形结构,是哈希树的变种,是一种用于快速检索的多叉树结构。
典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
一个字符串类型的数组arr1,另一个字符串类型的数组arr2。arr2中有哪些字符,是arr1中出现的?请打印
package FaceQuestion.二叉树; /** * 前缀树 * */ public class TrieTree { public static class TrieNode{ public int path;//每个字符串节点路过的次数 public int end;//每个字符串的节点出现的次数 public TrieNode[] map;//字符的路径 public TrieNode(){ path=0; end=0; map=new TrieNode[26]; } } /** * 前缀树类 */ public static class Trie{ private TrieNode root; public Trie(){ root=new TrieNode(); } /** * 插入一个字符串 * @param word */ public void insert(String word){ if (word==null){ return; } char[] chs=word.toCharArray();//将字符串转换为字符数组 TrieNode node=root; int index=0;//每个字符的ascll码代表的序号 for (int i=0;i<chs.length;i++){ index=chs[i]-'a';//将每个字符转换为0,1,2,3...序号值 if(node.map[index]==null){//如果这条边为空,那么新建一个节点 node.map[index]=new TrieNode(); } node=node.map[index];//节点到下一个 node.path++;//经过的路径次数+1 } node.end++;//整个字符串出现的次数+1; } /** * 删除一个字符串 * 删除时,需要判断节点路劲经过次数是否已经为0,为0才可以删除 * @param word */ public void delete(String word){ if(search(word)){ //如果该字符串存在 char[] chs=word.toCharArray(); TrieNode node=root; int index=0; for (int i=0;i<chs.length;i++){ index=chs[i]-'a'; if (node.map[index].path--==1){ //如果一个节点的path值为0,那么就可以删除此节点 node.map[index]=null; return; } node=node.map[index];//节点不断向下 } node.end--; } } /** * 在字典树中寻找是否存在摸个字符串 * @param word * @return */ public boolean search(String word){ if (word==null){ return false; } char[] chs=word.toCharArray(); TrieNode node=root; int index=0; for (int i=0;i<chs.length;i++){ index=chs[i]-'a'; if (node.map[index]==null){ return false; } node=node.map[index]; } return node.end!=0;//如果字符串尾步字符节点的end值为0,表示字符串没有出现过,那么就找不到这个字符串 } /** * 寻找一个字符串的前缀部分出现的次数 * @param pre * @return */ public int prefixNumber(String pre){ if (pre==null){ return 0; } char[] chs=pre.toCharArray(); TrieNode node=root; int index=0; for (int i=0;i<chs.length;i++){ index=chs[i]-'a'; if (node.map[index]==null){ return 0; } node=node.map[index]; } return node.path; } } }