算法笔记第7章--前缀树和贪心

前缀树

建立前缀树时,沿途的e值++,最后一个节点的e值++。
每一个个节点,都有26长度的数组,
前缀树图

	//数据结构
	public static class TrieNode {
		public int path;//通过的次数
		public int end;//终点的次数
		public TrieNode[] nexts;//26个树枝,指向26个字母,如果字符种类特别多 ==>> HashMap<Char,Node> nexts; 或 TreeMap<Char,Node> nexts;
		public TrieNode() {
			path = 0;
			end = 0;
			//nexts[0] != null  ==>>有走向'a'的 
			//nexts[0] == null 	==>>没有走向'a'的 
			//...
			//nexts[25] != null ==>>有走向'z'的 
			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;//备份根节点,往下走
			node.path++;//根节点先path++,代表加入过多少个字符串,即以空串开始的字符串数,空串(根节点的path++,end++)
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				index = chs[i] - 'a';//字母对应的数组下表,决定走哪条路
				if (node.nexts[index] == null) {
					node.nexts[index] = new TrieNode();
				}
				node = node.nexts[index];//下移
				node.path++;//通过次数记录
			}
			node.end++;//最后一个节点
		}
		//删除, 查询过后和插入逆向操作
		public void delete(String word) {
			if (search(word) != 0) {// 确定存在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.nexts[index].path == 0) {//如果其path空了,则可以直接把后面置空
						node.nexts[index] = null;//java头节点置空,后面也就自动没了。C++得到底去析构
						return;
					}
					node = node.nexts[index];
				}
				node.end--;
			}
		}
		//这个单词加过几次
		public int search(String word) {
			if (word == null) {
				return 0;
			}
			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;
		}
	}

贪心

//笔试,面试不能区分
在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫作贪心算法。
也就是说,不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解。
局部最优-?->整体最优

贪心算法的在笔试时的解题套路
1,实现一个不依靠贪心策略的解法X,可以用最暴力的尝试(全排列,暴力枚举 ==>> 模板)
2,脑补出贪心策略A、贪心策略B、贪心策略C…
3,用解法X和对数器,去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确
4,不要去纠结贪心策略的证明

题目1
给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最小的字典序
字典序比较:
1、字典谁在后谁大,(a, an); an大>a
2、把位数用0补齐,比较(a, an) ==> (a0, an); an>a

贪心策略:
1、谁的字典序小,谁在前。❌  反例: (b, ba) bab < bba
2、先结合,谁的字典序小谁就在前面。✔
【注】比较器必须有传递性,不能是个环,水火草相克❌。

	public static class MyComparator implements Comparator<String> {
		@Override
		public int compare(String a, String b) {
			return (a + b).compareTo(b + a);//比较器,前者小于后者<0,前者大于后者>0
		}
	}

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

1,根据某标准建立一个比较器来排序
2,根据某标准建立一个比较器来组成堆

题目2

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为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 static 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;
	}

题目3
输入:
costs【】表示i号项目的花费
prof its[i]表示i号项目在扣除花费之后还能挣到的钱(利润)
k表示你只能串行的最多做k个项目
m表示你初始的资金
说明:
你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。
输出
你最后获得的最大钱数。

	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> minCostQ = 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;
	}

题目4
非贪心,堆练习
图解
小根堆放较大的那一半,大顶堆放较小的那一半。
1》cur>=大堆顶?cur进大 : cur 进小。
2》大小根堆的size 相差到2,多的pop到少的里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值