0%

[toc]

beanFactory基础概念#

Spring Ioc容器体系除了IOC serviceProvider,还包括其他的东西。
在这里插入图片描述
图上可以看到除了IocProvider, 还包括了AOP、线程管理、周期管理之类的。


Spring提供2种容器 BeanFactory和ApplicationContext
区别:

  • BeanFactory: 延迟化加载。适合轻量级场景
  • ApplicationContext: 给予BeanFacotry构建, 除了支持BeamFacotry所有功能,还提供了其他特性)
    ApplicationContext所管理 的对象,在该类型容器启动之后,默认全部初始化并绑定完成

区别就是 1个是要用的时候才加载, 另一个是直接全部扫描并加载好(有点像jvm的client模式和server模式区别)


BeanFacotry是1个接口, 最重要的方法是getBean(name)
BeanFacotry的调用方式:
在这里插入图片描述


BeanFactory里的几个重要概念:
用图书馆的比喻在图里标注了一下
在这里插入图片描述


BeanFactory如何明确依赖关系,绑定依赖?

代码编写方式#

通过BeanDefinitionRegistry(BeamFacotry的实现,)去手动注册, 但是手动注册必须实现BeanDefintionRegistry:
在这里插入图片描述
基于SpringBoot ,怎么拿到BeanFactory进行直接编码?

1
写一下项目里怎么拿到BeanFactory

2.外部配置文件#

支持Properties文件格式和XML文件格式
Spring会实现1个BeanDefinitionReader类,用于进行 "图书的入库读取操作”, 并注册到BeanDefinetionRegistry里
propetris:
在这里插入图片描述

需要注意的一点,就是$0和$1后面的 (ref),(ref)用来表示所依赖的是引用对象,而不是普通的类型。如果不加(ref), PropertiesBeanDefinitionReader会将djListener和djPersister作为简单的String类型 进行注入
调用方式:
在这里插入图片描述
把reader绑定到registry,然后变成BeanFactory

xml:
,Spring同样为XML 格式的配置文件提供了现成的BeanDefinitionReader实现,即XmlBeanDefinitionReader

  • SpringBoot(serviceComb里),这种配置文件到哪去了呢??

3.注解方式#

@Autowired@Autowired是这里的主角,它的存在将告知Spring容器需要为当前对象注入哪些依赖对象。同时如果要使用这个注解,必须在spring配置中增加context:component-scan

这个太常见了。
SpringBoot里配置了context:component-scan吗?


BeanFactory实现原理#

官方关于IOC的参考图:在这里插入图片描述
它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息)
然后根据这些信息绑定整个系统的对象,终组装成 一个可用的基于轻量级容器的应用系统。

Ioc启动过程#

总共分为2个阶段:
容器启动和实例化阶段。
并且在这里2个阶段中,加入了足够多的可扩展点。
在这里插入图片描述

容器启动阶段:#

在这里插入图片描述
从图里可以看到, Ioc从配置中将需要加载的元素映射成BeanDefintion, 并一一注册到BeanDefinitionRegistry中。

实例化阶段(简单版)#

当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器 需要隐式地调用getBean方法时,就会触发第二阶段的活动。

  • 上面这句话可以看到, 不仅仅是getBean会触发,因为依赖所引发的也会进行初始化(有点像Java的Class初始化机制,既可以class.forName(),也可以间接触发?)

实例化的简单过程如下:
在这里插入图片描述
这张图的要点就是:

  1. 已经初始化过的,就不会再初始化了。
  2. 如果有一些回调的接口,会去调用再初始化装备

Bean实例化的详细过程:#

在这里插入图片描述

第一步: 实例化,且返回包装类:#

在这里插入图片描述

容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。
通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类
下面是一些用于初始化Bean时里的接口或者实现类

InstantiationStrategy是实例化的策略抽象接口
SimpleInstantiationStrategy是实现类,只能反射来实例化对象实例,但不支持方法注入方式的对象实例化
CglibSubclassingInstantiation通过CGLIB 的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象 实例化需求。(默认都用这个)
PS:确认一下工作项目里用的是什么接口的

  • 实例化不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例 进行包裹,返回相应的BeanWrapper实例
第二步:基于BeanWrapper设置成员属性#

在这里插入图片描述

实例化BeanWrapper后,会做类似如下操作
在这里插入图片描述

  • 从这张图可以看到之前为什么说 set注入很好用
    这里只要调用setPropertyValue(成员名, 成员) 即可进行注入, 不用去反射特定的方法。
第三步:检查Aware接口#

在这里插入图片描述

如果检测到有这个接口,则将这些Aware接口定义中规定的依赖注入给当前对象实例,都是一些特殊元素的注入。

啥意思呢……就是指有一些和Ioc(Bean或BeanFactory)相关的属性,可以通过Aware放进你要实例化的实例中。 这个我记得后面有例子,先略过,只放下几个Spring里的经典Aware:

  • BeanNameAware 检测到当前对象实 例实现了该接口,会将该对象实例的bean定义对应的beanName设置到当前对象实例
  • BeanClassLoaderAware 会将对应加载当前bean的Classloader注入当前对象实例
  • BeanFactoryAware,BeanFactory容器会将自身(即BeanFactory)设置到当前对象实例。这样,当前对象 实例就拥有了一个BeanFactory容器的引用,并且可以对这个容器内允许访问的对象按照需要 进行访问
第四步:BeanPostProcessor处理#

BeanPostProcessor, yyds!
在这里插入图片描述
BeanPostProcessor 是容器提供的对象实例化阶段的强有力的扩展点。

BeanPostProcessor和BeanFactoryPostProcessor的区别:
BeanPostProcessor是存在于对象实例化阶段
而BeanFactoryPostProcessor则是存在于容器启动阶段

  • BeanPostProcessor可进行实例前处理和后处理,方法为postProcessBeforeInitialization()和postProcessAfterInitialization()
  • 通常比较常见的使用BeanPostProcessor的场景,是处理标记接口实现类,或者为当前对象提供 代理实现(注意这个代理,为后面的Aop做铺垫

例如有个叫ApplicationContextAwareProcessor的, 会做一些和ApplicationContext相关的Bean前置处理(但是注意这时候已经实例化好了,只是还没调用自定义init方法!)
在这里插入图片描述
之前用过ApplicationContextAware,来在容器启动时,获取AplicationContext,进行你需要的处理。

书里介绍了一下自定义的BeanPostProcessor步骤,我就贴下里面BeanProcessor的实现:
在这里插入图片描述
看他就是自己实现了个BeanPostProcessor,然后用instance去判断是不是自己要处理的对象(每个都扫描了一遍~~)

问题: SpringBoot里要自己用代码注册BeanPostProcessor或者写到applicationContext配置里吗?

第五步: 初始化#

“BeanPostProcessor之后, 会检查实例对象是否实现了InitializingBean接口,如果是,调用其afterProper- tiesSet()方法进一步调整对象实例的状态。

在这里插入图片描述

缺点:如果真的让我们的业务对象实现这个接口,则显得 Spring容器比较具有侵入性。
改进:用init-method属性在xml中配置, 选择调用某个已实现方法来做初始化操作,并且不需要初始化时可以剔除。

  • 注意init的顺序和BeanPostProcessor
  • 在这里插入图片描述
六步:注册析构方法#

例如数据池对象,需要在结束时,执行close操作
在这里插入图片描述

但Spring容器在关闭之前, 不会自动调用这些回调方法。
所以,需要我们告知容器,在哪个时间点来执行对象的自定义销 毁方法

对于BeanFactory容器来说,调用ConfigurableBeanFactory提供的 destroySingletons()方法销毁容器中管理的所有singleton类型的对象实例。

在这里插入图片描述

对于ApplicationContext容器来说 可用registerShutdownHook(涉及底层runtime方法)
保证在Java虚拟机退出之前,这些singtleton类型的bean对象 实例的自定义销毁逻辑会被执行

[toc]

第一题:#

1394. 找出数组中的幸运数 - 力扣(LeetCode)

1661875618698

哈希表?数组?没啥意思

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int findLucky(int[] arr) {
int[] counts = new int[501];
for(int a : arr) {
counts[a]++;
}
for (int i = 500;i>=1;i--) {
if (counts[i] == i) {
return i;
}
}
return -1;
}
}

第二题 topk问题#

剑指 Offer 40. 最小的k个数 - 力扣(LeetCode)

topk问题别急着用sort,太简单

升级方法是堆(优先队列), 要求最小的几个,那其实就要搞个大顶堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
Queue<Integer> pqueue = new PriorityQueue<>((a,b)->(b-a));
if (k == 0) {
return new int[]{};
}
for (int i = 0; i < arr.length;i++) {
if (i < k) {
pqueue.offer(arr[i]);
continue;
}
if (pqueue.peek() > arr[i]) {
pqueue.poll();
pqueue.offer(arr[i]);
}
}
int[] res = new int[k];
for (int i = 0; i < k;i++) {
res[i] = pqueue.poll();
}
return res;
}
}

再升级的话用快排思想,每次划分后,挑可能有k的那一边继续划分,具体比较难理解就不管了,先放个答案在这,后面自己有空重写下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
randomizedSelected(arr, 0, arr.length - 1, k);
int[] vec = new int[k];
for (int i = 0; i < k; ++i) {
vec[i] = arr[i];
}
return vec;
}

private void randomizedSelected(int[] arr, int l, int r, int k) {
if (l >= r) {
return;
}
int pos = randomizedPartition(arr, l, r);
int num = pos - l + 1;
if (k == num) {
return;
} else if (k < num) {
randomizedSelected(arr, l, pos - 1, k);
} else {
randomizedSelected(arr, pos + 1, r, k - num);
}
}

// 基于随机的划分
private int randomizedPartition(int[] nums, int l, int r) {
int i = new Random().nextInt(r - l + 1) + l;
swap(nums, r, i);
return partition(nums, l, r);
}

private int partition(int[] nums, int l, int r) {
int pivot = nums[r];
int i = l - 1;
for (int j = l; j <= r - 1; ++j) {
if (nums[j] <= pivot) {
i = i + 1;
swap(nums, i, j);
}
}
swap(nums, i + 1, r);
return i + 1;
}

private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第三题 树的遍历和插入#

最大二叉树 II - 提交记录 - 力扣(LeetCode)

sb题目,看不懂题意, 其实就是把要插入的值优先右边放,直到某个点比自己小,那就可以取代这个点的位置,把这个点当成自己的左节点即可 ,题意写的乱七八糟的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode insertIntoMaxTree(TreeNode root, int val) {
if (val > root.val) {
TreeNode newRoot = new TreeNode(val);
newRoot.left = root;
return newRoot;
}
dfs(root, val, null, true);
return root;
}

boolean dfs(TreeNode node, int val, TreeNode parent, boolean isLeft) {
if (node == null || node.val < val) {
TreeNode newNode = new TreeNode(val);
if (isLeft) {
parent.left = newNode;
} else {
parent.right = newNode;
}
newNode.left = node;
return true;
}

if(dfs(node.right, val, node, false) ) {
return true;
}
return false;
}
}

[toc]

错误的语言或者表述#

道德评判#

对他人或者他人的行为做出评判和批评

应该调整为 对具体事情或者非主体的概念描述自己的感受和看法, 不能去否认对方本身。

作比较#

被人拿去左比较一定会难受的

推卸责任#

我不得不那么做, 我必须这样,都是因为某某某我只能这么做

缺少自己的真实感受和实际期望

总是提要求#

注意区分观察和评论#

与人沟通或者描述某些人时,

注意不要使用 “评论”,即主观的形容词

例如 你太懒了, 你很不懂事,你时好人,你总是xxx

观察则是带有具体事实和事件的

例如你昨天做了什么什么, 他每周都有一次xxxx

和对方沟通应该以观察为主, 尽可能不要用评论对方的方式来提出自己的观点或者诉求

体会和表达感受#

区分想法和感受#

感受是心情上的描述词,是客观的自己的内在感受,例如我很难过,我很高兴,我很害怕

而想法是主观的,可能是错误推断的,比如我觉得我xxxx,我觉得你xxxx,你是xxx的,我是xxx的

xxx让我很生气,这也是想法,因为里面的重点在于生气的原因。 应该改成我感到生气

为自己的感受负责#

表达完自己的感受后, 应当紧接自己的内在需要,或者说自己出现这种感受的“原因”,而原因来源于需要或者自己的期望。

而这个原因的主题必须是自己,而不是能是别人,也不能是对别人不做某事的职责。

比如我感到生气, 因为你迟到了

改成

我感到生气,因为本来我是希望能做到前排的位置的

我感到生气,因为你把文件丢到了地上毛手毛脚的

改成

你把文件丢到地上,我会有点生气, 因为这些文件很重要,我希望能好好报关

我感到难过,因为你总是不肯工作一直不上进

改成

我感到难过, 因为我希望你能独立并过上你自己想要的生活,我的压力也能轻松一点

提出请求#

尽量使用正向的、具体的请求语言,而非反向的、模糊语言#

你不要做xxx 改成我希望你做xxxx

你要像某某某热门 改成 你希望你 做某某某具体的事情

我希望你不要再喝酒了 改成 除了喝酒,有没有其他的方式可以替代?

尽量使用具体的请求语言,而非模糊的请求#

我希望你理解我 改成 能不能告诉我在你听来,我说的是什么意思

我希望你尊重我的隐私 改成 我希望你进门时先敲门

不能只提感受不说请求,也不能只说请求不说感受#

为了防止误解请求,可以让别人复述一下是否理解了#

提出请求后, 可以向对方索要他此刻的感受#

即你提出请求,对方统一后, 可以问一下他的感受,避免他为了过分顾及他们而不说话

区分请求和要求#

如果对方没有给出你想要的回应,你就生气、不高兴,说你简直是xxx,你怎么可以这样,那就是要求, 是会引发逆反的

但如果是请求,在对方没有给出想要的回应时,往往会先去体会对方的感受和需求,尝试询问对方拒绝的原因 ,而不是直接不开心并把不开心表现给对方看。

同理心#

倾听#

面对伤心的人, 避免以下任何选项

  • 给建议:你应该怎么做
  • 比惨:我也xxxx
  • 无用的安慰:你尽力了,你是最好的
  • 讲故事:我曾经balabla
  • 同情:
  • 询问:
  • 纠正

应该做的,是用心聆听他们的 观察(发生的事实)、感受、需要和请求。

一个人生气或者伤心, 一定是因为某种需要没有被满足,满足需求则一定对应这某个请求!

因此在你做出行动前,必须好好倾听!

复述#

  • 复述事实: 我看发生了这事情,你说的是这个对吧?
  • 感受: 你现在感觉很伤心,因为你希望xxx
  • 请求:你是不是希望我能做xxxx

避免没有具体内容、过于宽泛的的问题

例如”你说的是什么事?“、”你想我怎么做“

我们需要给他的是一个 判断题,让他自己决定我们复述的正确或者错误

如果对方实在说不清楚,我们不懂它的感受和需要,那就先以自己的感受为前提为他提出请求

”我很困惑,我希望你能告诉我你指的是哪件事“

要是询问, 避免下结论, 例如”哦你现在就是因为这个xxxx才对吧“, 这种语气不合适!

要注意倾听,不要着急做同理的询问

当对方说”不“#

当对方拒绝我们时,一定是她的需要和我们的请求不满足, 这时候应该去思考对方的感受并提出询问

打断啰嗦对话#

当对方复述自己过去悲惨的经历让你很不耐烦时,可以直接用同理的方式表达对她的理解,让他不要再往下讲了

非暴力沟通善待自己#

把自己觉得不得不的事情都列出来, 尝试改成是处于自己内在的需求所选择的!

避免以下几种:

  • 为了钱
  • 为了他人的评价和认可
  • 为了免受惩罚、内疚、羞愧
  • 为了“应该”、“不应该”的职责

如何处理愤怒的情绪#

将你的愤怒转为你的需求, 这样你的精力就会聚焦表达需求,解决问题,而非无用的愤怒上

  1. 冷静下来思考引发我愤怒的外部原因并且是实际事实, 而非主观评价

    是他对我做了什么什么成为了我愤怒的诱因,而不是他太sb成为诱因

  2. 找出你愤怒时对对方的内在形象和评价, 确认你有产生了一定的评判形象。

    他在针对我!他很自私让我很气!

  3. 剥离掉1和2即剥离外部事实和评判形象后, 思考自己是自己的什么需求没有得到满足

    我想要健康的身体,但是他打了我让我无法有健康的身体

[toc]

第一题:找规律题#

738. 单调递增的数字 - 力扣(LeetCode)

1661791574759

就是找规律

从左往右,确认前后是否满足x<=y,是就继续往下遍历

当发现不满足时, 要么是2466111这种有2个6,要么是2456111这种有1个6

前者要变成2459999, 后者变成2455999

可以看到就是找到6的最左边,把他变成6-1之后, 后面的全是9即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public int monotoneIncreasingDigits(int n) {
String s = String.valueOf(n);
boolean is9 = false;
int max = 0;
char[] cs = new char[s.length()];
for (int i = 0; i < s.length();i++) {
if (is9) {
cs[i] = '9';
continue;
}

int num = s.charAt(i) - '0';

if (i+1 < s.length() && num > s.charAt(i+1) - '0') {
// 往前面找到可以满足减1的
int j = i;
while(j>=0&&num == s.charAt(j) - '0') {
j--;
}
j++;
i = j;
cs[i] = (char)(s.charAt(i) - 1);
is9 = true;
} else {
cs[i] = (char)(num + '0');
max = Math.max(num, max);
}
}
return Integer.valueOf(new String(cs));
}
}

第二题:找重复数字就要记得利用位运算异或#

645. 错误的集合 - 力扣(LeetCode)

1661791743394

有时间O(nlogn)的排序解法(排序的时候求丢失的数字也很麻烦)

也有空间O(n)的哈希表解法

最佳应该是时间O(N)空间O(1),即利用位运算特点

将当前数组和1~n数组合并成1个新数组

那么丢失的数字a只有1个, 重复的数字b则有3个, 其他的都是2个

通过异或得到a^b的值

那么就是经典的分组方法, 先找到a^b中不相同的某一位, 然后进行划分分组, 基于划分后的分组再做异或,就能得到2个值了。

再遍历一次就能知道谁丢了谁重复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public int[] findErrorNums(int[] nums) {
int xorSum = 0;
for (int i = 0; i < nums.length;i++) {
xorSum ^= (i+1);
xorSum ^= nums[i];
}
int diffBit = xorSum & (-xorSum);
int sum0 = 0, sum1 = 0;
for (int i = 0; i < nums.length;i++) {
if ((nums[i] & diffBit) > 0) {
sum1 ^= nums[i];
} else {
sum0 ^= nums[i];
}

if (((i+1) & diffBit) > 0) {
sum1 ^= (i+1) ;
} else {
sum0 ^= (i+1) ;
}
}

for (int num : nums) {
if (num == sum1) {
return new int[]{sum1, sum0};
} else if (num == sum0){
return new int[]{sum0, sum1};
}
}
return new int[]{};
}

}

第三题:双指针处理退格问题#

844. 比较含退格的字符串 - 力扣(LeetCode)

1661791931016

最佳解法肯定是双指针, 空间O1, 不要用栈, 从右往左遍历即可

但是边界条件处理比较恶心,记得在while循环里判断,别放到外面搞,比较麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public boolean backspaceCompare(String s, String t) {
int si = s.length()-1, ti = t.length()-1;
int spopCount = 0, tpopCount = 0;
while(true) {
while(si >=0 && (s.charAt(si) == '#' || spopCount > 0)) {
if (s.charAt(si) == '#') {
spopCount++;
} else {
spopCount--;
}
si--;
}

while(ti >=0 && (tpopCount >0 || t.charAt(ti) == '#')) {
if (t.charAt(ti) == '#') {
tpopCount++;
} else {
tpopCount--;
}
ti--;
}
if (si ==-1 && ti == -1) {
return true;
} else if (si == -1 || ti == -1) {
return false;
}
if (si >=0 && ti >= 0 && s.charAt(si) != t.charAt(ti)) {
return false;
}
si--;
ti--;
}
}


}

[toc]


Q: 异或^有哪些性质?#

A:
a^a = 0
如果知道abc
那么a = (abc) (bc)


快速只保留末尾的1,其他全置0(例如1010100->0000100)#

b &(-b)


快速剔除末尾的1,其他保留(例如1010100->1010000)#

b &(b-1)
这个操作可以用来快速遍历位数组中的1。

[判断数字是否是2的幂](

如何快速求0000100的1是第几个位置?#

A:
java的Integer.bigCount可以快速计算1的数量
那么减1就变成了0000011
int digit = Integer.bitCount(digitMask - 1); 得到是第2位。



https://leetcode-cn.com/problems/power-of-two/submissions/)


利用不同的位进行分组#

可能的位运算问题,可以考虑“各位的数量"
例如只有1个数字是1个,其他都是3个3个出现,那么可以从每位的1的个数,推断出那个数字(其他肯定要么是1的3倍或者0倍,加起来)
例题:137. 只出现一次的数字 II
上面的状态变化也可以用数字电路的卡诺图进行处理,每次都是一次明确的位状态变更,不需要遍历32次。

也可也i注意利用a^b的结果,找到其中第一个不同的位进行分组(可以用b &(-b))



相关题目#

37. 解数独 用位运算可以节省空间

844. 比较含退格的字符串 - 力扣(LeetCode)

面试题 17.19. 消失的两个数字 - 力扣(LeetCode)

[toc]

概念和场景#

做法#

拓扑排序算法步骤:

  1. 得到所有节点的相邻点列表
  2. 得到所有节点的入度数量数组(切记,不可用相邻点列表代替入度数量,会炸的,因为过程中会改变,很麻烦的)
  3. 每次选入度为0的点做处理,对相邻点的入度数量做更新,减1
  4. 如果每次选很费时间,可以引入队列,队列时,每次要把队列里的全取出来再处理,而不是依次处理。每次出队的那一堆都是临时答案。

模板#

简易版本(n<=1000)#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int[] inCount = new int[k+1];
List<Integer>[] outLists = new List[k+1];
for (int i = 1; i <=k;i++) {
outLists[i] = new ArrayList();
}
for (int[] row : rowConditions) {
inCount[row[1]]++;
outLists[row[0]].add(row[1]);
}
int y = 0;
boolean[] vis = new boolean[k+1];
int n = inCount.length;
while (n-->0) {
int i;
for (i = 1; i <= k; i++) {
if (inCount[i] > 0 || vis[i]) {
continue;
}
vis[i] = true;
for (int out : outLists[i]) {
inCount[out]--;
}
// 记录这个点
break;
}
}

带队列的高性能版本(但容易写错)#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<Integer> result = new ArrayList<>();
while(!queue.isEmpty()) {
int size = queue.size();
List<Integer> needDeleteNodes = new ArrayList<>();
needDeleteNodes.addAll(queue);
queue.clear();
// 要每次都更新这个result
result = new ArrayList<>();
for (int needDeleNode : needDeleteNodes) {
result.add(needDeleNode);
for (int nextNode : nextNodes[needDeleNode]) {
// 不能delete
edgCounts[nextNode]--;
if (edgCounts[nextNode] == 1) {
queue.offer(nextNode);
}
}
}
}

相关题目#

310. 最小高度树 - 力扣(LeetCode)

6163. 给定条件下构造矩阵 - 力扣(LeetCode)

1661701435825

本期总结:#

  1. 注意拓扑排序的板子,别写错了重复判断的问题,别瞎搞一个inCount[i] = -1这种,不如老实弄个vis

6160. 和有限的最长子序列 - 力扣(LeetCode)

排个序就好了,没啥难的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {

public int[] answerQueries(int[] nums, int[] queries) {
Arrays.sort(nums);
int[] res = new int[queries.length];
for (int i = 0; i < queries.length;i++) {
int sum = 0;
int len = 0;
for (int j = 0;j<nums.length;j++) {
if (sum + nums[j] > queries[i]) {
break;
}
sum += nums[j];
len++;
}
res[i] = len;
}
return res;
}
}

6161. 从字符串中移除星号 - 力扣(LeetCode)

1661701444253

既然每次要移除最左边, 我就从右往左边遍历, 碰到星号,说明下次碰到字母时要变星号,记录一个数字即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public String removeStars(String s) {
boolean[] bs = new boolean[s.length()];
int needDeduceCount = 0;
for (int i = s.length()-1;i>=0;i--) {
if (s.charAt(i) == '*') {
needDeduceCount++;
bs[i] = true;
} else if (needDeduceCount > 0) {
needDeduceCount--;
bs[i] = true;
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length();i++) {
if (bs[i]) {
continue;
}
sb.append(s.charAt(i));
}
return sb.toString();
}
}

6162. 收集垃圾的最少总时间 - 力扣(LeetCode)

1661701543477

这种题目啥情况,啥算法也不用,直接3次遍历即可,主要是得计算到最后一个有这个垃圾的房子时就要截至了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public int garbageCollection(String[] garbage, int[] travel) {
char[] cs = new char[]{'G', 'P', 'M'};
int res = 0;
for (char c : cs) {
int sum = 0;
int realSum = 0;
for (int i = 0; i < garbage.length; i++) {
String garba = garbage[i];
int count = 0;
for (char g : garba.toCharArray()) {
if (g == c) {
count++;
}
}
if (count > 0) {
sum += count;
realSum = sum;
}
if (i != garbage.length-1) {
sum += travel[i];
}
}
res += realSum;
}
return res;
}
}

6163. 给定条件下构造矩阵 - 力扣(LeetCode)

1661701601482

1661701607970

想了半太天才意识到是拓扑排序, 越是叶子的,越可以放前面, 越是根的,越可以放后面

早点想到的话应该20分钟就做出来了,拓扑还是很简单的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Solution {
public int[][] buildMatrix(int k, int[][] rowConditions, int[][] colConditions) {
int[] rows = new int[k+1];
int[] cols = new int[k+1];
if (!sort(k, rowConditions, rows)
|| !sort(k, colConditions, cols)) {
return new int[][]{};
}

int[][] res = new int[k][k];
for (int i = 1; i <=k;i++) {
res[rows[i]][cols[i]] = i;
}
return res;
}

private boolean sort(int k, int[][] rowConditions, int[] rows) {
int[] inCount = new int[k+1];
List<Integer>[] outLists = new List[k+1];
for (int i = 1; i <=k;i++) {
outLists[i] = new ArrayList();
}
for (int[] row : rowConditions) {
inCount[row[1]]++;
outLists[row[0]].add(row[1]);
}
int y = 0;
boolean[] vis = new boolean[k+1];
while (true) {
int i;
for (i = 1; i <= k; i++) {
if (inCount[i] > 0 || vis[i]) {
continue;
}
vis[i] = true;
for (int out : outLists[i]) {
inCount[out]--;
}
rows[i] = y;
y++;
break;
}
if(i == k+1) {
break;
}
}
for (int i = 1;i<=k;i++) {
if (inCount[i] >0) {
return false;
}
}
return true;
}
}

[toc]

为什么我们需要IOC#

IoC是随着近年来轻量级容器(Lightweight Container)的兴起而逐渐被很多人提起的一个名词,它 的全称为Inversion of Control,中文通常翻译为“控制反转”,它还有一个别名叫做依赖注入(Dependency Injection)

本质上就是:
如果某个Java对象里有一些成员
那么尽量不要把这些成员写死(例如某个成员的类型是A, 不要写死成A=new A1()或者A=new A2())
而要通过 构造器 或者get、set来注入进来

换句话说,在这个对象的实现中,我们看不到成员对象的具体赋值, 一切都通过外部来决定恒成员是什么

而在Spring里,这个构造器和get、set都通过IOC SerivceProvider来提供
这个图很经典
在这里插入图片描述

3种注入方式:#

  • 构造方法注入:Ioc用构造方法去注入。
    好处是一旦构造好了,就可以马上用。 而且只需要对应类名和参数列表。缺点是参数多的话会很麻烦。

  • setter方法注入: Ioc用setXXX去注入成员。
    良好。易扩展。通过set去设置成员。

  • 接口注入: 必须要实现接口,然后接口里方法的参数为真正传入的成员对象。这个方式不提倡,有侵入性,一旦新增,其他全要开

接口注入看了好几遍才想明白是啥意思,换个通俗易懂的解释:

  • 构造方法注入: Ioc只要知道类名和构造参数, 即可给你用反射传进去,。
  • set方法注入: Ioc只要类名和你有哪些成员, 即可写反射的代码给你注入,都是setXXX,处理方式不会有变动。
  • 接口方注入: Ioc 必须知道你有哪些注入接口,才能给你注入。 比如你用Init(A a)来注入, 于是Ioc必须也指定init这个方法, 再加参数,才给给你注入
    明显接口注入,如果init方法改名了,可能会造成大面积的注入修改(因为其他地方可能都用了Init去构造了,然后爆炸)
    而构造和set方式几乎都是相同的注入操作,只要改入参和类名即可。

Q: 构造注入和set注入,哪个更好,为什么?#

A:
set注入更好。因为可以解决循环依赖问题
A中有成员B, B中有成员A, 如果都用构造方法注入,则A和B new的过程就存在循环依赖。
但是用set的话,就可以先生成A和B的实体,再分别通过set注入到成员中。


Q: spring解决循环依赖的过程?#

A:

  1. 开始初始化对象A
    singletonFactories:(刚构造完,也没人用过)
    earlySingletonObjects:(如果构造过且被别人依赖过,都放进ealy中)
    singletonObjects:(已经完全构建和注入完成)
    这3个缓存选取的优先顺序是从下往上。

  2. 调用A的构造,把A放入singletonFactories
    singletonFactories:A
    earlySingletonObjects:
    singletonObjects:

  3. 开始注入A的依赖,发现A依赖对象B

  4. 开始初始化对象B

  5. 调用B的构造,把B放入singletonFactories
    singletonFactories:A,B
    earlySingletonObjects:
    singletonObjects:

  6. 开始注入B的依赖,发现B依赖对象A

  7. 开始初始化对象A,发现A在singletonFactories里有,则直接获取A,
    并把A放入earlySingletonObjects,把A从singletonFactories删除
    singletonFactories:B
    earlySingletonObjects:A
    singletonObjects:

  8. 对象B刚才拿到了A对象,依赖注入完成

  9. 对象B创建完成,把B放入singletonObjects(即完成创建了),
    singletonFactories:
    earlySingletonObjects:A
    singletonObjects:B

  10. 对象B注入给A,继续注入A的其他依赖,直到A注入完成

  11. 对象A创建完成,把A放入singletonObjects,
    singletonFactories:
    earlySingletonObjects:
    singletonObjects:A,B

  12. 循环依赖处理结束,A和B都初始化和注入完成
    (Spring解决循环依赖的方法)[https://blog.csdn.net/lkforce/article/details/97183065]

IOC ServiceProvider#

在这里插入图片描述

就是如何相当于服务员
对于业务类,不需要知道自己的成员具体是谁
而如何确定自己的真正成员,来自于IOC SeviceProverdier

。它可以是一段代码,也可以是一组相关的类,甚至可以是比较通用的IoC框架或 者IoC容器实现
。Spring 的IoC容器就是一个提供依赖注入服务的IoC Service Provider

IOCSP的职责:
业务对象的构建管理,即怎么创建依赖对象
业务对象间 的依赖绑定, 即怎么确定谁依赖谁。

IOC SeviceProverdier的3种管理方式:#

1. 

直接编码,例如写1个map来做 类->具体子类的对应关系。然后在写map.put. get之类的代码来自己主动注入
2.
文本文件(xml、配置文件),然后自己去getXMl或者getPropetris解析
3.
注解, 相当于不用自己写map的put、get, 而是通过某个框架的注解,确定注入方式,然后去在那个框架对应的代码或者配置文件中写就行了。 其实就是1、2的升级版,不用自己去做解析和存储操作,你只要去框架那里写对应依赖关系即可。

[toc]

第一题:二叉树节点在某层是第几个的求解方法#

662. 二叉树最大宽度 - 力扣(LeetCode)

1661612722021

注意是某一层最左边和最右边节点的距离,不包含右边的null

那么只要知道如果是完全二叉树的话, 他是这一层的第几个即可。

那就是父亲节点列索引乘2或者乘2+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res = 0;
public int widthOfBinaryTree(TreeNode root) {
dfs(root, 0, 0);
return res;
}
Map<Integer, Integer> minHIndex = new HashMap<>();
void dfs(TreeNode node, int index, int h) {
if (node == null) {
return;
}
if (!minHIndex.containsKey(h)) {
minHIndex.put(h, index);
}
res = Math.max(index - minHIndex.get(h) + 1, res);


dfs(node.left, index*2, h+1);
dfs(node.right, index*2+1, h+1);
}
}

第二题 基于树的简单动态规划#

823. 带因子的二叉树 - 力扣(LeetCode)

1661612958186

排个序,然后按顺序求每个节点作为根节点的情况数,并放入缓存中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public int numFactoredBinaryTrees(int[] arr) {
Arrays.sort(arr);
int mod = 1000000007;
Map<Integer, Long> map = new HashMap<>();
long res = 0;
for (int i = 0; i < arr.length;i++) {
int big = arr[i];
long count = 1;
for (int j = i-1;j>=0;j--) {
int numLeft = arr[j];
if (big % numLeft != 0) {
continue;
}
int numRight = big / numLeft;
if (!map.containsKey(numRight)) {
continue;
}
long count1 = map.get(numLeft);
long count2 = map.get(numRight);
count += count1 * count2;
count %= mod;
}
map.put(big, count);
res += count;
res %= mod;
}
return (int)res;
}
}

第三题 简单链表+哈希题#

1836. 从未排序的链表中移除重复元素 - 力扣(LeetCode)

1661612941375

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicatesUnsorted(ListNode head) {
ListNode newHead = new ListNode(0);
newHead.next = head;
ListNode node = head;
Map<Integer, Integer> map = new HashMap<>();
while(node != null) {
map.put(node.val, map.getOrDefault(node.val, 0) + 1);
node = node.next;
}
ListNode lastNode = newHead;
node = head;
while(node != null) {
if (map.get(node.val) > 1) {
lastNode.next = node.next;
node = node.next;
} else {
lastNode = node;
node = node.next;
}
}
return newHead.next;
}
}

[toc]

第一题简单dfs#

LCP 44. 开幕式焰火 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
Set<Integer> set = new HashSet<>();
public int numColor(TreeNode root) {
dfs(root);
return set.size();
}
void dfs(TreeNode node) {
if (node == null) {
return;
}
set.add(node.val);
dfs(node.left);
dfs(node.right);
}
}

第二题-记忆化搜索#

473. 火柴拼正方形 - 力扣(LeetCode)

用记忆化搜索有点长,看下答案的动态规划咋整的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
public boolean makesquare(int[] matchsticks) {
int sum = Arrays.stream(matchsticks).sum();
if (sum % 4!=0) {
return false;
}
int[] vis = new int[matchsticks.length];
Arrays.fill(vis,1);
return dfs(vis,
matchsticks, new HashSet<>(), 0, sum/4);

}

boolean dfs(int[] vis, int[] matchsticks, Set<Integer> set, int nowSum, int target) {
int k = 1;
int sum = 0;
for (int v :vis) {
sum += v * k;
k*=2;
}

if (nowSum == target) {
nowSum = 0;
} else if(nowSum > target) {
set.add(sum);
return false;
}

if (nowSum == 0 && sum == 0) {
return true;
}
if (set.contains(sum)) {
return false;
}


for (int i = 0; i < matchsticks.length;i++) {
if (vis[i] == 0) {
continue;
}
vis[i] = 0;
if(dfs(vis, matchsticks, set, nowSum + matchsticks[i], target)) {
return true;
}
vis[i] = 1;
}
set.add(sum);
return false;
}
}

实际上不需要考虑某个火柴放在第0个边还是第4个边,直接按边摆

当你用了第1、3、7、9个火柴时,可以直接尝试往边上拼,正好拼满一个边就下一个

dp[状态压缩标志位]表示当前正在摆的那个边的长度

第三题 枚举回文数,只要枚举前一半#

906. 超级回文数 - 力扣(LeetCode)

1661531403264

18位回文数,但又要求是回文数的平方

所以开根号的值是9位, 且必须是回文数

那么9位回文数,最多只要求 前4位的数字,加中间可有可无的一个数字即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Solution {
public int superpalindromesInRange(String left, String right) {
dfs(0, new StringBuilder());
Long leftNum = Long.valueOf(left);
Long rightNum = Long.valueOf(right);
int leftIndex = -1;
int rightIndex = -1;
res.add(1L);
res.add(4L);
res.add(9L);
List<Long> list = new ArrayList<>(res);
Collections.sort(list);
System.out.println(list);
for (int i = 0;i < res.size(); i++) {
if (leftIndex == -1 && leftNum<= list.get(i)) {
leftIndex = i;
}
if (rightIndex == -1 && rightNum <= list.get(i)) {
rightIndex = i;
break;
}
}
if (rightIndex == -1) {
rightIndex = list.size();
}
System.out.println("right=" + rightIndex + ";left=" + leftIndex);
return rightIndex - leftIndex;
}
Set<Long> res = new HashSet<>();
void dfs(int index, StringBuilder sb) {
if (index == 4) {
String s = Integer.valueOf(sb.toString()).toString();
if (s.equals("0")) {
return;
}
for (char i = '0'; i <='9';i++) {
String newS = s + i + new StringBuilder(s).reverse().toString();
doPalid(newS);
}
String newS = s + new StringBuilder(s).reverse().toString();
doPalid(newS);
return;
}
for (char i = '0'; i <='9';i++) {
dfs(index+1, sb.append(i));
sb.deleteCharAt(sb.length()-1);
}
}

public void doPalid(String newS) {
Long newNum = Long.valueOf(newS);
Long mulNum = newNum * newNum;
// 看是否是回文数
String mulNumS = String.valueOf(mulNum);
if (mulNumS.equals(new StringBuilder().append(mulNumS).reverse().toString())) {
res.add(mulNum);
}
}

}