左神算法笔记(九)——前缀树

前缀树

前缀树

前缀树用上图进行理解,其中节点均用圆圈表示,“abc”等字符加入到整棵树中作为路径加入,而不是以节点形式加入,同时每个字符在加入时均从头结点出发,因此可以看到“abc”和“bce”为两个分支,同时“abc”和“abd”前半段为一个分支,最后形成两个分支。
扩充功能:1.将加入的字符以路径的形式加入节点可以存放是否为字符串结束为止的信息,从而可以在加入“be”这种跟之前走过的路径相同,但是没有最后形成新的分支的字符串统计出数目,最后可以用于整体加入了多少字符串信息的统计。(扩展了字符串加了几次的功能)
2.查询到达的前缀,加的过程中,到达了该节点几次。

public static class TrieNode{
	public int path;
	public int end;
	public TrieNode[] nexts;

	public TrieNode(){
		path = 0;
		end = 0;
		//为了简化,将26个字母作为26条路
		nexts = 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';
			//判断这条路起始位置是否存在,不存在则新建,同时将后面的道路依次打通。
			if(node.nexts[index] == null){
				nofr.nexts[index] = new TrieNode();
			}
			node = node.nexts[index];
			node.path++;
		}
		node.end++;
	}
	
	public void delete(String word){
		if(search(word) != 0){
			char[] chs = word.toCharArray();
			TrieNode node = root;
			int index = 0;
			for(int i = 0;i<chs.length;i++){
				index = chs[i] - 'a';
				//路径信息某个节点减一变成0,意味着接下来的字符串全部都是所需要删除的字符串,所以下面的字符串直接设置为null
				if(--node.nexts[index].path == 0){
					node.nexts[index] = null;
					return;
				}
				node = node.nexts[index];
			}
			node.end--;
		}
	}

	public int search(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';
			if(node.nexts[index] == null){
				return 0;
			}
			node = node.nexts[index];
		}
		return node.end;
	}

	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.nexts[index] == null){
				return 0;
			}
			node = node.nexts[index];
		}
		return node.path;
	}
}

给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最低的字典序

将两个字符串整合起来之后再进行排序,而不是仅仅两个字符串比较直接排序最后整合。可以使用贪心算法。
如果需要证明自己的排序是唯一的,则需要证明整个顺序是由传递性的。(a>b,b>c,则a>c)如果当前字符串已经是最小的,则此时需要证明如果改变字符串中任何的两个小字符串,则小于当前的字符串。
贪心策略提出之后,如果要证明贪心策略正确,则会付出很多的经历,不如直接使用对数器,不需要证明贪心策略。

public static class MyComparator implements Comparator<String> {
	@override
	public int compare(String a,String b){
		return(a+b).compareTo(b+a);
	}
}

public 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;
}

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如 长度为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铜板。 输入一个数组,返回分割的最小代价。

对于此题,输入的数组就是需要切成的份数和数值,返回最小代价则按照霍夫曼编码思路,总长度为总节点,不同节点之和为下面的子节点,通过下面子节点的组合,形成代价最小的树。
新建小根堆,然后将根节点和另一个最小的拿出,将根节点跟另一个最小的值结合,此时将结合之和的数值返回小根堆,重新形成小根堆,递归操作,直到完成整个数组元素的合并。中间的过程就是最小分割的代价的过程,最后相加的数值就是最小代价。

public class int lessMoney(int [] arr){
	PriorityQueue<Integer> pQ = new PriorityQueue<>();
	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;
}

## 两个数组,一个为代价数组,一个为利润数组,两个数组第i位代表每个项目的情况。拿回的钱是代价加上利润。最开始给一个启动资金,如果花费小于启动资金,则可以启动项目,但是一次只能做一个项目,最多只能做K个项目。做完之后的资金变成启动资金加上项目利润。求最后获得的钱的数目。

1. 将cost代价数组形成小根堆,只要小根堆的头部花费低于启动资金,则将小根堆中的头部依次拿出,放到大根堆中。大根堆根据收益高,收益高则在头部。则拿出结束之后,小根堆中剩余的项目是目前无法做的项目,大根堆中存放的项目是目前可以做的,并且按照利润进行了排序。

```java
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]);
	}
	//MinCostComparator和下面的Max都是继承比较器,将Node中的cost花费进行比较,实现大根堆和小根堆
	PriorityQueue<Node> minCastQ = new PriorityQueue<>(new MinCostComparator());
	PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
	for(int i = 0;i<nodes.length;i++){
		minCostQ.add(nodes[i]);
	}
	for(int i = 0;i<k;i++){
		while(!minCostQ.isEmpty() &&minCostQ.peek().c<=W){
			maxProfitQ.add(minCostQ.poll());
		}
		if(maxProfitQ.isEmpty()){
			return W;
		}
		W += maxProfitQ.poll().p;
	}
	return W;
}

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目 的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数 组,里面 是一个个具体的项目),你来安排宣讲的日程,要求会 议室进行 的宣讲的场次最多。返回这个最多的宣讲场次。

利用结束时间进行判断,按照项目的结束时间进行排序,当前项目的结束时间可以排除掉其他不能开始的项目,下一个选择的项目是end结束之后下一个结束时间最早且开始时间小于上一次结束时间的项目。

public static class Program{
	public int start;
	public int end;
	public Program(int start,int end){
		this.start = start;
		this.end = end;
	}
}

public static class ProgramComparator implements Comparator<Program>{
	@Override
	public int compare(Program o1,Program o2){
		return o1.end-o2.end;
	}
}

public static int bestArrange(Program[] programs,int start){
	Arrays.sort(programs,new ProgramComparator());
	int result = 0;
	for(int i = 0;i< programs.length;i++){
		if(start<=programs[i].start){
			result++;
			start = programs[i].end;
		}
	}
	return result;
}
发布了24 篇原创文章 · 获赞 6 · 访问量 496

猜你喜欢

转载自blog.csdn.net/qq_35065720/article/details/104174856
今日推荐