持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情
下面是我整理的跟着b站左程云的数据结构与算法学习笔记,欢迎大家一起学习。
前缀树
不以点以边更好 虽然也可以以点表示值,但总体效果不如边,比如头节点的展示 包括经典的算法等都是基于边
节点代码
public static class TrieNode {
//有多少点经过这里
public int path;
//有多少点依此为终点
public int end;
public TrieNode[] nexts;
public TrieNode() {
path = 0;
end = 0;
//26代表底下可能可以连26个字母 如nexts[0]!=null 该字母有通向a的路
nexts = new TrieNode[26];
}
}
当有线经过该点时,p++,如果是尾节点,e++
如 当为a时,a两边的p++;
若e=1;则有到该点的边
把前缀树建好,代价是字符的数量
若想知道有多少字符串以ab为前缀 可以看ab结点上的p值
构建前缀树
将字符串构造进前缀树中
准备一个公共的头节点root,根节点的pass++;表示加入多少个字符串 先将字符串转为字符数组,通过遍历字符数组,将字符转化为数字,即字符串-'a'得到a=0... 若指定节点的node[index]为空,新建节点,节点继续走到node[index];并将path++;将最后一个遍历的node节点上的end++ 代码实现
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) {
node.nexts[index] = new TrieNode();
}
node = node.nexts[index];
node.path++;
}
node.end++;
}
26代表26个小写字母,若字符种类过多,可将其转化为HashMap<Char,Node> 某个字符的下一个节点 路与路之间有序组织可以用TreeMap
查找指定字符串
思路:若已经插入abc
- 查找ab,当ab长度走完时,停留在b之后的节点上,该节点上的end值为0,不存在该字符串
- 查找abcd,此时长度遍历到c后的边时,node下没有该值,返回空,不存在该值
- 若为abc,返回c后节点下的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;
}
查找有多少个字符以指定字符串为前缀
思路:代码与查找指定字符串相似,只是遍历到该节点后,取出pass值 代码实现
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;
}
如图,在前缀树中删除abe
跟着abe一个一个节点遍历,沿途p--;最后的节点e--
代码实现
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';
if (--node.nexts[index].path == 0) {
node.nexts[index] = null;
return;
}
node = node.nexts[index];
}
node.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;
}
解题套路
将所有字符串中字符串选择拼接顺序,形成最小字典序的结果
若两个字母长度不一,将短的字母用0补齐长度,按照字母顺序由高位到地位排序
但这种方案并不能保证字典序最低
如图字典序最低为bab
新的贪心策略 加上比较 若a,b
若a,b<=b,a 则a前,否则b前
可以理解为两个数的相加 a,b=a* k^b+b
证明过程是一个不断进行数学运算的过程
证明出比较策略具有传递性,可以排出唯一的一组策略
前,后<=后,前
若a,b之间有其他字母
用数学归纳法可以证明不知相邻都能推出该结论,进而证明贪心算法有序
典型例题
例题1
相当于
求最小的整体和
解题思路 使用小根堆,通过不断将最小的两值进行合并并重新放进去,反过来实现该工程
得到沿途最低代价
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;
}
例题2
通过不断的投入,获得收益,启动更大的项目
只是每一步都没有选择,且最大利润只能到7,下面这种情况更能说明贪心算法
解题思路
根据花费加进小根堆,并将所有项目加锁,通过成本找出<=成本的项目,解锁该项目,并将其通过利润加进大根堆,将弹出的项目(即为最大收益项目)进行,并获得收益,循环反复
在数据流中,随时取得中位数
实现步骤: 准备一个大根堆和一个小根堆
- 将第一个数字放进大根堆
- 判断第二个数是否小于大根堆堆顶,若是,入大根堆,否则进小根堆
- 判断大小根堆的大小,如果大size-小size>=2,较大堆顶弹出进较小的堆
如以下的流程
- 5先进入大根堆,3小于5进大根堆,此时大根堆的长度超过小根堆的长度超过2,将5弹出并放进小根堆;
- 7和4都大于3放进小根堆,此时小根堆的长度超过大根堆的长度超过2,将4弹出并放进大根堆 此时较小的中位数在大根堆中,较大的中位数在小根堆中 整个流程的所有调整都为logN水平
N皇后问题
isvalid函数的过程
n行各有n种选择 时间复杂度n^n
代码实现
public static int num1(int n) {
if (n < 1) {
return 0;
}
int[] record = new int[n];
return process1(0, record, n);
}
// 目前来到了第i行,record[0...i-1]之前的行放过的皇后 整体一共有n行 返回值为合理的摆法
public static int process1(int i, int[] record, int n) {
//终止行
if (i == n) {
return 1;
}
int res = 0;
for (int j = 0; j < n; j++) {
if (isValid(record, i, j)) {
record[i] = j;
res += process1(i + 1, record, n);
}
}
return res;
}
public static boolean isValid(int[] record, int i, int j) {
for (int k = 0; k < i; k++) {
// Math.abs(record[k] - j) == Math.abs(i - k)判断两点是否共斜线 即45度
if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
return false;
}
}
return true;
}
常数优化,运用位运算
//常数优化版本 通过位运算,最好不要超过32位
public static int num2(int n) {
if (n < 1 || n > 32) {
return 0;
}
int upperLim = n == 32 ? -1 : (1 << n) - 1;
return process2(upperLim, 0, 0, 0);
}
public static int process2(int upperLim, int colLim, int leftDiaLim,
int rightDiaLim) {
if (colLim == upperLim) {
return 1;
}
int pos = 0;
int mostRightOne = 0;
pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
int res = 0;
while (pos != 0) {
mostRightOne = pos & (~pos + 1);
pos = pos - mostRightOne;
res += process2(upperLim, colLim | mostRightOne,
(leftDiaLim | mostRightOne) << 1,
(rightDiaLim | mostRightOne) >>> 1);
}
return res;
}
位的限制主要通过求出左限制,右限制与前一行并将这三个或求和
下一轮中,左限制左移,右限制右移
位置的限制通过移位很容易得到