前缀树
建立前缀树时,沿途的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到少的里