字典树又称为前缀树或Trie树,是处理字符串常见的数据结构。假设组成所有单词的字符仅是“a”~"z",请实现字典树结构,并包含以下四个主要功能:
思考:
字典树的介绍。字典树是一种树形结构,优点是利用字符串的公共前缀来节约存储空间。
字典树的基本性质如下:
在字典树上搜索添加过的单词的步骤为:
字典树的节点类型:
public static class TrieNode {
public int path; //表示由多少个字符串共用这个节点
public int end;//表示有多少个字符串是以这个节点结尾的
public TrieNode[] map;//哈希表结构,key代表该节点的一条字符路径,value表示字符路径指向的节点
public TrieNode() {
path = 0;
end = 0;
map = new TrieNode[26];
}
}
Trie树如何实现:
代码:
/**
* 前缀树
*/
public class TrieTree {
public static class TrieNode {
public int path; //表示由多少个字符串共用这个节点
public int end;//表示有多少个字符串是以这个节点结尾的
public TrieNode[] map;//哈希表结构,key代表该节点的一条字符路径,value表示字符路径指向的节点
public TrieNode() {
path = 0;
end = 0;
map = new TrieNode[26];
}
}
public static class Trie {
private TrieNode root;//头
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
if (word == null) {
return;
}
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0; //哪条路
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a'; //0~25
if (node.map[index] == null) {
node.map[index] = new TrieNode();
}
node = node.map[index];
node.path++;
}
node.end++;
}
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--;
}
}
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;
}
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;
}
}
}
相关题目:
一个字符串类型的数组arr1,另一个字符串类型的数组arr2。
arr2中有哪些字符,是arr1中出现的?请打印。
arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印。
arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印arr2中出现次数最大的前缀。
例如,给定数组{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和30,花费30,一共花费90个铜板。
输入一个数组,返回分割的最小代价。
思考:本题采用哈夫曼编码的思想:
(1)首先构造小根堆
(2)每次取最小的两个数(小根堆),使其代价最小。并将其和加入到小根堆中
(3)重复(2)过程,直到最后堆中只剩下一个节点。
注意:代价不是最后一个值,而是所有非叶节点之和,即上面求得两两节点之和。
例如:1,3 6 6 9 16组成一个小根堆。
1. 1和3合并等于4,代价为4。把4加入到小根堆,此时小根堆含有4,6,6,9,16,调成小根堆形式。
2. 4和6合并等于10,代价为10。把10加入到小根堆,此时小根堆为10,6,9,16,调成小根堆形式。
3. 6和9合并等于15,代价为15。把15加入到小根堆,此时小根堆为10,15,16,调成小根堆形式。
4. 10和15合并等于25,代价为25。把25加入到小根堆,此时小根堆为25,16,调成小根堆形式。
5. 25和16合并等于41,代价为41。把41加入到小根堆,此时小根堆为41。
因此,总共代价为4+10+15+25+41。
import java.util.Comparator;
import java.util.PriorityQueue;
public class LessMoney {
public static void main(String[] args) {
//solution
int[] arr = {6, 7, 8, 9};
System.out.println(lessMoney(arr));
int[] arrForHeap = {3, 5, 2, 7, 0, 1, 6, 4};
// minH heap
PriorityQueue minQ1 = new PriorityQueue<>();
for (int i = 0; i < arrForHeap.length; i++) {
minQ1.add(arrForHeap[i]);
}
while (!minQ1.isEmpty()) {
System.out.print(minQ1.poll() + " ");
}
System.out.println();
// min heap use Comparator
PriorityQueue minQ2 = new PriorityQueue<>(new MinheapComparator());
for (int i = 0; i < arrForHeap.length; i++) {
minQ2.add(arrForHeap[i]);
}
while (!minQ2.isEmpty()) {
System.out.print(minQ2.poll() + " ");
}
System.out.println();
// max heap use Comparator
PriorityQueue maxQ = new PriorityQueue<>(new MaxheapComparator());
for (int i = 0; i < arrForHeap.length; i++) {
maxQ.add(arrForHeap[i]);
}
while (!maxQ.isEmpty()) {
System.out.print(maxQ.poll() + " ");
}
}
/**
* 大根堆
*/
public static class MaxheapComparator implements Comparator {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
/**
* 大根堆
*/
public static class MinheapComparator implements Comparator {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
}
public static int lessMoney(int[] arr) {
if (arr == null || arr.length == 0)
return 0;
PriorityQueue pQ = new PriorityQueue<>();
//把所有数加入到堆中
for (int i = 0; i < arr.length; i++) {
pQ.add(arr[i]);
}
//切割金条的总花费
int sum = 0;
int curr = 0;
while (pQ.size() > 1) {
curr = pQ.poll() + pQ.poll();
sum += curr;
pQ.add(curr);
}
return sum;
}
}
输入:参数1,正数数组costs,参数2,正数数组profits,参数3,正数k,参数4,正数m
costs[i]表示i号项目的花费profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润),k表示你不能并行,只能串行的最多做k个项目,m表示你初始的资金。
说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。
输出:你最后获得的最大钱数。
思考:给定一个初始化投资资金,给定N个项目,想要获得其中最大的收益,并且一次只能做一个项目。这是一个贪心策略的问题,按照花费的多少放到一个小根堆里面,然后要是小根堆里面的头节点的花费少于给定资金,就将头节点一个个取出来,放到按照收益的大根堆里面,这样就然后将大根堆的堆顶弹出。
代码:
package NewCoder.class_07;
import java.util.Comparator;
import java.util.PriorityQueue;
public class IPO {
//一个节点代表一个项目
public static class Node {
int profit;//收益
int cost;//花费
public Node(int profit, int cost) {
this.profit = profit;
this.cost = cost;
}
}
/**
* 计算最大项目收益
*
* @param k:最多可以做多少个项目
* @param m:总的初始资金
* @param costs:花费
* @param profits:收益
* @return
*/
public static int ipo(int k, int m, int[] costs, int[] profits) {
//小根堆、大根堆
Node[] nodes = new Node[profits.length];
for (int i = 0; i < profits.length; i++) {
nodes[i] = new Node(profits[i], costs[i]);
}
PriorityQueue minQueue = new PriorityQueue(new minHeapCompatator());
PriorityQueue maxQueue = new PriorityQueue(new maxHeapCompatator());
for (int i = 0; i < costs.length; i++) {
minQueue.add(nodes[i]);
}
for (int i = 0; i < k; i++) {
while (!minQueue.isEmpty() && minQueue.peek().cost <= m) {
maxQueue.add(minQueue.poll());
}
if (maxQueue.isEmpty()) {
return m;
}
m += maxQueue.poll().profit;
}
return m;
}
public static class minHeapCompatator implements Comparator {
@Override
public int compare(Node o1, Node o2) {
return o1.cost - o2.cost;//按照花费排序的小根堆
}
}
public static class maxHeapCompatator implements Comparator {
@Override
public int compare(Node o1, Node o2) {
return o2.profit - o1.profit;//按照收益排序的大根堆
}
}
public static void main(String[] args) {
int[] costs = new int[]{10, 20, 100};
int[] profits = new int[]{11, 10, 200};
int k = 3;
int m = 50;
System.out.println(ipo(k, m, costs, profits));
}
}
题目描述:有一个源源不断地吐出整数的数据流,假设你有足够的空间来保存吐出的数。请设计一个名叫MedianHolder的结构,MedianHolder可以随时取得之前吐出所有树的中位数。
要求:1.如果MedianHolder已经保存了吐出的N个数,那么任意时刻将一个新的数加入到MedianHolder的过程中,其时间复杂度为O(logN)。2.取得已经吐出的N个数整体的中位数的过程,时间复杂度为O(1).
思考:设计的MedianHolder中有两个堆,一个是大根堆,一个是小根堆。大根堆中含有接收的所有数中较小的一半,并且按大根堆的方式组织起来,那么这个堆的堆顶就是较小一半的数中最大的那个。小根堆中含有接收的所有数中较大的一半,并且按小根堆的方式组织起来,那么这个堆的堆顶就是较大一半的数中最小的那个。
例如,如果已经吐出的数为6,1,3,0,9,8,7,2.
较小的一半为:0,1,2,3,那么3就是这一半的数组成的大根堆的堆顶
较大的一半为:6,7,8,9,那么6就是这一半的数组成的小根堆的堆顶
因为此时数的总个数为偶数,所以中位数就是两个堆顶相加,再除以2.
如果此时新加入一个数10,那么这个数应该放进较大的一半里,所以此时较大的一半数为6,7,8,9,10,此时6依然是这一半的数组成的小根堆的堆顶,因为此时数的总个数为奇数,所以中位数应该是正好处在中间位置的数,而此时大根堆有4个数,小根堆有5个数,那么小根堆的堆顶6就是此时的中位数。如果此时又新加入一个数11,那么这个数也应该放进较大的一半里,此时较大一半的数为:6,7,8,9,10,11.这个小根堆大小为6,而大根堆的大小为4,所以要进行如下调整:
1.如果大根堆的size比小根堆的size大2,那么从大根堆里将堆顶元素弹出,并放入小根堆里
2,如果小根堆的size比大根堆的size大2,那么从小根堆里将堆顶弹出,并放入大根堆里。
经过这样的调整之后,大根堆和小根堆的size相同。
总结如下:
这样随时都可以知道已经吐出的所有数处于中间位置的两个数是什么,取得中位数的操作时间复杂度为O(1),同时根据堆的性质,向堆中加一个新的数,并且调整堆的代价为O(logN)。
代码:
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* 随时找到数据流的中位数
* 思路:
* 利用一个大根堆和一个小根堆去保存数据,保证前一半的数放在大根堆,后一半的数放在小根堆
* 在添加数据的时候,不断地调整两个堆的大小,使得两个堆保持平衡
* 要取得的中位数就是两个堆堆顶的元素
*/
public class MedianQuick {
public static class MedianHolder {
private PriorityQueue maxHeap = new PriorityQueue(new MaxHeapComparator());
private PriorityQueue minHeap = new PriorityQueue(new MinHeapComparator());
/**
* 调整堆的大小
* 当两个堆的大小差值变大时,从数据多的堆中弹出一个数据进入另一个堆中
*/
private void modifyTwoHeapsSize() {
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());
}
}
/**
* 添加数据的过程
*
* @param num
*/
public void addNumber(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);
return;
}
if (this.minHeap.peek() > num) {
this.maxHeap.add(num);
} else {
this.minHeap.add(num);
}
}
modifyTwoHeapsSize();
}
/**
* 获取中位数
*
* @return
*/
public Integer getMedian() {
int maxHeapSize = this.maxHeap.size();
int minHeapSize = this.minHeap.size();
if (maxHeapSize + minHeapSize == 0) {
return null;
}
Integer maxHeapHead = this.maxHeap.peek();
Integer minHeapHead = this.minHeap.peek();
if (((maxHeapSize + minHeapSize) & 1) == 0) {
return (maxHeapHead + minHeapHead) / 2;
}
return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
}
}
/**
* 大根堆比较器
*/
public static class MaxHeapComparator implements Comparator {
@Override
public int compare(Integer o1, Integer o2) {
if (o2 > o1) {
return 1;
} else {
return -1;
}
}
}
/**
* 小根堆比较器
*/
public static class MinHeapComparator implements Comparator {
@Override
public int compare(Integer o1, Integer o2) {
if (o2 < o1) {
return 1;
} else {
return -1;
}
}
}
// for test
public static int[] getRandomArray(int maxLen, int maxValue) {
int[] res = new int[(int) (Math.random() * maxLen) + 1];
for (int i = 0; i != res.length; i++) {
res[i] = (int) (Math.random() * maxValue);
}
return res;
}
// for test, this method is ineffective but absolutely right
public static int getMedianOfArray(int[] arr) {
int[] newArr = Arrays.copyOf(arr, arr.length);
Arrays.sort(newArr);
int mid = (newArr.length - 1) / 2;
if ((newArr.length & 1) == 0) {
return (newArr[mid] + newArr[mid + 1]) / 2;
} else {
return newArr[mid];
}
}
public static void printArray(int[] arr) {
for (int i = 0; i != arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
boolean err = false;
int testTimes = 200000;
for (int i = 0; i != testTimes; i++) {
int len = 30;
int maxValue = 1000;
int[] arr = getRandomArray(len, maxValue);
MedianHolder medianHold = new MedianHolder();
for (int j = 0; j != arr.length; j++) {
medianHold.addNumber(arr[j]);
}
if (medianHold.getMedian() != getMedianOfArray(arr)) {
err = true;
printArray(arr);
break;
}
}
System.out.println(err ? "Oops..what a fuck!" : "today is a beautiful day^_^");
}
}
例如:strs=["abc","de"],可以拼成“abcde”,也可以拼成“deabc”,但是前者的字典顺序更小,所以返回“abcde”。
strs=["b","ba"],可以拼成“bba”,也可以拼成“bab”,但是后者的字典顺序更小,所以返回“bab”。
思路:假设有两个字符串,分别记为a和b,a和b拼起来的字符串表示为a.b。那么如果a.b的字典顺序小于b.a。就把字符串a放在前面,否则把字符串b放在前面。每两个字符串之间都按照这个标准进行比较,以此标准排序后,再依次串起来的大写字符串就是结果。
假设有a,b,c三个字符串,它们有如下关系:
a.b b.c 如果能够根据上面两式证明出a.c 证明传递性后,还需要证明通过这种比较方式排序后,如果交换任意两个字符串的位置所得到的总字符串,将拥有更大的字典顺序。证明略。 整个解法的时间复杂度就是排序本身的复杂度,即O(logN)。 代码: 题目八: 一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。给你每一个项目开始的时间和结束的时间(给你一个数组,里面是一个个具体的项目),你来安排宣讲的日程,要求会议室进行的宣讲场次最多。返回这个最多的宣讲场次。 输入: 5 1 3 2 5 4 7 6 9 8 10 输出 3 思考: 区间问题都是选用早结束为策略,谁结束早就先做哪个宣讲。 代码: import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class LowestStringMain {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String[] strs = sc.nextLine().split(" ");
String res = lowestString(strs);
System.out.println(res);
}
sc.close();
}
private static String lowestString(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
//根据新的比较方式排序
Arrays.sort(strs, new MyComparator());
String res = "";
for (int i = 0; i < strs.length; i++) {
res += strs[i];
}
return res;
}
private static class MyComparator implements Comparator
package NewCoder.class_07;
import java.io.BufferedInputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class BastArrange {
public static class Program {
int start;
int end;
public Program(int start, int end) {
this.start = start;
this.end = end;
}
}
private static int bastArrange(Program[] p) {
int ans = 0;
Arrays.sort(p, new Comparator