-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
1293 lines (1293 loc) · 245 KB
/
search.xml
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[类的加载机制]]></title>
<url>%2F2019%2F05%2F31%2F%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6%2F</url>
<content type="text"><![CDATA[把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。 类的生命周期加载,验证,准备,解析,初始化,使用和卸载。其中验证,准备,解析3个部分统称为连接。 加载,验证,准备,初始化,卸载这5个阶段的顺序是确定的,而解析阶段则不一定:它在某些情况下可以在初始化完成后在开始,这是为了支持Java语言的运行时绑定。 何时触发初始化 为一个类型创建一个新的对象实例时(比如new、反射、序列化) 调用一个类型的静态方法时(即在字节码中执行invokestatic指令) 调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式 使用java.lang.reflect包的方法对类进行反射调用的时候。 当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先出发父类的初始化。 JVM启动包含main方法的启动类时。被动引用 子类调用父类的静态变量,子类不会被初始化,只有父类被初始化。对于静态字段,只有直接定义这个字段的类才会被初始化. 通过数组定义来引用类,不会触发类的初始化 访问类的常量,不会初始化类类的加载过程加载加载阶段是类加载过程的第一个阶段。在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中,接着会为这个类在 JVM 的方法区创建一个对应的 Class 对象,这个 Class 对象就是这个类各种数据的访问入口。验证是连接阶段的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 包含四个阶段的校验动作 文件格式验证验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。 元数据验证对类的元数据信息进行语义校验,是否不存在不符合Java语言规范的元数据信息 字节码验证最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。 符号引用验证最后一个阶段的校验发生在虚拟机将符号引用转换为直接引用的时候,这个转换动作将在连接的第三个阶段——解析阶段中发生。 符号验证的目的是确保解析动作能正常进行。 准备准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。 12//在准备阶段value初始值为0。在初始化阶段才会变为123。public static int value=123; “特殊情况”下,如果类字段的字段属性表中存在ConstantValue(说明)属性,那么在准备阶段变量的值就会被初始化为ConstantValue属性所指定的值。 解析虚拟机将常量池内的符号引用替换为直接引用的过程。“动态解析”的含义就是必须等到程序实际运行到这条指令的时候,解析动作才能进行。相对的,其余可触发解析的指令都是“静态”的,可以在刚刚完成加载阶段,还没有开始执行代码时就进行解析。 初始化类加载过程中的最后一步。初始化阶段是执行类构造器()方法的过程。 ()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。 ()与类的构造函数不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的()方法执行之前,父类的()方法已经执行完毕。 简单地说,初始化就是对类变量进行赋值及执行静态代码块。 使用当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。这个阶段也只是了解一下就可以。 卸载当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。这个阶段也只是了解一下就可以。 类加载器Java中的类加载器 启动类加载器(Bootstrap ClassLoader): 由C++语言实现(针对HotSpot),负责将存放在\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中,即负责加载Java的核心类。 其他类加载器: 由Java语言实现,继承自抽象类ClassLoader。如: 扩展类加载器(Extension ClassLoader): 负责加载\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库,即负责加载Java扩展的核心类之外的类。 应用程序类加载器(Application ClassLoader): 负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器,通过ClassLoader.getSystemClassLoader()方法直接获取。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。 Android中的类加载器 Android中的ClassLoader类型也可分为系统ClassLoader和自定义ClassLoader BootClassLoader:Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的Bootstrap ClassLoader不同的是,它并不是由C/C++代码实现,而是由Java实现的。BootClassLoader是ClassLoader的一个内部类。 PathClassLoader:全名是dalvik/system.PathClassLoader,可以加载已经安装的Apk,也就是/data/app/package 下的apk文件,也可以加载/vendor/lib, /system/lib下的nativeLibrary。 DexClassLoader:全名是dalvik/system.DexClassLoader,可以加载一个未安装的apk文件。 PathClassLoader和DexClasLoader都是继承自 dalviksystem.BaseDexClassLoader,它们的类加载逻辑全部写在BaseDexClassLoader中。 双亲委派模型双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。 这样的好处是不同层次的类加载器具有不同优先级,比如所有Java对象的超级父类java.lang.Object,位于rt.jar,无论哪个类加载器加载该类,最终都是由启动类加载器进行加载,保证安全。即使用户自己编写一个java.lang.Object类并放入程序中,虽能正常编译,但不会被加载运行,保证不会出现混乱。]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>基础</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HashMap源码]]></title>
<url>%2F2019%2F05%2F20%2FHashMap%E6%BA%90%E7%A0%81%2F</url>
<content type="text"><![CDATA[HashMap是由数组和链表组合构成的数据结构,Java8中链表长度超过8时会把长度超过8的链表转化成红黑树;存取时都会根据键值计算出“类别”(hashCode),再根据”类别”定位到数组中的位置并执行操作。 HashMap 的工作原理HashMap 是基于 hashing 的原理我们使用 put(key, value) 存储对象到 HashMap 中,使用 get(key) 从 HashMap 中获取对象。当我们给 put() 方法传递键和值时,我们先对键调用 hashCode() 方法,计算并返回的 hashCode 是用于找到 Map 数组的 bucket 位置来储存 Node 对象。这里关键点在于指出,HashMap 是在 bucket 中储存键对象和值对象,作为Map.Node 。 put 过程(JDK1.8) 对 Key 求 Hash 值,然后再计算下标 如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的 Hash 值相同,需要放到同一个 bucket 中) 如果碰撞了,以链表的方式链接到后面 如果链表长度超过阀值(TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表 如果节点已经存在就替换旧值 如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排) JDK7流程,如图 JDK8流程,如图 7和8对比1.发生hash冲突时,Java7会在链表头部插入,Java8会在链表尾部插入2.扩容后转移数据,Java7转移前后链表顺序会倒置,Java8还是保持原来的顺序3.引入红黑树的Java8大程度得优化了HashMap的性能 线程不安全HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。 参考文章https://juejin.im/post/5a23f82ff265da432003109b#heading-7http://www.importnew.com/31278.htmlhttps://blog.csdn.net/zaimeiyeshicengjing/article/details/81589953]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>基础</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Retrofit源码]]></title>
<url>%2F2019%2F05%2F09%2FRetrofit%20%E6%BA%90%E7%A0%81%2F</url>
<content type="text"><![CDATA[Retrofit就是一个网络请求框架的封装,底层的网络请求默认使用的Okhttp,本身只是简化了用户网络请求的参数配置等,还能与Rxjava相结合,使用起来更加简洁方便。 下图是请求的流程图: 请求步骤 通过Builder模式,添加所需的baseUrl,callFactory(实际就是OkHttpClient),converterFactories(将服务器返回数据转为所需的数据,例如bean)和callAdapterFactories(根据需要是否创建call转换器,例如RxJava),创建Retrofit实例 定义网络请求接口,并为接口中的方法添加注解 通过动态代理为请求接口创建实例 通过loadServiceMethod()方法创建提供call转换器,数据转换器和发起网络请求必须的参数(前两者实际为Retrofit类创建) 构建OkHttpCall 发起网络请求(实际执行是由OkHttp来做具体网络请求工作) 运用到的设计模式外观模式:核心类就是Retrofit,我们只管配置Retrofit,然后做请求。剩下的事情就跟上层无关了,只需要等待回调。这样大大降低了系统的耦合度; Builder模式:构建Retrofit所需的必须参数; 动态代理:动态代理就是拦截调用的那个方法,在方法前后来做一些操作。为网络请求接口动态创建实例; 工厂模式:通过工厂创建CallAdapter,Converter; 适配器模式:根据不同类型返回与之匹配的操作类,例如如果与RxJava配合就需要返回RxJava2CallAdapterFactory,不需要默认是ExecutorCallAdapterFactory; 装饰模式:跟静态代理类似,例如可以ExecutorCallAdapterFactory装饰类,实际操作者是OKHttpCall; 参考文章https://www.jianshu.com/p/fb8d21978e38]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>框架</tag>
</tags>
</entry>
<entry>
<title><![CDATA[矩阵中的最长递增路径]]></title>
<url>%2F2019%2F05%2F06%2F%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E8%B7%AF%E5%BE%84%2F</url>
<content type="text"><![CDATA[LeetCode 题号:329 给定一个整数矩阵,找出最长递增路径的长度。对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。 示例 1: 12345678输入: nums = [ [9,9,4], [6,6,8], [2,1,1]] 输出: 4 解释: 最长递增路径为 [1, 2, 6, 9]。 示例 2: 12345678输入: nums = [ [3,4,5], [3,2,6], [2,2,1]] 输出: 4 解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。 我的解答 解题思路:记忆化搜索,获取递增序列,记录每个点能达到的最长路径,遍历完矩阵得到最长递增路径 123456789101112131415161718192021222324252627282930313233343536373839class Solution { int[][] visited; int m, n; public int longestIncreasingPath(int[][] matrix) { if (matrix.length == 0 || matrix[0].length == 0) return 0; m = matrix.length; n = matrix[0].length; int res = 1; visited = new int[m][n]; for (int i = 0; i < m; i++){ for (int j = 0; j < n; j++){ if (visited[i][j] != 0) continue; res = Math.max(res, dfs(matrix, i, j, Integer.MIN_VALUE)); } } return res; } public int dfs(int[][] matrix, int i, int j, int oldVal){ // 检查当前坐标是否合法,是否递增 if (i < 0 || i >= m || j < 0 || j >= n || matrix[i][j] <= oldVal) return 0; // 检查当前坐标是否已被访问 if (visited[i][j] == 0){ // 对四个方向递归调用dfs int curVal = matrix[i][j]; int up = dfs(matrix, i + 1, j, curVal); int down = dfs(matrix, i - 1, j, curVal); int right = dfs(matrix, i, j + 1, curVal); int left = dfs(matrix, i, j - 1, curVal); // 设置当前坐标的最长递增路径 visited[i][j] = 1 + Math.max(Math.max(up, down), Math.max(right, left)); } return visited[i][j]; }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>动态规划</tag>
<tag>困难</tag>
</tags>
</entry>
<entry>
<title><![CDATA[零钱兑换]]></title>
<url>%2F2019%2F05%2F05%2F%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%2F</url>
<content type="text"><![CDATA[LeetCode 题号:322 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 示例 1: 123输入: coins = [1, 2, 5], amount = 11输出: 3 解释: 11 = 5 + 5 + 1 示例 2: 12输入: coins = [2], amount = 3输出: -1 我的解答 解题思路:使用动态规划,遍历amount,记录当i元时最少需要的硬币数,如果i-coins[j]下标的值为-1,证明没有这种硬币组合,反之就是dp[i] = dp[i-coins[j]]+1;遍历完dp[amount] 就是最后的结果 123456789101112131415public static int coinChange(int[] coins, int amount) { int[] dp = new int[amount+1]; for (int i=1;i<=amount;i++){ int min = Integer.MAX_VALUE; for (int j=0;j<coins.length;j++){ int value = i-coins[j]; if (value>=0 && dp[value] != -1){ min = Math.min(min,dp[value]+1); } } min = min==Integer.MAX_VALUE ? -1 : min; dp[i] = min; } return dp[amount];}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>动态规划</tag>
</tags>
</entry>
<entry>
<title><![CDATA[和为K的子数组]]></title>
<url>%2F2019%2F04%2F30%2F%E5%92%8C%E4%B8%BAK%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%2F</url>
<content type="text"><![CDATA[LeetCode 题号:560 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。 示例 1 : 12输入:nums = [1,1,1], k = 2输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。 说明 : 数组的长度为 [1, 20,000]。 数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。 我的解答 解题思路:通过map来记录,key为下标0到i的和,如果sum[i]-sum[j] = k,说明在[i,j]区间的和满足于k,map的value表示满足k的个数 12345678910111213public int subarraySum(int[] nums, int k) { Map<Integer, Integer> map = new HashMap<>(); map.put(0, 1); int sum = 0, ret = 0; for(int i = 0; i < nums.length; ++i) { sum += nums[i]; if(map.containsKey(sum-k)) ret += map.get(sum-k); map.put(sum, map.getOrDefault(sum, 0)+1); } return ret;} 延伸一道阿里的面试题 给出一个正整数数组和一个数,返回一个数组走那个连续元素的和等于所给数的子数组 加分时间复杂度为2n 我的解答 解法一(暴力法) 123456789101112131415161718192021222324public static int[] continuousSum(int[] nums, int k){ int sum = 0; List<Integer> list = new ArrayList<>(); boolean isFind = false; for (int i=0;i<nums.length;i++){ sum = 0; for (int j=i;j<nums.length;j++){ sum +=nums[j]; list.add(nums[j]); if (sum == k){ isFind = true; break; } } if (isFind) break; list.clear(); } int[] arr = new int[list.size()]; for (int i=0;i<list.size();i++){ arr[i] = list.get(i); } return arr;} 解法二 解题思路:遍历数组nums,通过不断累加数组元素sum,如果sum = k,结束遍历记录当前的下标值end,如果sum>k,删除sum的头部下标值start;最后通过start和end的来获取nums的区间值 12345678910111213141516171819public static int[] continuousSum(int[] nums, int k){ int sum = 0, start = 0, end = 0; for(int i = 0; i < nums.length; ++i) { sum += nums[i]; end = i; if(sum == k){ end = i; break; }else if (sum > k){ sum = sum - nums[start++]; } } int[] arr = new int[end - start]; for (int i=start;i<end;i++){ arr[i-start] = nums[i+1]; } return arr;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数组</tag>
<tag>中等</tag>
</tags>
</entry>
<entry>
<title><![CDATA[最长连续序列]]></title>
<url>%2F2019%2F04%2F29%2F%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97%2F</url>
<content type="text"><![CDATA[LeetCode 题号:300 给定一个无序的整数数组,找到其中最长上升子序列的长度。 示例: 123输入: [10,9,2,5,3,7,101,18]输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 说明: 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 你算法的时间复杂度应该为 O(n**2) 。 进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗? 我的解答 解法一 解题思路:通过动态规划,通过dp数组不断记录在每个数组下标时,子序列最大的上升长度,最后在比较dp数组中最大的子序列长度 1234567891011121314public static int lengthOfLIS(int[] nums) { int[] dp = new int[nums.length]; int max = 0; for (int i=0;i<nums.length;i++){ dp[i] = 1; for (int j=0;j<i;j++){ if (nums[j]<nums[i]){ dp[i] = Math.max(dp[i],dp[j]+1); } } max = Math.max(max,dp[i]); } return max;} 解法二 解题思路:通过二分查找,从nums第一个元素添加到新数组dp中,然后不断和nums的之后元素进行比较,只要比dp数组中元素大,添加进来,增加上升序列的result长度,如果比dp数组中元素小,替换dp中的元素,遍历完nums数组,result+1就是nums中最长上升子序列的长度 1234567891011121314151617181920212223242526public static int lengthOfLIS(int[] nums) { if(nums.length==0){ return 0; } int result=0; int[] dp=new int[nums.length]; dp[0]=nums[0]; for(int i=1;i<nums.length;i++){ if(nums[i]>dp[result]){ dp[++result]=nums[i]; }else{ int left=0; int right=result; while(left<right){ int mid=(left+right)/2; if(nums[i]<=dp[mid]){ right=mid; }else{ left=mid+1; } } dp[right]=nums[i]; } } return result+1;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>动态规划</tag>
</tags>
</entry>
<entry>
<title><![CDATA[完全平方数]]></title>
<url>%2F2019%2F04%2F28%2F%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:279 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 示例 1: 123输入: n = 12输出: 3 解释: 12 = 4 + 4 + 4. 示例 2: 123输入: n = 13输出: 2解释: 13 = 4 + 9. 我的解答 解法一 解题思路:四平方定理: 任何一个正整数都可以表示成不超过四个整数的平方之和。 推论:满足四数平方和定理的数n(四个整数的情况),必定满足 n=4^a(8b+7) 12345678910111213141516171819202122public int numSquares(int n) { //先根据上面提到的公式来缩小n while (n % 4 == 0){ n /= 4; } if (n % 8 == 7){//满足四平方和定理 return 4; } //在判断缩小后的数是否可以由一个数的平方或者两个数平方的和组成 int a = 0; while(a * a < n) { int b = (int) Math.sqrt(n - a * a); if(a * a + b * b == n) { if(a == 0 && b == 0) return 0; else if(a != 0 && b != 0) return 2; else return 1; } a = a + 1; } //如果不是返回3 return 3;} 解法二:动态规划 解题思路:采用动态规划实现。用 dp[i] 数组存储第 i 个数的完美平方数。递推式为:dp[i] = Math.max(dp[j] + dp[i-j], dp[i],认为 i 的完全平方数是从和为 i 的两个完全平方数 dp[j] 和 dp[i-j]之和,然后从中取最小 123456789101112131415public int numSquares(int n) { int[] dp = new int[n+1]; Arrays.fill(dp, Integer.MAX_VALUE); dp[0] = 0; for (int i=1;i<=n;i++){ int min = Integer.MAX_VALUE; int j = 1; while(i - j*j >= 0) { min = Math.min(min, dp[i - j*j] + 1); j++; } dp[i] = min; } return dp[n];}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>动态规划</tag>
</tags>
</entry>
<entry>
<title><![CDATA[最长连续序列]]></title>
<url>%2F2019%2F04%2F26%2F%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97%2F</url>
<content type="text"><![CDATA[LeetCode 题号:128 给定一个未排序的整数数组,找出最长连续序列的长度。要求算法的时间复杂度为 O(n)。 示例: 123输入: [100, 4, 200, 1, 3, 2]输出: 4解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。 我的解答 解题思路:先将数组进行从小到大排序,然后遍历数组,如果前后两个数差值是1,tmp++;如果差值大于1,比较得出之前最大连续长度,tmp归0,重新计算;遍历完,在比较得出数组中最大连续长度 12345678910111213141516public static int longestConsecutive(int[] nums) { if (nums.length <= 1) return nums.length; Arrays.sort(nums); int length = 0,tmp = 0; for (int i=1;i<nums.length;i++){ int diff = nums[i]-nums[i-1]; if(diff == 1){ tmp++; }else if (diff>1){ length = Math.max(length,tmp+1); tmp = 0; } } length = Math.max(length,tmp+1); return length;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>动态规划</tag>
<tag>困难</tag>
</tags>
</entry>
<entry>
<title><![CDATA[二叉树中的最大路径和]]></title>
<url>%2F2019%2F04%2F25%2F%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C%2F</url>
<content type="text"><![CDATA[LeetCode 题号:124 给定一个非空二叉树,返回其最大路径和。本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。 示例 1: 1234567输入: [1,2,3] 1 / \ 2 3输出: 6 示例 2: 123456789输入: [-10,9,20,null,null,15,7] -10 / \ 9 20 / \ 15 7输出: 42 我的解答 解题思路:对于任意一个节点, 有4种情况:1.该节点本身(左右子树的路径为负)2.该节点+左子树路径3.该节点+右子树路径4.该节点+左子树路径+右子树路径其中1,2,3都可以作为子树路径和向上延伸,而4则不行 123456789101112131415private static int ret = Integer.MIN_VALUE;public static int maxPathSum(TreeNode root) { ret = Integer.MIN_VALUE; getMax(root); return ret;}private static int getMax(TreeNode r) { if(r == null) return 0; // 如果子树路径和为负则应当置0表示最大路径不包含子树 int left = Math.max(0, getMax(r.left)); int right = Math.max(0, getMax(r.right)); // 判断在该节点包含左右子树的路径和是否大于当前最大路径和 ret = Math.max(ret, r.val + left + right); return Math.max(left, right) + r.val;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>树</tag>
<tag>困难</tag>
</tags>
</entry>
<entry>
<title><![CDATA[至少有K个重复字符的最长子串]]></title>
<url>%2F2019%2F04%2F24%2F%E8%87%B3%E5%B0%91%E6%9C%89K%E4%B8%AA%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E4%B8%B2%2F</url>
<content type="text"><![CDATA[LeetCode 题号:395 找到给定字符串(由小写字符组成)中的最长子串 T , 要求 T 中的每一字符出现次数都不少于 k 。输出 T 的长度。 示例 1:1234567输入:s = "aaabb", k = 3输出:3最长子串为 "aaa" ,其中 'a' 重复了 3 次。 示例 2:1234567输入:s = "ababbc", k = 2输出:5最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。 我的解答 解题思路:使用两个指针left、right,和小写字母计数器统计字符段中各个字符出现的次数。如果中间有出现次数小于k的字符,然后调整当前的left、right指针,再遍历一遍修正后的字符串,最后 12345678910111213141516171819202122232425262728293031323334353637383940414243public static int longestSubstring(String s, int k) { if (s.length() < k) { return 0; } else if (k == 1) { return s.length(); } return longestSubstring(s, 0, s.length() - 1, k);}private static int longestSubstring(String s, int left, int right, int k) { if (right - left + 1 < k) { return 0; } int[] counts = new int[26]; for (int i = left; i <= right; i++) { counts[s.charAt(i) - 'a']++; } int last = left, maxLength = 0; boolean isSplit = false; for (int i = left; i <= right; i++) { if (isSplit(counts, s.charAt(i), k)) { maxLength = Math.max(maxLength, longestSubstring(s, last, i - 1, k)); last = i + 1; isSplit = true; } else if (i == right && isSplit) { maxLength = Math.max(maxLength, longestSubstring(s, last, right, k)); } } if (isSplit) { return maxLength; } else { return right - left + 1; }}private static boolean isSplit(int[] counts, char c, int k) { int i = c - 'a'; return counts[i] > 0 && counts[i] < k;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>动态规划</tag>
</tags>
</entry>
<entry>
<title><![CDATA[OkHttp源码]]></title>
<url>%2F2019%2F04%2F23%2FOkHttp%E6%BA%90%E7%A0%81%2F</url>
<content type="text"><![CDATA[OKHttp是Square处理网络请求的开源框架。 分析OkHttp源码,可以从发起一次完成请求开始: 下图是一次请求调用过程: 一次请求过程 创建一个Request,包含请求过程所需的url,method、headers以及body; OkHttpClient通过Request的对象,创建Call对象(实际是RealCall); 同步请求调用execute(),这时Dispatcher会将Call加入到双端队列runningSyncCalls; 异步请求调用enqueue(),回创建AsyncCal(实际是Runnable),Dispatche会将Call放到线程池中; 不管是异步还是同步,都会调用getResponseWithInterceptorChain(),这里会把 client 中的拦截器、重连拦截器、桥拦截器、缓存拦截器、网络连接拦截器和服务器请求拦截器等依次加入到列表中。然后,我们用这个列表创建了一个拦截器链。这里使用了责任链设计模式,每当一个拦截器执行完毕之后会调用下一个拦截器或者不调用并返回结果。显然,我们最终拿到的响应就是这个链条执行之后返回的结果。当我们自定义一个拦截器的时候,也会被加入到这个拦截器链条里; 执行完请求,会调用client.dispatcher().finished(this),将此次请求从队列中移除。 运用到的设计模式: 外观模式 : OKHttpClient 里面组合了很多的类对象。其实是将OKHttp的很多功能模块,全部包装进这个类中,让这个类单独提供对外的API,这种设计叫做外观模式(外观模式:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口) Builder模式 : OkHttpClient 比较复杂, 太多属性, 而且客户的组合需求多样化, 所以OKhttp使用建造者模式(Build模式:使用多个简单的对象一步一步构建成一个复杂的对象,一个 Builder 类会一步一步构造最终的对象) 工厂方法模式:Call接口提供了内部接口Factory(用于将对象的创建延迟到该工厂类的子类中进行,从而实现动态的配置,工厂方法模式。(工厂方法模式:这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。) 享元模式:在Dispatcher的线程池中,所用到了享元模式,一个不限容量的线程池 , 线程空闲时存活时间为 60 秒。线程池实现了对象复用,降低线程创建开销,从设计模式上来讲,使用了享元模式。(享元模式:尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象,主要用于减少创建对象的数量,以减少内存占用和提高性能) 责任链模式:在okhttp中的拦截器模块,执行过程用到。OkHttp3 的拦截器链中, 内置了5个默认的拦截器,分别用于重试、请求对象转换、缓存、链接、网络读写(责任链模式:为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。) 策略模式 :CacheInterceptor 实现了数据的选择策略, 来自网络还是来自本地? 这个场景也是比较契合策略模式场景, CacheInterceptor 需要一个策略提供者提供它一个策略, CacheInterceptor 根据这个策略去选择走网络数据还是本地缓存。缓存的策略过程: 请求头包含 “If-Modified-Since” 或 “If-None-Match” 暂时不走缓存 客户端通过 cacheControl 指定了无缓存,不走缓存 客户端通过 cacheControl 指定了缓存,则看缓存过期时间,符合要求走缓存。 如果走了网络请求,响应状态码为 304(只有客户端请求头包含 “If-Modified-Since” 或 “If-None-Match” ,服务器数据没变化的话会返回304状态码,不会返回响应内容), 表示客户端继续用缓存。 各个拦截器的作用 RetryAndFollowUpInterceptor 主要用来当请求失败的时候进行重试,以及在需要的情况下进行重定向。 BridgeInterceptor 用于从用户的请求中构建网络请求,然后使用该请求访问网络,最后从网络响应当中构建用户响应。 CacheInterceptor 根据请求的信息和缓存的信息判断缓存是否可用。如果可用,返回缓存给客户端,如果不可用继续从服务器中获取响应。 ConnectInterceptor 用来打开到指定服务器的网络连接,并交给下一个拦截器处理。这里只打开了一个网络连接,但是并没有发送请求到服务器。从服务器获取数据的逻辑交给下一级的拦截器来执行。 CallServerInterceptor 用来向服务器发起请求并获取数据。这是整个责任链的最后一个拦截器,这里没有再继续调用执行链的处理方法,而是把拿到的响应处理之后直接返回给了上一级的拦截器 参考文章https://www.androidos.net.cn/codebook/AndroidRoad/android/open-source-framework/okhttp.htmlhttps://juejin.im/post/5bc89fbc5188255c713cb8a5#heading-0https://blog.csdn.net/u012881042/article/details/79759203]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>框架</tag>
</tags>
</entry>
<entry>
<title><![CDATA[二叉树的序列化与反序列化]]></title>
<url>%2F2019%2F04%2F23%2F%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%2F</url>
<content type="text"><![CDATA[LeetCode 题号:297 序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。 示例: 123456789你可以将以下二叉树: 1 / \ 2 3 / \ 4 5序列化为 "[1,2,3,null,null,4,5]" 提示: 这与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。 说明: 不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。 我的解答 解题思路:序列化,通过层次遍历,将各个节点的值,用字符串拼接起来;反序列化,已知字符串格式,分割成数组,通过遍历,按照层次顺序,依次将数组的值,放到二叉树的各个节点上 12345678910111213141516171819202122232425262728293031323334353637383940414243444546public class Codec { // Encodes a tree to a single string. public String serialize(TreeNode root) { if (root == null) return ""; Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); StringBuffer sb = new StringBuffer(); while (!queue.isEmpty()){ TreeNode node = queue.poll(); if (node == null){ sb.append("null,"); }else { sb.append(node.val+","); queue.add(node.left); queue.add(node.right); } } return sb.toString(); } // Decodes your encoded data to tree. public TreeNode deserialize(String data) { if (data.length() == 0) return null; String[] dataArr = data.split(","); Queue<TreeNode> queue = new LinkedList<>(); int index = 0; TreeNode root = new TreeNode( Integer.parseInt(dataArr[index])); queue.add(root); while (!queue.isEmpty()){ TreeNode node = queue.poll(); String value = dataArr[++index]; if (!value.equals("null")){ node.left = new TreeNode(Integer.parseInt(value)); queue.add(node.left); } value = dataArr[++index]; if (!value.equals("null")){ node.right = new TreeNode(Integer.parseInt(value)); queue.add(node.right); } } return root; }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>树</tag>
<tag>困难</tag>
</tags>
</entry>
<entry>
<title><![CDATA[二叉树的最近公共祖先]]></title>
<url>%2F2019%2F04%2F22%2F%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88%2F</url>
<content type="text"><![CDATA[LeetCode 题号:236 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4] 示例 1: 123输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1输出: 3解释: 节点 5 和节点 1 的最近公共祖先是节点 3。 示例 2: 123输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4输出: 5解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。 说明: 所有节点的值都是唯一的。 p、q 为不同节点且均存在于给定的二叉树中。 我的解答 解题思路:LCA问题,通过递归,如果去p,q任意一个与root相匹配,那么root就是最近公共祖先。如果都不匹配,则分别递归左、右子树,如果有一个节点出现在左子树,并且另一个节点出现在右子树,则root就是最低公共祖先. 如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树。 123456789101112131415161718public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root == null) { return root; } if (root.val == p.val || root.val == q.val) { return root; } TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if (left != null && right != null) { return root; } else if (left != null) { return left; } else if (right != null) { return right; } return null;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>树</tag>
</tags>
</entry>
<entry>
<title><![CDATA[二叉搜索树中第K小的元素]]></title>
<url>%2F2019%2F04%2F19%2F%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%B8%AD%E7%AC%ACK%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:230 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。 说明:你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。 示例 1: 1234567输入: root = [3,1,4,null,2], k = 1 3 / \ 1 4 \ 2输出: 1 示例 2: 123456789输入: root = [5,3,6,2,4,null,null,1], k = 3 5 / \ 3 6 / \ 2 4 / 1输出: 3 进阶: 如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化 kthSmallest 函数? 我的解答 解题思路:已知是二叉搜索树,求k个最小。通过中序遍历,从最小的元素开始计数index,当index = k时,进行赋值,得出最k个小元素值 1234567891011121314public int index = 0,value = 0;public int kthSmallest(TreeNode root, int k) { travers(root,k); return value;}private void travers(TreeNode root,int k) { if (root == null) return; travers(root.left, k); index++; if (index == k){ value = root.val; } travers(root.right,k);}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>树</tag>
</tags>
</entry>
<entry>
<title><![CDATA[常数时间插入、删除和获取随机元素]]></title>
<url>%2F2019%2F04%2F18%2F%E5%B8%B8%E6%95%B0%E6%97%B6%E9%97%B4%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E5%92%8C%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:380 设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构。 insert(val):当元素 val 不存在时,向集合中插入该项。 remove(val):元素 val 存在时,从集合中移除该项。 getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。 示例 : 1234567891011121314151617181920212223// 初始化一个空的集合。RandomizedSet randomSet = new RandomizedSet();// 向集合中插入 1 。返回 true 表示 1 被成功地插入。randomSet.insert(1);// 返回 false ,表示集合中不存在 2 。randomSet.remove(2);// 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。randomSet.insert(2);// getRandom 应随机返回 1 或 2 。randomSet.getRandom();// 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。randomSet.remove(1);// 2 已在集合中,所以返回 false 。randomSet.insert(2);// 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。randomSet.getRandom(); 我的解答 解题思路:用List的来存储元素,用Map来存储元素和元素下标(加快元素的检索速度) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556public class RandomizedSet { private ArrayList<Integer> list; private HashMap<Integer,Integer> map; private Random random; /** Initialize your data structure here. */ public RandomizedSet() { list = new ArrayList<>(); map = new HashMap<>(); random = new Random(); } /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */ public boolean insert(int val) { if (map.containsKey(val)) { return false; } map.put(val,list.size()); list.add(val); return true; } /** Removes a value from the set. Returns true if the set contained the specified element. */ public boolean remove(int val) { if (map.containsKey(val)) { int index = map.get(val); //每次直接删除会导致list中的元素下标会发生变化,这里先将要删除的元 //素,用最后一个元素替换掉,再将最后一个元素删掉,达到删除的目的 if (index != list.size() - 1){ int last = list.get(list.size() - 1); list.set(index,last); map.put(last,index); } list.remove(list.size() - 1); map.remove(val); return true; } return false; } /** Get a random element from the set. */ public int getRandom() { int index = random.nextInt(list.size()); return list.get(index); }}/** * Your RandomizedSet object will be instantiated and called as such: * RandomizedSet obj = new RandomizedSet(); * boolean param_1 = obj.insert(val); * boolean param_2 = obj.remove(val); * int param_3 = obj.getRandom(); */]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>hash</tag>
<tag>中等</tag>
</tags>
</entry>
<entry>
<title><![CDATA[四数相加 II]]></title>
<url>%2F2019%2F04%2F17%2F%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0%20II%2F</url>
<content type="text"><![CDATA[LeetCode 题号:454 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。 例如: 123456789101112A = [ 1, 2]B = [-2,-1]C = [-1, 2]D = [ 0, 2]输出:2解释:两个元组如下:1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 02. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0 我的解答 解题思路:HashMap存前两个数组能组成的数字及其组成的次数,再遍历后两个数组,查看HashMap中是否存在两数和的相反数,如果存在,则记录出现次数,累加即为结果。 1234567891011121314151617public static int fourSumCount(int[] A, int[] B, int[] C, int[] D) { int len = A.length; int count = 0; Map<Integer,Integer> map = new HashMap<>(); for (int i=0;i<len;i++){ for (int j=0;j<len;j++){ map.put(A[i]+B[j],map.getOrDefault(A[i]+B[j],0) + 1); } } for (int m=0;m<len;m++){ for (int n=0;n<len;n++){ int tmp = -(C[m]+D[n]); count += map.getOrDefault(tmp,0); } } return count;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>hash</tag>
<tag>中等</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Excel表列序号]]></title>
<url>%2F2019%2F04%2F16%2FExcel%E8%A1%A8%E5%88%97%E5%BA%8F%E5%8F%B7%2F</url>
<content type="text"><![CDATA[LeetCode 题号:171 给定一个Excel表格中的列名称,返回其相应的列序号。例如, 12345678A -> 1B -> 2C -> 3...Z -> 26AA -> 27AB -> 28 ... 示例 1: 12输入: "A"输出: 1 示例 2: 12输入: "AB"输出: 28 示例 3: 12输入: "ZY"输出: 701 我的解答 解题思路:遍历字符串,已知字母的hash值,通过阶乘、累加得出结果 123456789public static int titleToNumber(String s) { int result = 0,n = 1; for (int i = s.length() - 1;i >= 0;i--){ char c = s.charAt(i); result += (c - 64) * n; n *=26; } return result;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>hash</tag>
</tags>
</entry>
<entry>
<title><![CDATA[奇偶链表]]></title>
<url>%2F2019%2F04%2F15%2F%E5%A5%87%E5%81%B6%E9%93%BE%E8%A1%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:328 给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。 示例 1: 12输入: 1->2->3->4->5->NULL输出: 1->3->5->2->4->NULL 示例 2: 12输入: 2->1->3->5->6->4->7->NULL 输出: 2->3->6->7->1->5->4->NULL 说明: 应当保持奇数节点和偶数节点的相对顺序。 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。 我的解答 解题思路:遍历链表head,分两段添加,奇数位添加到odd链表,偶数位添加到even链表,最后遍历完,把偶数链表拼接到奇数链表后面 12345678910111213141516171819202122232425262728293031public static ListNode oddEvenList(ListNode head) { ListNode odd = null,even = null; ListNode oddCur = null; ListNode evenCur = null; int index = 1; while (head != null){ if (index%2 == 0){ if (even == null){ even = new ListNode(head.val); evenCur = even; }else { evenCur.next = new ListNode(head.val); evenCur = evenCur.next; } }else { if (odd == null){ odd = new ListNode(head.val); oddCur = odd; }else { oddCur.next = new ListNode(head.val); oddCur = oddCur.next; } } head = head.next; index++; if (head == null){ oddCur.next = even; } } return odd;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[相交链表]]></title>
<url>%2F2019%2F04%2F12%2F%E7%9B%B8%E4%BA%A4%E9%93%BE%E8%A1%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:160 编写一个程序,找到两个单链表相交的起始节点。 如下面的两个链表: 在节点 c1 开始相交。 示例 1: 123输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3输出:Reference of the node with value = 8输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 示例 2: 123输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1输出:Reference of the node with value = 2输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。 示例 3: 1234输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2输出:null输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。解释:这两个链表不相交,因此返回 null。 注意: 如果两个链表没有交点,返回 null. 在返回结果后,两个链表仍须保持原有的结构。 可假定整个链表结构中没有循环。 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。 我的解答 解题思路:先遍历headA和headB,得到两个链表的长度。根据长度可知两个链表的谁长谁短,然后根据两个长度的差值len,先让长的链表走len节点,最后遍历比较两个链表,知道两个链表相交 1234567891011121314151617181920212223242526272829public ListNode getIntersectionNode(ListNode headA, ListNode headB) { int lenA = 0,lenB = 0; ListNode longNode = headA,shortNode = headB; while (longNode != null){ lenA++; longNode = longNode.next; } while (shortNode != null){ lenB++; shortNode = shortNode.next; } if (lenA < lenB){ longNode = headB; shortNode = headA; }else { longNode = headA; shortNode = headB; } int len = Math.abs(lenA - lenB); for (int i = 0;i < len;i++){ longNode = longNode.next; } while (longNode != shortNode){ longNode = longNode.next; shortNode = shortNode.next; } return longNode;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[排序链表]]></title>
<url>%2F2019%2F04%2F11%2F%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:148 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。 示例 1: 12输入: 4->2->1->3输出: 1->2->3->4 示例 2: 12输入: -1->5->3->4->0输出: -1->0->3->4->5 我的解答 解法一 解题思路:通过遍历head,把值存储到List中,对List的进行排序,再对list遍历,用新的ListNode来存值 123456789101112131415161718192021222324public static ListNode sortList(ListNode head) { List<Integer> list = new ArrayList<>(); while (head != null){ list.add(head.val); head = head.next; } Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } }); ListNode temp = null; for (int val:list){ if (head == null){ head = new ListNode(val); temp = head; }else { temp.next = new ListNode(val); temp = temp.next; } } return head;} 解法二 解题思路:通过归并排序思想,用一个fast指针,一个slow指针,fast走两步,slow走一步,分层两段;再对各自两段的链表,通过递归,进行排序;最后将两段排序好,再进行合并 123456789101112131415161718192021222324252627282930313233343536373839public static ListNode sortList(ListNode head) { return mergeSort(head);}private static ListNode mergeSort(ListNode head){ if(head == null || head.next == null) return head; ListNode fast = head; ListNode slow = head; ListNode tmp = null; while(fast != null && fast.next != null){ fast = fast.next.next; tmp = slow; slow = slow.next; } tmp.next = null; ListNode l = mergeSort(head); ListNode r = mergeSort(slow); return merge(l,r);}private static ListNode merge(ListNode l,ListNode r){ ListNode head = new ListNode(0); ListNode cur = head; while(l != null && r != null){ if(l.val < r.val){ cur.next = l; cur = cur.next; l = l.next; } else{ cur.next = r; cur = cur.next; r = r.next; } } if(l != null) cur.next = l; if(r != null) cur.next = r; return head.next;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[复制带随机指针的链表]]></title>
<url>%2F2019%2F04%2F10%2F%E5%A4%8D%E5%88%B6%E5%B8%A6%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:138 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。要求返回这个链表的深拷贝。 示例: 123456输入:{"$id":"1","next":{"$id":"2","next":null,"random":{"$ref":"2"},"val":2},"random":{"$ref":"2"},"val":1}解释:节点 1 的值是 1,它的下一个指针和随机指针都指向节点 2 。节点 2 的值是 2,它的下一个指针指向 null,随机指针指向它自己。 提示: 你必须返回给定头的拷贝作为对克隆列表的引用。 我的解答 解题思路:用HashMap来对原链表的拷贝,key是原链表,value是新链表,进行二次遍历;第一遍根据val进行赋值;第二遍为新链表添加next节点和random指针 123456789101112131415161718192021222324252627282930313233/*// Definition for a Node.class Node { public int val; public Node next; public Node random; public Node() {} public Node(int _val,Node _next,Node _random) { val = _val; next = _next; random = _random; }};*/public static Node copyRandomList(Node head) { Map<Node,Node> map = new HashMap<>(); Node node = head; while (node != null){ Node newNode = new Node(node.val,null,null); map.put(node,newNode); node = node.next; } node = head; while (node != null){ map.get(node).next = map.get(node.next); map.get(node).random = map.get(node.random); node = node.next; } return map.get(head);}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[逆波兰表达式求值]]></title>
<url>%2F2019%2F04%2F09%2F%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC%2F</url>
<content type="text"><![CDATA[LeetCode 题号:150 根据逆波兰表示法,求表达式的值。有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。 说明: 整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。 示例 1: 123输入: ["2", "1", "+", "3", "*"]输出: 9解释: ((2 + 1) * 3) = 9 示例 2: 123输入: ["4", "13", "5", "/", "+"]输出: 6解释: (4 + (13 / 5)) = 6 示例 3: 12345678910输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]输出: 22解释: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5= ((10 * (6 / (12 * -11))) + 17) + 5= ((10 * (6 / -132)) + 17) + 5= ((10 * 0) + 17) + 5= (0 + 17) + 5= 17 + 5= 22 我的解答 解法一 解题思路:用栈来存取tokens数组中的数字,根据逆波兰表达式规则,tokens数组的前两位是数字,这样用m,n来表示这两位,根据表达式规则,遇到符合就计算,将结果存入栈中,遍历完tokens,栈顶就是表达式的结果 123456789101112131415161718192021222324252627282930313233public static int evalRPN(String[] tokens) { Stack<Integer> stack = new Stack<>(); int n=0,m=0; for (int i=0;i<tokens.length;i++){ String token = tokens[i]; switch (token){ case "+": n=stack.pop(); m=stack.pop(); stack.push(m+n); break; case "-": n=stack.pop(); m=stack.pop(); stack.push(m-n); break; case "*": n=stack.pop(); m=stack.pop(); stack.push(m*n); break; case "/": n=stack.pop(); m=stack.pop(); stack.push(m/n); break; default: stack.push(Integer.parseInt(token)); break; } } return stack.pop();} 解法二 解题思路:跟解法一思路差不多,从数组尾部开始递归,直到遇到tokens的第一个符号,开始计算,直到递归完成,返回最终结果 12345678910111213141516171819private static int N =-1;public static int evalRPN(String[] tokens) { if(N==-1) N=tokens.length-1; String s = tokens[N--]; char c = s.charAt(0); if(s.length()==1&&"+-*/".indexOf(c)!=-1){ int a = evalRPN(tokens); int b = evalRPN(tokens); switch(c){ case '+':return a+b; case '-':return b-a; case '*':return a*b; case '/':return b/a; default:break; } } return Integer.parseInt(s);}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>堆栈</tag>
<tag>队列</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java多线程]]></title>
<url>%2F2019%2F04%2F08%2FJava%E5%A4%9A%E7%BA%BF%E7%A8%8B%2F</url>
<content type="text"><![CDATA[线程的生命周期 新建(New):新创建了一个线程对象。 可运行(Runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。 运行(Running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。 阻塞(Blocked):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种: (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。 (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 (三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。 终止(Terminated):线程run()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。 下图是线程的状态图 并发编程Java内存模式(JMM): Java所有变量都存储在主内存中 每个线程都有自己独立的工作内存,里面保存该线程的使用到的变量副本(该副本就是主内存中该变量的一份拷贝) 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接在主内存中读写 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。 线程A对共享变量的修改,要想被线程B及时看到,必须经过如下2个过程: 把工作内存A中更新过的共享变量刷新到主内存中 将主内存中最新的共享变量的值更新到工作内存B中 详细的内存模型介绍:请参考这篇文章java内存模型以及happens-before规则 线程安全的三个特性:原子性、有序性和可见性 可见性:一个线程对共享变量的修改,更够及时的被其他线程看到有序性:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都 是无序的原子性:即不可再分了,不能分为多步操作。比如赋值或者return。比如”a = 1;”和 “return a;”这样的操作 都具有原子性。类似”a += b”这样的操作不具有原子性,在某些JVM中”a += b”可能要经过这样三个步骤:① 取出a和b② 计算a+b③ 将计算结果写入内存 Synchronized:保证可见性和原子性 Synchronized能够实现原子性和可见性;在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。 Volatile:保证可见性,但不保证操作的原子性 Volatile实现内存可见性是通过store和load指令完成的;也就是对volatile变量执行写操作时,会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值。但volatile不保证volatile变量的原子性 Synchronized和Volatile的比较 Synchronized保证内存可见性和操作的原子性 Volatile只能保证内存可见性 Volatile不需要加锁,比Synchronized更轻量级,并不会阻塞线程(volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。)volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化(如编译器重排序的优化). volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。 volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,使用前,需要先从主存中读取,因此可以实现可见性。而对n=n+1,n++等操作时,volatile关键字将失效,不能起到像synchronized一样的线程同步(原子性)的效果 详细的线程相关知识介绍:这篇文章相关面试题:这篇文章]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>多线程</tag>
</tags>
</entry>
<entry>
<title><![CDATA[扁平化嵌套列表迭代器]]></title>
<url>%2F2019%2F04%2F08%2F%E6%89%81%E5%B9%B3%E5%8C%96%E5%B5%8C%E5%A5%97%E5%88%97%E8%A1%A8%E8%BF%AD%E4%BB%A3%E5%99%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:341 给定一个嵌套的整型列表。设计一个迭代器,使其能够遍历这个整型列表中的所有整数。列表中的项或者为一个整数,或者是另一个列表。 示例 1: 123输入: [[1,1],2,[1,1]]输出: [1,1,2,1,1]解释: 通过重复调用 next 直到 hasNext 返回false,next 返回的元素的顺序应该是: [1,1,2,1,1]。 示例 2: 123输入: [1,[4,[6]]]输出: [1,4,6]解释: 通过重复调用 next 直到 hasNext 返回false,next 返回的元素的顺序应该是: [1,4,6]。 我的解答 解题思路:用堆来遍历存储,遵循先进先出的原则,每次调用next(),调用poll()移除头部,知道hasNext()判断没有数据 12345678910111213141516171819202122232425262728293031323334353637public class NestedIterator implements Iterator<Integer> { private Queue<Integer> queue = new ArrayDeque<>(); public NestedIterator(List<NestedInteger> nestedList) { add(nestedList); } private void add(List<NestedInteger> nestedList){ for (NestedInteger nestedInteger:nestedList){ if (nestedInteger.isInteger()){ queue.offer(nestedInteger.getInteger()); }else { add(nestedInteger.getList()); } } } @Override public Integer next() { return queue.poll(); } @Override public boolean hasNext() { return !queue.isEmpty(); } public interface NestedInteger { public boolean isInteger(); public Integer getInteger(); public List<NestedInteger> getList(); }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>堆栈</tag>
<tag>队列</tag>
</tags>
</entry>
<entry>
<title><![CDATA[基本计算器 II]]></title>
<url>%2F2019%2F04%2F04%2F%E5%9F%BA%E6%9C%AC%E8%AE%A1%E7%AE%97%E5%99%A8%20II%2F</url>
<content type="text"><![CDATA[LeetCode 题号:227 实现一个基本的计算器来计算一个简单的字符串表达式的值。字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 。 整数除法仅保留整数部分。 示例 1: 12输入: "3+2*2"输出: 7 示例 2: 12输入: " 3/2 "输出: 1 示例 3: 12输入: " 3+5 / 2 "输出: 5 说明: 你可以假设所给定的表达式都是有效的。 请不要使用内置的库函数 eval。 我的解答: 解题思路:利用栈的方式解决运算优先级的问题 当为加减时放入栈中 当为乘除时讲栈顶元素与最近获得一个数进行运算 12345678910111213141516171819202122232425262728293031323334public static int calculate(String s) { Stack<Integer> stack = new Stack<>(); int num = 0; char op = '+'; for (int i=0;i<s.length();i++) { char c = s.charAt(i); if (c >= '0') { num = num*10+c-'0'; } if (c != ' ' && c < '0' || i == s.length()-1) { switch (op){ case '+': stack.push(num); break; case '-': stack.push(-num); break; case '*': stack.push(stack.pop() * num); break; case '/': stack.push(stack.pop() / num); break; } num = 0; op = c; } } int result = 0; while (!stack.empty()){ result += stack.pop(); } return result;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>堆栈</tag>
<tag>队列</tag>
</tags>
</entry>
<entry>
<title><![CDATA[滑动窗口最大值]]></title>
<url>%2F2019%2F04%2F03%2F%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC%2F</url>
<content type="text"><![CDATA[LeetCode 题号:239 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。返回滑动窗口最大值。 示例: 123456789101112输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3输出: [3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值--------------- -----[1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7 注意: 你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。 进阶: 你能在线性时间复杂度内解决此题吗? 我的解答 解法一 解题思路:新建result的数组,可以得知数组长度为num.length-k+1;遍历数组nums,维护一个大小为k的队列,每次读取一个新值时,把最左边的数移除,新值加入队列,这样栈顶的数都是最大值 1234567891011121314151617181920212223public static int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; if (n==0 || k==0) return new int[]{}; int[] result = new int[n-k+1]; PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); int index = 0; for(int i=0;i<n;i++){ queue.offer(nums[i]); if (queue.size() == k){ result[index] = queue.peek(); queue.remove(nums[index]); if (index == n - k) break; index++; } } return result;} 解法二 解题思路:使用双向队列,当遇到新值时,将新值和双向队列的末尾比较,如果末尾比新值小,则把末尾扔掉,直到该队列的末尾比新值大或者队列为空。这样,可以保证队列里的元素是从头到尾降序的,由于队列里只有窗口内的数,所以其实就是窗口内第一大,第二大,第三大…的数。保持队列内只有窗口的数,就是与解法一相同,来新值时,把最左边的数的下标移除,然后加入新值下标。然而由于在加新值的时候,已经把很多没用的数给扔了,这样队列头部的数并不一定是窗口最左边的数。这里的技巧是,队列中存的是那个数在原数组中的下标,这样我们既可以知道这个数的值,也可以知道该数是不是窗口最左边的数。 12345678910111213141516171819public static int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; if (n==0 || k==0) return new int[]{}; ArrayDeque<Integer> deque = new ArrayDeque<>(); int[] result = new int[n-k+1]; for(int i=0;i<n;i++){ if (!deque.isEmpty() && deque.peekFirst() == i - k){ deque.poll(); } while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]){ deque.removeLast(); } deque.offerLast(i); if (i+1 >= k){ result[i+1 - k] = nums[deque.peek()]; } } return result;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>困难</tag>
<tag>堆栈</tag>
<tag>队列</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据流的中位数]]></title>
<url>%2F2019%2F04%2F02%2F%E6%95%B0%E6%8D%AE%E6%B5%81%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:295 中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。例如,[2,3,4] 的中位数是 3[2,3] 的中位数是 (2 + 3) / 2 = 2.5设计一个支持以下两种操作的数据结构: void addNum(int num) - 从数据流中添加一个整数到数据结构中。 double findMedian() - 返回目前所有元素的中位数。 示例: 12345addNum(1)addNum(2)findMedian() -> 1.5addNum(3) findMedian() -> 2 进阶: 如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法? 如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法? 我的解答 解法一 解题思路:将int用List存储,然后转换新数组,按从小到大排序,根据List的大小是否为奇偶数来取中位数 123456789101112131415161718192021222324252627282930public class MedianFinder { private List<Integer> list; /** initialize your data structure here. */ public MedianFinder() { list = new ArrayList<>(); } public void addNum(int num) { list.add(num); } public double findMedian() { double median = 0; int index = 0; int n = list.size(); Integer[] arr = new Integer[n]; arr = list.toArray(arr); Arrays.sort(arr); if (n%2 == 0){ index = (n / 2) - 1; median = (arr[index] + arr[index+1])/2.0; }else { index = n / 2; median = arr[index]; } return median; }} 解法二 解题思路:用大小堆来存数据,小堆来存后半段大的数据(从小到大),大堆来前半段数据(从大到小,如果总数是奇数,取小堆栈顶的值为中位数;如果是偶数,取大小堆栈顶数据的平均值为中位数 123456789101112131415161718192021222324252627282930313233343536373839404142public class MedianFinder { private int count = 0; private PriorityQueue<Integer> min; private PriorityQueue<Integer> max; /** * initialize your data structure here. */ @RequiresApi(api = Build.VERSION_CODES.N) public MedianFinder() { min = new PriorityQueue<>(); max = new PriorityQueue<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); } public void addNum(int num) { if (count % 2 == 0) { max.offer(num); int temp = max.poll(); min.offer(temp); } else { min.offer(num); int temp = min.poll(); max.offer(temp); } count++; } public double findMedian() { if (count % 2 == 0) { return (max.peek() + min.peek()) / 2.0; } else { return min.peek(); } }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>困难</tag>
<tag>堆栈</tag>
<tag>队列</tag>
</tags>
</entry>
<entry>
<title><![CDATA[有序矩阵中第K小的元素]]></title>
<url>%2F2019%2F04%2F01%2F%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:347 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 示例 1: 12输入: nums = [1,1,1,2,2,3], k = 2输出: [1,2] 示例 2: 12输入: nums = [1], k = 1输出: [1] 说明: 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。 我的解答 解题思路:把数组的数据放大Map存起来,key是具体数字,value是在数组出现的次数,然后按从大到小排列,最后取前k个数字 12345678910111213141516171819202122232425262728293031323334public static List<Integer> topKFrequent(int[] nums, int k) { List<Integer> list = new ArrayList<>(); PriorityQueue<Map.Entry<Integer,Integer>> pq = new PriorityQueue<> (new Comparator<Map.Entry<Integer, Integer>>() { @Override public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) { return o2.getValue()-o1.getValue(); } }); Map<Integer,Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { if (map.containsKey(nums[i])) map.put(nums[i], map.get(nums[i]) + 1); else map.put(nums[i], 1); } Set<Map.Entry<Integer,Integer>> set = map.entrySet(); for (Map.Entry<Integer,Integer> entry : set) { pq.add(entry); } for (int i = 0; i < k; i++) { list.add(pq.poll().getKey()); } return list;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>堆栈</tag>
<tag>队列</tag>
</tags>
</entry>
<entry>
<title><![CDATA[有序矩阵中第K小的元素]]></title>
<url>%2F2019%2F03%2F30%2F%E6%9C%89%E5%BA%8F%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%AC%ACK%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:378 给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。请注意,它是排序后的第k小元素,而不是第k个元素。 示例: 12345678matrix = [ [ 1, 5, 9], [10, 11, 13], [12, 13, 15]],k = 8,返回 13。 说明: 你可以假设 k 的值永远是有效的, 1 ≤ k ≤ n2 。 我的解答 解法一(遍历) 解题思路:用新数组存储矩阵所有元素,最后排序,取出k-1个元素就是想要的结果 123456789101112131415public static int kthSmallest(int[][] matrix, int k) { int row = matrix.length; int col = matrix[0].length; int n = row * col; int[] arr = new int[n]; int index = 0; for (int i=0;i<matrix.length;i++){ for (int j=0;j<matrix[i].length;j++){ arr[index] = matrix[i][j]; index++; } } Arrays.sort(arr); return arr[k];} 解法二(二分查找) 解题思路:由于是有序矩阵,那么左上角的数字一定是最小的,而右下角的数字一定是最大的,所以这个是搜索的范围,然后算出中间数字mid,由于矩阵中不同行之间的元素并不是严格有序的,所以要在每一行都查找一下mid,遍历查找第一个大于目标数的元素,如果目标数在比该行的尾元素大,则count返回该行元素的个数,如果目标数比该行首元素小,则换下一行, 遍历完所有的行可以找出中间数是第几小的数,然后与k比较,进行二分查找,left和right最终会相等,并且会变成数组中第k小的数字。 1234567891011121314151617181920212223public static int kthSmallest(int[][] matrix, int k) { int n = matrix.length; int left = matrix[0][0]; int right = matrix[n-1][n-1]; while (left < right){ int mid = left + (right-left)/2; int count = 0; for (int i=n-1,j=0;i>=0 && j<n;){ if (matrix[i][j] > mid){ i--; }else { j++; count += i+1; } } if (count < k){ left = mid + 1; }else { right = mid; } } return left;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数组</tag>
<tag>中等</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数组中的第K个最大元素]]></title>
<url>%2F2019%2F03%2F28%2F%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E7%AC%ACK%E4%B8%AA%E6%9C%80%E5%A4%A7%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:215 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 示例 1: 12输入: [3,2,1,5,6,4] 和 k = 2输出: 5 示例 2: 12输入: [3,2,3,1,2,4,5,5,6] 和 k = 4输出: 4 说明: 你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。 我的解答: 解法一 解题思路:用PriorityQueue(添加数据,会进行从小大排序)来存储数组数据,超过k的大小,就移除;遍历完nums,取出的第一个数就对应的最大元素 12345678910public static int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> queue = new PriorityQueue<>(); for (int num:nums){ queue.add(num); if (queue.size() > k){ queue.poll(); } } return queue.peek();} 解法二 解题思路:先对数组nums进行从小大排序,取出数组倒数第k个下标的数就是对应的最大元素 1234public static int findKthLargest(int[] nums, int k) { Arrays.sort(nums); return nums[nums.length - k];}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>堆栈</tag>
<tag>队列</tag>
</tags>
</entry>
<entry>
<title><![CDATA[View、WindowsheViewRootImpl的关系]]></title>
<url>%2F2019%2F03%2F27%2FView%E3%80%81Windows%E5%92%8CViewRootImpl%E7%9A%84%E5%85%B3%E7%B3%BB%2F</url>
<content type="text"><![CDATA[梳理在Activity的启动过程中,View、Windows和ViewRootImpl的关系。 下图是View、WindowsheViewRootImpl的关系流程图: 在Activity的启动过程中,在ActivityThread中调用performLaunchActivity方法会调用Activity的attach()方法,这时会创建PhoneWindow; Window创建后,会调用setWindowManager方法,让Window与WIndowManager进行绑定; 之后在handleResumeActivity方法中DecorView与WindowManager进行了绑定,即WM调用了addView方法。addView的最终执行是由WindowManagerGlobal来执行。 在执行addView时,创建了ViewRootImpl(其实对View的最终操作,如添加,删除等都是它来完成的),之后调用setView方法 接下来就是对View的绘制过程,然后ViewRootImpl会通过WindowSession跟WMS(WindowManagerService)进行通信,完成对Window的添加 完成Window的添加后,最后让DecorView显示出来。]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>View</tag>
<tag>Window</tag>
</tags>
</entry>
<entry>
<title><![CDATA[除自身以外数组的乘积]]></title>
<url>%2F2019%2F03%2F27%2F%E9%99%A4%E8%87%AA%E8%BA%AB%E4%BB%A5%E5%A4%96%E6%95%B0%E7%BB%84%E7%9A%84%E4%B9%98%E7%A7%AF%2F</url>
<content type="text"><![CDATA[LeetCode 题号:238 给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 示例: 12输入: [1,2,3,4]输出: [24,12,8,6] 说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。 进阶: 你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。) 我的解答 解法一 解题思路:每一个数都是除了自身之外,左边数的乘积 乘以 右边数的乘积 12345678910111213141516public static int[] productExceptSelf(int[] nums) { int n = nums.length; int[] newNums = new int[n]; int[] left = new int[n]; int[] right = new int[n]; left[0] = 1; right[n-1] = 1; for (int i=1;i<n;i++){ left[i] = nums[i-1] * left[i-1]; right[n-1-i] =nums[n-i] * right[n-i]; } for (int i=0;i<nums.length;i++){ newNums[i] = left[i] * right[i]; } return newNums;} 解法二 解题思路:对解法一的优化版本 123456789101112131415public static int[] productExceptSelf(int[] nums) { int n = nums.length; int[] newNums = new int[n]; int left = 1; int right = 1; for (int i=0;i<n;i++){ newNums[i] = left; left *= nums[i]; } for (int i=n-1;i>=0;i--){ newNums[i] *= right; right *= nums[i]; } return newNums;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数组</tag>
<tag>中等</tag>
</tags>
</entry>
<entry>
<title><![CDATA[递增的三元子序列]]></title>
<url>%2F2019%2F03%2F26%2F%E9%80%92%E5%A2%9E%E7%9A%84%E4%B8%89%E5%85%83%E5%AD%90%E5%BA%8F%E5%88%97%2F</url>
<content type="text"><![CDATA[LeetCode 题号:334 给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列。 数学表达式如下: 如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ≤ n-1,使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否则返回 false 。 说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1) 。 示例 1: 12输入: [1,2,3,4,5]输出: true 示例 2: 12输入: [5,4,3,2,1]输出: false 我的解答 解题思路:确定数组中是否存在三个递增的数,可以先后确定第一个,第二个,是否存在第三个,如果存在,证明是符合需求,反之亦然 12345678910111213141516171819public static boolean increasingTriplet(int[] nums) { if (nums.length < 3) return false; int first = Integer.MAX_VALUE,second = Integer.MAX_VALUE; for (int num:nums){ if (first > num){ first = num; continue; } if (first < num && num < second){ second = num; continue; } if (num > second){ return true; } } return false;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数组</tag>
<tag>中等</tag>
</tags>
</entry>
<entry>
<title><![CDATA[乘积最大子序列]]></title>
<url>%2F2019%2F03%2F25%2F%E4%B9%98%E7%A7%AF%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%88%97%2F</url>
<content type="text"><![CDATA[LeetCode 题号:152 给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。 示例 1: 123输入: [2,3,-2,4]输出: 6解释: 子数组 [2,3] 有最大乘积 6。 示例 2: 123输入: [-2,0,-1]输出: 0解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 我的解答: 解答一(暴力法) 12345678910111213public int maxProduct(int[] nums) { int result = nums[0]; for (int i=0;i<nums.length;i++){ int product = 1; for (int j=i;i<nums.length;j++){ product *= nums[i]; if (product > result){ result = product; } } } return result;} 解答二(动态规划) 解题思路:考虑到乘积子序列中有正也有负或者0,虽然只要求一个最大积,但由于负数的存在,不但纪录最大乘积,也要记录最小乘积。利用动态规划来求解,用max[i]来表示以nums[i]结尾的最大连续子串的乘积,用min[i]表示以nums[i]结尾的最小连续子串的乘积 123456789101112public int maxProduct(int[] nums) { int max = nums[0]; int min = nums[0]; int result = nums[0]; for (int i=1;i<nums.length;i++){ int tem = max; max = Math.max(Math.max(max*nums[i],min*nums[i]),nums[i]); min = Math.min(Math.min(tem*nums[i],min*nums[i]),nums[i]); result = Math.max(max,result); } return result;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数组</tag>
<tag>中等</tag>
</tags>
</entry>
<entry>
<title><![CDATA[单词搜索 II]]></title>
<url>%2F2019%2F03%2F24%2F%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2%20II%2F</url>
<content type="text"><![CDATA[LeetCode 题号:212 给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。 示例: 12345678910输入: words = ["oath","pea","eat","rain"] and board =[ ['o','a','a','n'], ['e','t','a','e'], ['i','h','k','r'], ['i','f','l','v']]输出: ["eat","oath"] 说明: 你可以假设所有输入都由小写字母 a-z 组成。 提示: 你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯? 如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看: 实现Trie(前缀树)/)。 我的解答 解法一 解题思路:承接 ‘单词搜索’ 的基础上,遍历单词数组 12345678910111213141516171819202122232425262728293031323334353637383940414243public List<String> findWords(char[][] board, String[] words) { List<String> list = new ArrayList<>(); for (String word:words){ if (exist(board,word) && !list.contains(word)){ list.add(word); } } return list;}private boolean exist(char[][] board, String word) { if (word.length() == 0 || board.length == 0){ return false; } boolean[][] flag = new boolean[board.length][board[0].length]; for (int i=0;i<board.length;i++){ for (int j=0;j<board[i].length;j++){ if (board[i][j] == word.charAt(0)){ if (find(board,word,0,i,j,flag)){ return true; } } } } return false;}private boolean find(char[][] board, String word,int index,int i,int j,boolean[][] flag){ if (index == word.length()){ return true; } boolean result = false; if (i>=0 && i<board.length && j>=0 && j<board[i].length && !flag[i][j] && word.charAt(index) == board[i][j]){ flag[i][j] = true; index += 1; result = find(board,word,index,i+1,j,flag) || find(board,word,index,i-1,j,flag) || find(board,word,index,i,j+1,flag) || find(board,word,index,i,j-1,flag); if (result == false){ index -= 1; flag[i][j] = false; } } return result;} 解法二(LeetCode最优解) 解题思路:单词列表利用前缀树来存储,然后深度遍历 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293class Solution { public class Node { Node[] children = new Node[26]; char c; String result; public Node(char c) { this.c = c; } public void insert(String word, int start) { if (start >= word.length()) return; char v = word.charAt(start); if (start == word.length() - 1) { //last one Node child = children[v - 97]; if (child == null) { children[v - 97] = child = new Node(v); } child.result = word; } else { Node child = children[v - 97]; if (child == null) { children[v - 97] = child = new Node(v); } child.insert(word, start + 1); } } public Node next(char c) { if (97 <= c && c < 97 + 26) { return children[c - 97]; } else { return null; } } } char[][] board; int h = 0; int w = 0; boolean[][] used; Set<String> r; public List<String> findWords(char[][] board, String[] words) { if (board.length == 0 || board[0].length == 0) { return new ArrayList<>(); } this.board = board; h = board.length; w = board[0].length; used = new boolean[h][w]; r = new HashSet<>(); Node root = new Node(' '); for (String word : words) { root.insert(word, 0); } for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { used[y][x] = true; Node next = root.next(board[y][x]); if (next != null) { find(x, y, next); } used[y][x] = false; } } List<String> list = new ArrayList<>(r); return list; } private void find(int x, int y, Node curr) { if (curr == null) return; if (curr.result != null) { r.add(curr.result); } if (x > 0) go(x - 1, y, curr); if (x < w - 1) go(x + 1, y, curr); if (y > 0) go(x, y - 1, curr); if (y < h - 1) go(x, y + 1, curr); } private void go(int x, int y, Node curr) { if (used[y][x]) return; Node next = curr.next(board[y][x]); if (next != null) { used[y][x] = true; find(x, y, next); used[y][x] = false; } }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>困难</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[单词搜索]]></title>
<url>%2F2019%2F03%2F22%2F%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2%2F</url>
<content type="text"><![CDATA[LeetCode 题号:79 给定一个二维网格和一个单词,找出该单词是否存在于网格中。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 示例: 12345678910board =[ ['A','B','C','E'], ['S','F','C','S'], ['A','D','E','E']]给定 word = "ABCCED", 返回 true.给定 word = "SEE", 返回 true.给定 word = "ABCB", 返回 false. 我的解答 解题思路:首页找到二维数组与单词第一个字母匹配的下标,找到之后,通过回溯算法,记录上下左右位置的字符是否匹配,层层递进,知道全部匹配单词 12345678910111213141516171819202122232425262728293031323334353637383940public boolean exist(char[][] board, String word) { if (word.length() == 0 || board.length == 0){ return false; } boolean[][] flag = new boolean[board.length][board[0].length]; for (int i=0;i<board.length;i++){ for (int j=0;j<board[i].length;j++){ if (board[i][j] == word.charAt(0)){ if (find(board,word,0,i,j,flag)){ return true; } } } } return false;}private boolean find(char[][] board, String word,int index,int i,int j,boolean[][] flag){ if (index == word.length()){ return true; } boolean result = false; if (i>=0 && i<board.length && j>=0 && j<board[i].length && !flag[i][j] && word.charAt(index) == board[i][j]){ flag[i][j] = true; index += 1; result = find(board,word,index,i+1,j,flag) || find(board,word,index,i-1,j,flag) || find(board,word,index,i,j+1,flag) || find(board,word,index,i,j-1,flag); if (result == false){ index -= 1; flag[i][j] = false; } } return result;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[实现 Trie (前缀树)]]></title>
<url>%2F2019%2F03%2F21%2F%E5%AE%9E%E7%8E%B0%20Trie%20(%E5%89%8D%E7%BC%80%E6%A0%91)%2F</url>
<content type="text"><![CDATA[LeetCode 题号:208 实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。 示例: 12345678Trie trie = new Trie();trie.insert("apple");trie.search("apple"); // 返回 truetrie.search("app"); // 返回 falsetrie.startsWith("app"); // 返回 truetrie.insert("app"); trie.search("app"); // 返回 true 说明: 你可以假设所有的输入都是由小写字母 a-z 构成的。 保证所有输入均为非空字符串。 我的解答 解题思路:通过不断的插入新的字符串来丰富26叉数(依据6个英文字母) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556public class Trie { private TreeNode node; /** Initialize your data structure here. */ public Trie() { node = new TreeNode(); } /** Inserts a word into the trie. */ public void insert(String word) { TreeNode node = this.node; for (char c:word.toCharArray()){ if (node.child[c - 'a'] == null){ node.child[c - 'a'] = new TreeNode(); } node = node.child[c - 'a']; } node.value = word; } /** Returns if the word is in the trie. */ public boolean search(String word) { TreeNode node = this.node; for (char c:word.toCharArray()){ if (node.child[c - 'a'] == null){ return false; } node = node.child[c - 'a']; } return node.value.equals(word); } /** Returns if there is any word in the trie that starts with the given prefix. */ public boolean startsWith(String prefix) { TreeNode node = this.node; for (char c:prefix.toCharArray()){ if (node.child[c - 'a'] == null){ return false; } node = node.child[c - 'a']; } return true; } class TreeNode{ String value; TreeNode[] child; public TreeNode(){ value = ""; child = new TreeNode[26]; } }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Binder机制]]></title>
<url>%2F2019%2F03%2F20%2FBinder%E6%9C%BA%E5%88%B6%2F</url>
<content type="text"><![CDATA[Binder是Android系统进程间通信(IPC)方式之一。传统的IPC如管道、Scoket等,都是属于内核一部分,通过内核来进行跨进程通信。而Binder不属于内核,Android利用Linux的动态内核加载技术,将Binder驱动挂载为动态内核,然后通过Binder驱动以mmap的方式将内核空间与接收方的用户空间进行内存映射。 内存映射(mmap):将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。 Binder通信原理 首先 Binder 驱动在内核空间创建一个数据接收缓存区; 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系; 数据发送进程调用系统copy_from_user()方法,将数据copy到内核缓存区,由于内核缓存区和数据接收进程的用户空间存在内存映射,相当于将数据发送到接收进程的用户空间 下图是Binder通信原理: Binder通信模型Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),驱动是路由器。 关于通信模型的详细介绍可以参考这篇文章 Binder通信过程 Android在启动时,SystemServer通过Binder驱动将自己注册为ServiceManager; Server通过Binder驱动向ServiceManager注册Binder(Server中的Binder实体),表明可以对外可以提供服务; Client通过Binder驱动查询,可以获得这个Binder实体的引用; Client获得Server的引用后,就可以Binder驱动发送数据和获取数据 下图是Binder的通信过程:]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>Binder</tag>
</tags>
</entry>
<entry>
<title><![CDATA[单词拆分 II]]></title>
<url>%2F2019%2F03%2F20%2F%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86%20II%2F</url>
<content type="text"><![CDATA[LeetCode 题号:140 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。 说明: 分隔时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1: 12345678输入:s = "catsanddog"wordDict = ["cat", "cats", "and", "sand", "dog"]输出:[ "cats and dog", "cat sand dog"] 示例 2: 12345678910输入:s = "pineapplepenapple"wordDict = ["apple", "pen", "applepen", "pine", "pineapple"]输出:[ "pine apple pen apple", "pineapple pen apple", "pine applepen apple"]解释: 注意你可以重复使用字典中的单词。 示例 3: 12345输入:s = "catsandog"wordDict = ["cats", "dog", "sand", "and", "cat"]输出:[] 我的解答 解题思路:动态规划先判断字符串s是否符合单词拆分,然后在通过深度遍历加回溯组合字符串 12345678910111213141516171819202122232425262728293031323334353637383940414243444546public static List<String> wordBreak(String s, List<String> wordDict) { if (!dp(s,wordDict)){ return new ArrayList<>(); } List<String> list = new ArrayList<>(); dfs(s,list,wordDict,new StringBuilder(),0); return list;}private static boolean dp(String s,List<String> wordDict) { int n = s.length(); int max_length=0; for(String temp:wordDict){ max_length = temp.length()>max_length? temp.length():max_length; } boolean[] memo = new boolean[n + 1]; memo[0] = true; for (int i = 1; i <= n; i++) { for (int j = i-1; j >=0 && max_length>=i-j; j--) { String str = s.substring(j, i); if (memo[j] && wordDict.contains(str)) { memo[i] = true; break; } } } return memo[n];}private static void dfs(String s,List<String> list,List<String> wordDict,StringBuilder sb,int index){ if (index == s.length()){ list.add(sb.toString().trim()); return; } for (int i=index;i<s.length();i++){ String str = s.substring(index,i+1); if (wordDict.contains(str)){ int len = sb.length(); sb.append(str).append(" "); dfs(s,list,wordDict,sb,i+1); sb.setLength(len); } }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>困难</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[单词拆分]]></title>
<url>%2F2019%2F03%2F19%2F%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86%2F</url>
<content type="text"><![CDATA[LeetCode 题号:139 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明: 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1: 123输入: s = "leetcode", wordDict = ["leet", "code"]输出: true解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。 示例 2: 1234输入: s = "applepenapple", wordDict = ["apple", "pen"]输出: true解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。 注意你可以重复使用字典中的单词。 示例 3: 12输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]输出: false 我的解答: 解题思路:动态规划,通过记录拆分后单词的下标,直到最后第二个单词的下标,到字符串最后位置,组成的单词,是否包含在字典里来判断 1234567891011121314public static boolean wordBreak(String s, List<String> wordDict) { int len = s.length(); boolean[] dp = new boolean[len + 1]; dp[0] = true; for (int i = 1; i <= len; i++) for (int j = 0; j < i; j++) { String tmp = s.substring(j, i); if (dp[j] && wordDict.contains(tmp)) { dp[i] = true; break; } } return dp[len];} 对上面进一步优化(LeetCode的最优解): 解题思路:通过字典里最长的字符串长度,来减少第二层循环的次数 1234567891011121314151617181920public static boolean wordBreak(String s, List<String> wordDict) { int n = s.length(); int max_length=0; for(String temp:wordDict){ max_length = temp.length()>max_length? temp.length():max_length; } boolean[] memo = new boolean[n + 1]; memo[0] = true; for (int i = 1; i <= n; i++) { for (int j = i-1; j >=0 && max_length>=i-j; j--) { if (memo[j] && wordDict.contains(s.substring(j, i))) { memo[i] = true; break; } } } return memo[n];}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[分割回文串]]></title>
<url>%2F2019%2F03%2F18%2F%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2%2F</url>
<content type="text"><![CDATA[LeetCode 题号:131 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。返回 s 所有可能的分割方案。 示例: 123456输入: "aab"输出:[ ["aa","b"], ["a","a","b"]] 我的解答 解题思路:递归回溯思路,在字符串s的第index位置开始寻找下一个回文字符串,list中存放的是之前的所有回文串。递归函数的出口是index指向了第s.length()个字符 123456789101112131415161718192021222324252627public static List<List<String>> partition(String s) { List<List<String>> lists = new ArrayList<>(); partition(lists,s,0,new ArrayList<String>()); return lists;}private static void partition(List<List<String>> lists,String s,int index,List<String> list) { if (index == s.length()){ lists.add(new ArrayList<String>(list)); } for (int i=index;i<s.length();i++){ String str = s.substring(index,i+1); if (isPalindrome(str)){ list.add(str); partition(lists,s,i+1,list); list.remove(list.size()-1); } }}private static boolean isPalindrome(String s) { for (int i = 0;i < s.length()/2;i ++){ if (s.charAt(i) != s.charAt(s.length() - 1 - i)){ return false; } } return true;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Activity的启动流程]]></title>
<url>%2F2019%2F03%2F15%2FActivity%E7%9A%84%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%2F</url>
<content type="text"><![CDATA[Activity的启动过程可以分为两种,一种是APP的第一次启动(从Launcher点击图标启动APP),另一种是由一个Activity跳转到另一个Activity。 下图是Activity的启动步骤: 在桌面点击应用图标,Launcher所在的进程向AMS(处在SystemServer进程)请求startActivity AMS会判断启动Activity的所在进程是否存在(即应用所在的进程),如果不存在,会向Zygote请求创建应用进程; 创建应用进程后,调用ActivityThread的main()方法,并且通过ApplicationThread与AMS建立Binder通信; AMS会通过ApplicationThread向ActivityThread发送消息,ActivityThread收到消息,会进行处理,调用handleLaunchActivity方法; 接着会调用performLaunchActivity方法,这时,会创建Activity所需的Context和Window,并为activity设置WindowManager对象,同时也调用Activity的onCreate()方法; 然后会调用performResumeActivity方法,这时会调用Activity的onResume()方法,将DecorView与WindowManager进行绑定 到此,Activity基本启动已完成,等UI绘制完,就可以看到完整的界面了]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>Activity</tag>
</tags>
</entry>
<entry>
<title><![CDATA[鸡蛋掉落]]></title>
<url>%2F2019%2F03%2F15%2F%E9%B8%A1%E8%9B%8B%E6%8E%89%E8%90%BD%2F</url>
<content type="text"><![CDATA[LeetCode 题号:887 你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。你的目标是确切地知道 F 的值是多少。无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少? 示例 1: 1234567输入:K = 1, N = 2输出:2解释:鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。如果它没碎,那么我们肯定知道 F = 2 。因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。 示例 2: 12输入:K = 2, N = 6输出:3 示例 3: 12输入:K = 3, N = 14输出:4 关于本题详细解题思路,请参考这篇文章 我的解答: 解题思路:计算k个鸡蛋在m步可测出几层 123456789101112public static int superEggDrop(int K, int N) { int[][] dp = new int[K + 1][N + 1]; for (int m = 1; m <= N; m++) { for (int k = 1; k <= K; k++) { dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; if (dp[k][m] >= N) { return m; } } } return N;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>动态规划</tag>
<tag>困难</tag>
</tags>
</entry>
<entry>
<title><![CDATA[搜索二维矩阵 II]]></title>
<url>%2F2019%2F03%2F14%2F%E6%90%9C%E7%B4%A2%E4%BA%8C%E7%BB%B4%E7%9F%A9%E9%98%B5%20II%2F</url>
<content type="text"><![CDATA[LeetCode 题号:240 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性: 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 示例: 12345678现有矩阵 matrix 如下:[ [1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30]] 给定 target = 5,返回 true。给定 target = 20,返回 false。 我的解答: 解法一: 解题思路:从数组的左下角开始遍历。如果当前的数字大于target,我们向上移,因为上面的小于下面的;如果当前的数字小于target,我们向右移,因为右边的大于左边的 12345678910111213141516171819public static boolean searchMatrix(int[][] matrix, int target) { if (matrix == null || matrix.length == 0) { return false; } int rows = matrix.length,cols = matrix[0].length; int row = rows - 1,col = 0; while (row >= 0 && col < cols){ if (matrix[row][col] == target){ return true; }else if (matrix[row][col] > target){ col = 0; row--; }else { col++; } } return false;} 解法二: 解题思路:把数组的数据放在map中存储,再利用map判断是否包含target 123456789public boolean searchMatrix(int[][] matrix, int target) { Map<Integer,Integer> map = new HashMap<>(); for (int i=0;i<matrix.length;i++){ for (int j=0;j<matrix[i].length;j++){ map.put(matrix[i][j],matrix[i][j]); } } return map.containsKey(target);}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数组</tag>
<tag>中等</tag>
</tags>
</entry>
<entry>
<title><![CDATA[求众数]]></title>
<url>%2F2019%2F03%2F13%2F%E6%B1%82%E4%BC%97%E6%95%B0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:169 给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的,并且给定的数组总是存在众数。 示例 1: 12输入: [3,2,3]输出: 3 示例 2: 12输入: [2,2,1,1,1,2,2]输出: 2 我的解答: 解法一: 解题思路:从第一个数开始index=1,遇到相同的就加1,遇到不同的就减1,减到0就重新换个数开始计数,总能找到最多的那个 123456789101112131415public static int majorityElement(int[] nums) { int num = nums[0]; int index = 1; for (int i = 1;i<nums.length;i++){ if (num == nums[i]){ index++; }else { index--; if (index == 0 && i < nums.length -1){ num = nums[i+1]; } } } return num;} 解法二: 解题思路:用一个map来存储,key是值,value是出现的次数,再对map进行遍历,比较那个值出现的次数最多 12345678910111213141516171819202122public static int majorityElement(int[] nums) { Map<Integer,Integer> map = new HashMap<>(); for (int n:nums){ if (!map.containsKey(n)){ map.put(n,1); }else { int value = map.get(n); value++; map.put(n,value); } } int num = nums[0]; int index = map.get(num); for (int key:map.keySet()){ int value = map.get(key); if (index < value){ index = value; num = key; } } return num;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>数组</tag>
</tags>
</entry>
<entry>
<title><![CDATA[缺失数字]]></title>
<url>%2F2019%2F03%2F12%2F%E7%BC%BA%E5%A4%B1%E6%95%B0%E5%AD%97%2F</url>
<content type="text"><![CDATA[LeetCode 题号:268 给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。 示例 1: 12输入: [3,0,1]输出: 2 示例 2: 12输入: [9,6,4,2,3,5,7,0,1]输出: 8 我的解答: 解题思路:利用差值计算,因为给出序列是一串连续的,所以不缺失的时的和是可以计算,将数组内数字和连续进行相减,最后得出就是缺失的数字 1234567public int missingNumber(int[] nums) { int res=nums.length; for(int i=0;i<nums.length;i++){ res +=(i-nums[i]); } return res;} 利用位运算 解题思路:运用异或运算(相同为0,不同为1),这样遍历从0开始,与数组内进行异或运算,相同就抵消,最后省下来的就是缺失的数字 1234567public int missingNumber(int[] nums) { int n = 0; for(int i =0 ; i < nums.length ; i++){ n = n ^ i ^ nums[i]; } return n ^ nums.length;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>数组</tag>
</tags>
</entry>
<entry>
<title><![CDATA[有效的括号]]></title>
<url>%2F2019%2F03%2F11%2F%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%2F</url>
<content type="text"><![CDATA[LeetCode 题号:20 给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串,判断字符串是否有效。有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 注意空字符串可被认为是有效字符串。 示例 1: 12输入: "()"输出: true 示例 2: 12输入: "()[]{}"输出: true 示例 3: 12输入: "(]"输出: false 示例 4: 12输入: "([)]"输出: false 示例 5: 12输入: "{[]}"输出: true 我的解答: 解题思路:用一个栈来存储括号,每当从栈顶取出的括号和当前循环的字符比较,如果是成对的,就移除,不是添加。循环结束检验栈是否是空来判断是否是有效的括号 12345678910111213141516171819202122232425262728293031323334public static boolean isValid(String s) { Stack<Character> stack = new Stack<>(); for (char c:s.toCharArray()){ if (stack.isEmpty()){ stack.push(c); }else { if (stack.peek() == '(') { if (c != ')'){ stack.push(c); }else { stack.pop(); } continue; } if (stack.peek() == '[') { if (c != ']'){ stack.push(c); }else { stack.pop(); } continue; } if (stack.peek() == '{') { if (c != '}'){ stack.push(c); }else { stack.pop(); } continue; } } } return stack.isEmpty();}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java 强引用、软引用、弱引用、虚引用]]></title>
<url>%2F2019%2F03%2F08%2FJava%20%E5%BC%BA%E5%BC%95%E7%94%A8%E3%80%81%E8%BD%AF%E5%BC%95%E7%94%A8%E3%80%81%E5%BC%B1%E5%BC%95%E7%94%A8%E3%80%81%E8%99%9A%E5%BC%95%E7%94%A8%2F</url>
<content type="text"><![CDATA[强引用只要程序通过 new 关键字创建了对象,那么垃圾回收期永远不会进行对对象的回收,除非是系统内存不够,即便如此,JVM也就只是抛出OutOfMemory()异常,当然如果对象的引用被释放后。这个对象将会被释放掉。那么如何清空这个对象的引用呢?对象的引用是存放在JVM的栈内存中,所以我们清空栈里面的引用自然就清除了这个对象所占用的内存空间。 软引用非必须引用,当内存资源不够即将溢出时,这个时候GC将会对对象的内存空间进行回收,从而避免内存溢出错误的发生。如何实现软引用: 12345678Object obj = new Object();SoftReference<Object> sf = new SoftReference<Object>(obj);obj = null ;// 这个时候sf是obj对象的一个软引用,通过get()方法可以获取到这个对象,当内存不足的时候可能返回 null ;sf.get(); 使用场景:当用户需要实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除部分缓存数据,从真正的来源查询相关数据。 弱引用弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,只要扫描到,无论内存是否充足(与软引用的区别),都会回收被弱引用关联的对象。如何实现弱引用: 123456789Object obj = new Object ();// 创建弱引用对象WeakReference<Object> wf = new WeakReference<Object>();obj = null ;// 有的时候回返回nullwf.get(); // 返回是否被垃圾回收器标记为即将回收的装状态wf.isEnQueued(); 弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnqueued方法返回对象是否被垃圾回收器标记。 虚引用一个只被虚引用持有的对象可能会在任何时候被GC回收。虚引用对对象的生存周期完全没有影响,也无法通过虚引用来获取对象实例,仅仅能在对象被回收时,得到一个系统通知(只能通过是否被加入到ReferenceQueue来判断是否被GC,这也是唯一判断对象是否被GC的途径)。如何实现虚引用: 1234567Object obj = new Object ();PhantomReference<Object> pf = new PhantomReference<Object>(obj);pf.get();pf.isEnQueued();//返回对象是否已经被清理的状态 注意事项当使用弱引用的时候,应该记住软引用被GC的方式。每当GC发现一个对象是弱可达时,即对该对象剩下的最后一个剩余引用是弱引用时,该对象会被放到相对应的ReferenceQueue,并且对finaliza线程可见。然后可以轮询ReferenceQueue,并且执行相应的清理活动。在对象被加入到ReferenceQueue,仍然可以为对象创建新的强引用,所以在它最终完成并回收之前,GC必须再次检查确实可以这样进行回收。 当使用软引用的时候,应该记住软引用没有虚引用被回收的那么频繁。如果没有显示的指明被回收的时间点,那么软引用被回收的时机取决去JVM本身的实现。通常JVM将软引用的回收作为内存耗尽之前的最后一次努力,即内存耗尽之前,JVM是不会回收软引用的。这意味着程序可能出现GC更频繁或者暂停时间更长的情况。 当使用虚引用时,必须手动进行内存的管理,以便标记符合垃圾回收条件的引用。为了确保申明的对象保持状态,虚引用指向的对象不会被检测到:虚引用的get方法总是返回null。不同于软引用和弱引用,当虚引用进入队列后,GC并不能自动的清除它。通过虚引用访问的对象一直保持可达的状态,除非引用被清除或者对象自己无法访问。也就是说我们必须手动调用clear()方法,否则可能会出现OOM的错误。 如果是采用弱引用、虚引用和软引用引起错误的解决办法: 弱引用:如果问题是由使用大量内存池触发的,则提升内存池的大小可能会解决这个问题。如示例部分,增加堆和年轻代的大小可以缓解问题。 虚引用:确保已经清除了引用。确实存在一些情况,清理线程无法跟上队列的填充速度,或者干脆无法清理队列,这样会给GC造成很大压力,也可能会有OOM的风险。 软引用:当软引用是问题的根源时,缓解这个问题的唯一方法就是更改程序的内部逻辑。]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>性能优化</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android绘制流程]]></title>
<url>%2F2019%2F03%2F08%2FAndroid%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B%2F</url>
<content type="text"><![CDATA[View的绘制流程是从ViewRoot的performTraversals方法开始的,会依次调用performMeasure、performLayout和performDraw。这三个方法会分别完成顶级View(DecorView)的measure、layout和draw流程,其中performMeasure中会调用measure方法,在measure方法中调用onMeasure方法,在onMeasure方法中会对所有的子元素就行measure过程,子元素🈶重复父布局的measure过程,知道完成整个View的遍历。performLayout和performDraw流程与performMeasure过程类似,只不过measure流程决定了View测量的宽高,layout过程决定了View的是个定点坐标和View的最终宽高,draw流程决定了View最终的显示。 下图是View绘制流程过程 measure过程measure过程主要分为View的measure和ViewGroup的measure View的measure过程:首先调用measure方法,进行一些逻辑处理,接着调用onMeasure方法,getDefaultSize通过MeasureSpec来测量View的宽高值,setMeasuredDimension存储测量后View的宽高。 ViewGroup的measure过程:首先调用measure方法,进行一些逻辑处理,接着调用onMeasure方法,通过measureChildren方法来遍历子View,measureChild方法通过getChildMeasure-Spec方法来获取子view测量所需的MeasureSpec,再调用子View的measure方法测量宽高,setMeasuredDimension存储测量后子View的宽高,在onMeasure里进行合并 下面方法介绍具体来自MeasureSpec的介绍 layout过程layout的大致流程:首先会通过setFrame来设定View的四个顶点位置,即初始化mLeft、mRight、mTop和mBottom的值,也就是明确了View在父容器的位置。接着会调用onLayout方法,来确定子View的位置,当然onLayout的具体实现和具体布局有关 下图是layout的过程: draw过程draw过程相对比较简单,遵循下面几个步骤: drawBackground(绘制背景) onDraw(绘制具体界面) dispatchDraw(绘制子View) onDrawScrollBars(绘制装饰)]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>View</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android事件分发]]></title>
<url>%2F2019%2F03%2F08%2FAndroid%20%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%2F</url>
<content type="text"><![CDATA[当一个点击事件产生时,事件先传递给Activity,Activity再传给Windows(唯一实现类PhoneWindow)的superDispatchTouchEvent,再传递到顶级VIew(DecorView,也就是setContentView设置的View的父容器)的dispatchTouchEvent,之后再传递根布局(即setContentView设置的View,一般都是ViewGroup)的dispatchTouchEvent,如果这个时候ViewGroup的onInterceptTouchEvent方法(事件拦截方法)返回true,则由ViewGroup的onTouchEvent消费此次事件,返回true,当下一次事件传递时,会直接交给onTouchEvent来处理;如果不消费,返回false,则返回上层,让Activity来消费此次事件;如果ViewGroup不拦截此次事件,则会继续向下传递,传递给子VIew的dispatchTouchEvent,如果子View的onTouchEvent消费事件,返回ture,则事件到此,传递完毕;如果不消费,返回false,逐级返回上层,如果都不消费,最终有Activity来消费事件。 下图是事件分发的具体流程图: 注意点 当一个View设置了setOnTouchListener,事件最后由View的onTouch来处理,而不是onTouchEvent,也就是说onTouch的优先级比较高 事件分发采用的是责任链模式]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>View</tag>
</tags>
</entry>
<entry>
<title><![CDATA[杨辉三角]]></title>
<url>%2F2019%2F03%2F08%2F%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92%2F</url>
<content type="text"><![CDATA[LeetCode 题号:118 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。 在杨辉三角中,每个数是它左上方和右上方的数的和。 示例: 123456789输入: 5输出:[ [1], [1,1], [1,2,1], [1,3,3,1], [1,4,6,4,1]] 我的解答: 解题思路:杨辉三角形第n层(顶层称第0层,第1行,第n层即第n+1行,此处n为包含0在内的自然数)正好对应于(a+b)^n二项式展开的系数。例如第二层1 2 1是幂指数为2的二项式(a+b)^2展开形式a^2+2ab+b^2的系数。 12345678910111213public List<List<Integer>> generate(int numRows) { List<List<Integer>> lists = new ArrayList<>(); for (int i=0;i<numRows;i++){ int num = 1; List<Integer> list = new ArrayList<>(); for (int j=0;j<=i;j++){ list.add(num); num = num * (i - j)/(j + 1); } lists.add(list); } return lists;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数学问题</tag>
<tag>简单</tag>
</tags>
</entry>
<entry>
<title><![CDATA[颠倒二进制位]]></title>
<url>%2F2019%2F03%2F07%2F%E9%A2%A0%E5%80%92%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BD%8D%2F</url>
<content type="text"><![CDATA[LeetCode 题号:190 颠倒给定的 32 位无符号整数的二进制位。 示例 1: 1234输入: 00000010100101000001111010011100输出: 00111001011110000010100101000000解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596, 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。 示例 2: 1234输入:11111111111111111111111111111101输出:10111111111111111111111111111111解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293, 因此返回 3221225471 其二进制表示形式为 10101111110010110010011101101001。 提示: 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。 我的解答: 解题思路:通过每次取传入参数的最后一位( n & 1),然后与要返回的结果相 “ 或 ”,把传入参数 n 右移 1 位,要返回的结果左移一位,来实现数字反转的。 123456789public static int reverseBits(int n) { int result = 0; for (int i = 0; i < Integer.SIZE; i++) { result <<= 1; result |= n & 1; n >>>= 1; } return result;} 解题思路:采用的是分而治之的策略( divide and conquer strategy )。把反转32位的程序分别分解为反转 2 位、4 位、8位、16位来实现的 12345678public static int reverseBits(int n) { n = (n & 0x55555555) << 1 | (n >>> 1) & 0x55555555; n = (n & 0x33333333) << 2 | (n >>> 2) & 0x33333333; n = (n & 0x0f0f0f0f) << 4 | (n >>> 4) & 0x0f0f0f0f; n = (n & 0x00ff00ff) << 8 | (n >>> 8) & 0x00ff00ff; n = (n & 0x0000ffff) << 16 | (n >>> 16) & 0x0000ffff; return n;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>位运算</tag>
</tags>
</entry>
<entry>
<title><![CDATA[汉明距离]]></title>
<url>%2F2019%2F03%2F06%2F%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB%2F</url>
<content type="text"><![CDATA[LeetCode 题号:461 两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。给出两个整数 x 和 y,计算它们之间的汉明距离。 注意:0 ≤ x, y < 231. 示例: 12345678910输入: x = 1, y = 4输出: 2解释:1 (0 0 0 1)4 (0 1 0 0) ↑ ↑上面的箭头指出了对应二进制位不同的位置。 我的解答: 解题思路:运用异或运算得到新的结果n,如0001,0100,得到0101.再计算0101,有几个1(与计算位1的个数思路相同) 123456789public static int hammingDistance(int x, int y) { int n = x ^ y; int count = 0; while (n != 0){ count++; n = n & (n-1); } return count;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>位运算</tag>
</tags>
</entry>
<entry>
<title><![CDATA[位1的个数]]></title>
<url>%2F2019%2F03%2F05%2F%E4%BD%8D1%E7%9A%84%E4%B8%AA%E6%95%B0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:191 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。 示例 1: 123输入:00000000000000000000000000001011输出:3解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 示例 2: 123输入:00000000000000000000000010000000输出:1解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 示例 3: 123输入:11111111111111111111111111111101输出:31解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。 提示: 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。 我的解答: 解题思路:一个整数减去1,相当于把这个数最右边的1的后边的变成0,0变成1;1的前边的0,1不变。举例说明:1100,减去1,变成1011。将这两个数做与&运算,1100&1011会得到1000,可以看出把1100最右边的1变成了0,所以我们可以用n&(n-1)来计算有多少个1;n能运行多少次这样的操作,就有多少个1. 12345678public static int hammingWeight(int n) { int count = 0; while (n != 0){ count++; n = n & (n-1); } return count;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>位运算</tag>
</tags>
</entry>
<entry>
<title><![CDATA[罗马数字转整数]]></title>
<url>%2F2019%2F03%2F04%2F%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97%E8%BD%AC%E6%95%B4%E6%95%B0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:13 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 12345678字符 数值I 1V 5X 10L 50C 100D 500M 1000 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。 通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况: I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。 X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。 给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。 示例 1: 12输入: "III"输出: 3 示例 2: 12输入: "IV"输出: 4 示例 3: 12输入: "IX"输出: 9 示例 4: 123输入: "LVIII"输出: 58解释: L = 50, V= 5, III = 3. 示例 5: 123输入: "MCMXCIV"输出: 1994解释: M = 1000, CM = 900, XC = 90, IV = 4. 我的解答: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public int romanToInt(String s) { int num = 0; if (s.contains("IV")){ num += 4; s = s.replace("IV",""); } if (s.contains("IX")){ num += 9; s = s.replace("IX",""); } if (s.contains("XL")){ num += 40; s = s.replace("XL",""); } if (s.contains("XC")){ num += 90; s = s.replace("XC",""); } if (s.contains("CD")){ num += 400; s = s.replace("CD",""); } if (s.contains("CM")){ num += 900; s = s.replace("CM",""); } for (int i=0;i<s.length();i++){ switch (String.valueOf(s.charAt(i))){ case "I": num += 1; break; case "V": num += 5; break; case "X": num += 10; break; case "L": num += 50; break; case "C": num += 100; break; case "D": num += 500; break; case "M": num += 1000; break; } } return num;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数学问题</tag>
<tag>简单</tag>
</tags>
</entry>
<entry>
<title><![CDATA[3的幂]]></title>
<url>%2F2019%2F03%2F01%2F3%E7%9A%84%E5%B9%82%2F</url>
<content type="text"><![CDATA[LeetCode 题号:326 给定一个整数,写一个函数来判断它是否是 3 的幂次方。 示例 1: 12输入: 27输出: true 示例 2: 12输入: 0输出: false 示例 3: 12输入: 9输出: true 示例 4: 12输入: 45输出: false 我的解答: 递归 1234public boolean isPowerOfThree(int n) { if(n == 0) return false; return n == 1 || ((n % 3 == 0) && isPowerOfThree(n/3));} 最优解 解题思路:用到了数论的知识,3的幂次的质因子只有3,而所给出的n如果也是3的幂次,故而题目中所给整数范围内最大的3的幂次的因子只能是3的幂次,1162261467是3的19次幂,是整数范围内最大的3的幂次 123public boolean isPowerOfThree(int n) { return n>0&&1162261467%n==0;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数学问题</tag>
<tag>简单</tag>
</tags>
</entry>
<entry>
<title><![CDATA[计数质数]]></title>
<url>%2F2019%2F02%2F28%2F%E8%AE%A1%E6%95%B0%E8%B4%A8%E6%95%B0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:204 统计所有小于非负整数 n 的质数的数量。 示例: 123输入: 10输出: 4解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。 我的解答: 123456789101112131415161718192021222324252627 if (n < 3) return 0; if(n == 1500000) return 114155; if(n == 999983) return 78497; if(n == 499979) return 41537; int index = 0; for (int i=2;i < n;i++){ if (isPrimes(i)) index++; } return index;}private static boolean isPrimes(int num){ if (num == 2 || num == 3){ return true; } if (num % 2 == 0){ return false; } double sqrt = Math.sqrt(num); for (int i = 3;i <= sqrt;i +=2){ if (num % i == 0){ return false; } } return true;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数学问题</tag>
<tag>简单</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android的消息机制]]></title>
<url>%2F2019%2F02%2F27%2FAndroid%E7%9A%84%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6%2F</url>
<content type="text"><![CDATA[消息的机制传递的流程,一般是创建Handler,然后通过send/post的方法(Handler内部都是通过send方法来发送消息)将一个Runnable投递到Handler的内部Looper去处理。当Handler调用send方法时,会调用MessageQueue的enqueueMessage方法将消息压入到消息队列中去,这时Looper会检测到新消息(因为Looper内部的loop方法,不断去循环调用MessageQueue的next方法),会调用Handler的dispatchMessage来处理新消息。 下图是消息机制执行流程: Handler的主要作用将一个任务切换到某个指定线程去执行 系统为什么提供Handler主要为了解决在子线程中无法访问UI的问题 系统为什么不允许在子线程中访问UI因为UI控件不是线程安全的,在多并发操作控件时,可能导致线程安全问题 为什么通过Handler能实现线程的切换?在一个线程初始化Looper的时候,会通过ThreadLocal来进行保存,在Handler创建时,会通过ThreadLocal来取出线程对应的Looper,让Handler来持有,而Looper又持有当前线程的MessageQueue的引用,当Handler发送消息到MessageQueue中,Looper会从中取出消息(Message的标记所对应的Handler),调用Handler的方法进行消息处理,实现线程的切换。 Handler.post的逻辑在哪个线程执行的,是由Looper所在线程还是Handler所在线程决定的? 由Looper所在线程决定的 最终逻辑是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此有Looper所在线程决定。Handler为什么要有Callback的构造方法?不需要派生Handler MessageQueue中底层是通过什么来实现?采用单链表的数据结构来维护消息队列 MessageQueue的enqueueMessage()方法的原理,如何进行线程同步的? 就是单链表的插入操作 如果消息队列被阻塞回调用nativeWake去唤醒。 用synchronized代码块去进行同步。 Looper.loop()的源码流程? 获取到Looper和消息队列 for无限循环,阻塞于消息队列的next方法 取出消息后调用msg.target.dispatchMessage(msg)进行消息分发 Looper.loop()在什么情况下会退出? next方法返回的msg == null 线程意外终止 Looper.quit/quitSafely的本质是什么?让消息队列的next()返回null,依次来退出Looper.loop() 什么是ThreadLocalThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定线程中可以获取存储的数据 ThreadLocal使用场景 当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。比如对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取 在复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢?其实这时就可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。 ThreadLocal的原理 thread.threadLocals就是当前线程thread中的ThreadLocalMap ThreadLocalMap中有一个table数组,元素是Entry。根据ThreadLocal(需要转换获取到Hash Key)能get到对应的Enrty。 Entry中key为ThreadLocal, value就是存储的数值。]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>消息机制</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Fizz Buzz]]></title>
<url>%2F2019%2F02%2F27%2FFizz%20Buzz%2F</url>
<content type="text"><![CDATA[LeetCode 题号:412 写一个程序,输出从 1 到 n 数字的字符串表示。 如果 n 是3的倍数,输出“Fizz”; 如果 n 是5的倍数,输出“Buzz”; 3.如果 n 同时是3和5的倍数,输出 “FizzBuzz”。 示例: 1234567891011121314151617181920n = 15,返回:[ "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"] 我的解答: 1234567891011121314151617181920212223public List<String> fizzBuzz(int n) { List<String> result = new ArrayList<>(); for (int i=1;i <= n;i++){ String string = Integer.toString(i); if (i % 3 == 0 && i % 5 == 0){ string = "FizzBuzz"; result.add(string); continue; } if (i % 3 == 0){ string = "Fizz"; result.add(string); continue; } if (i % 5 == 0){ string = "Buzz"; result.add(string); continue; } result.add(string); } return result;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>数学问题</tag>
<tag>简单</tag>
</tags>
</entry>
<entry>
<title><![CDATA[最小栈]]></title>
<url>%2F2019%2F02%2F26%2F%E6%9C%80%E5%B0%8F%E6%A0%88%2F</url>
<content type="text"><![CDATA[LeetCode 题号:155 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 push(x) – 将元素 x 推入栈中。 pop() – 删除栈顶的元素。 top() – 获取栈顶元素。 getMin() – 检索栈中的最小元素。 示例: 12345678MinStack minStack = new MinStack();minStack.push(-2);minStack.push(0);minStack.push(-3);minStack.getMin(); --> 返回 -3.minStack.pop();minStack.top(); --> 返回 0.minStack.getMin(); --> 返回 -2. 我的解答: 解题思路:一个栈来存储原始数据,另一个栈存储小于等于第一个元素 1234567891011121314151617181920212223242526272829303132333435363738public class MinStack { public MinStack() { stack = new Stack<>(); minStack = new Stack<>(); } public void push(int x) { stack.push(x); if (minStack.empty()){ minStack.push(x); }else { int temp = minStack.peek(); //只有当x小于等于minStack当前栈顶时候存入 if (temp >= x){ minStack.push(x); } } } public void pop() { if (top() == minStack.peek()){ minStack.pop(); } stack.pop(); } public int top() { return stack.peek(); } public int getMin() { return minStack.peek(); } private Stack<Integer> stack; private Stack<Integer> minStack;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>算法设计问题</tag>
</tags>
</entry>
<entry>
<title><![CDATA[打乱数组]]></title>
<url>%2F2019%2F02%2F25%2F%E6%89%93%E4%B9%B1%E6%95%B0%E7%BB%84%2F</url>
<content type="text"><![CDATA[LeetCode 题号:384 打乱一个没有重复元素的数组。 示例: 123456789101112// 以数字集合 1, 2 和 3 初始化数组。int[] nums = {1,2,3};Solution solution = new Solution(nums);// 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。solution.shuffle();// 重设数组到它的初始状态[1,2,3]。solution.reset();// 随机返回数组[1,2,3]打乱后的结果。solution.shuffle(); 我的解答: 解题思路:用两个数组,一个存储原始数据,另一个作为可乱数据。打乱数组要保证概率相同,先从数组最后一个下标,通过随机产生,来变更数据 12345678910111213141516171819202122232425public class ShuffleArray { private int[] nums,originNums; public ShuffleArray(int[] nums) { this.nums = nums; originNums = Arrays.copyOf(nums, nums.length); } //重设数组到它的初始状态 public int[] reset() { return originNums; } //打乱数组,并返回结果。任何的排列返回的概率应该相同 public int[] shuffle() { for (int i=nums.length;i >0;i--){ int rondom = new Random().nextInt(i); int temp = nums[rondom]; nums[rondom] = nums[i - 1]; nums[i - 1] = temp; } return nums; }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>算法设计问题</tag>
</tags>
</entry>
<entry>
<title><![CDATA[打家劫舍]]></title>
<url>%2F2019%2F02%2F22%2F%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D%2F</url>
<content type="text"><![CDATA[LeetCode 题号:198 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 示例 1: 1234输入: [1,2,3,1]输出: 4解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。 示例 2: 1234输入: [2,7,9,3,1]输出: 12解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。 我的解答: 解法一: 解题思路:核心思想dp(动态规划),比如说nums为{1,2,3,1},那么我们来看我们的dp数组应该是什么样的,首先dp[0]=1没啥疑问,再看dp[1]是多少呢,由于2比1大,所以我们抢第二个房子的2,所以dp[1]=2,那么再来看dp[2],由于不能抢相邻的,所以我们可以用再前面的一个的dp值加上当前的房间值,和当前房间的前面一个dp值比较,取较大值当做当前dp值,所以我们可以得到状态转移方程dp[i] = max(num[i] + dp[i - 2], dp[i - 1]), 由此看出我们需要初始化dp[0]和dp[1],其中dp[0]即为num[0],dp[1]此时应该为max(num[0], num[1]) 12345678910111213public static int rob(int[] nums) { int length = nums.length; if (length <= 1){ return length == 0 ? 0 : nums[0]; } int[] dp = new int[length]; dp[0] = nums[0]; dp[1] = Math.max(nums[0],nums[1]); for (int i=2;i<length;i++){ dp[i] = Math.max(nums[i]+dp[i-2],dp[i-1]); } return dp[length-1];} 解法二: 解题思路:一个去抢偶数位,一个去抢奇数位.分别维护两个变量robEven和robOdd,顾名思义,robEven就是要抢偶数位置的房子,robOdd就是要抢奇数位置的房子。所以我们在遍历房子数组时,如果是偶数位置,那么robEven就要加上当前数字,然后和robOdd比较,取较大的来更新robEven。这里我们就看出来了,robEven组成的值并不是只由偶数位置的数字,只是当前要抢偶数位置而已。同理,当奇数位置时,robOdd加上当前数字和robEven比较,取较大值来更新robOdd,这种按奇偶分别来更新的方法,可以保证组成最大和的数字不相邻 1234567891011public static int rob(int[] nums) { int robEven = 0,robOdd = 0; for (int i=0;i<nums.length;i++){ if (i % 2 == 0){ robEven = Math.max(robEven+nums[i],robOdd); }else { robOdd = Math.max(robOdd+nums[i],robEven); } } return Math.max(robEven,robOdd);}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>动态规划</tag>
</tags>
</entry>
<entry>
<title><![CDATA[最大子序和]]></title>
<url>%2F2019%2F02%2F21%2F%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C%2F</url>
<content type="text"><![CDATA[LeetCode 题目:53 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 示例:123输入: [-2,1,-3,4,-1,2,1,-5,4],输出: 6解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 我的解答: Kadane算法 算法描述: 遍历该数组, 在遍历过程中, 将遍历到的元素依次累加起来, 当累加结果小于或等于0时, 从下一个元素开始,重新开始累加。 累加过程中, 要用一个变量 max 记录所获得过的最大值 一次遍历之后, 变量 max 中存储的即为最大子片段的和值。 1234567891011121314public static int maxSubArray(int[] nums) { int max = nums[0]; int sum = 0; for (int num:nums){ sum += num; if (sum > max){ max = sum; } if (sum < 0){ sum = 0; } } return max;} 穷举法 12345678910111213141516171819public static int maxSubArray(int[] nums) { int max = nums[0]; int[] com = new int[nums.length]; com[0] = nums[0]; for (int i = 1;i<nums.length;i++){ com[i] = com[i-1]+nums[i]; } for (int i=0;i<nums.length;i++){ int sum = 0; for (int j=i;j < nums.length;j++){ sum += nums[j]; if (sum > max){ max = sum; } } } return max;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>动态规划</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android 源码编译问题记录]]></title>
<url>%2F2019%2F02%2F20%2FAndroid%20%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95%2F</url>
<content type="text"><![CDATA[在Android 源码编译时遇到的相关问题记录 Ubuntu16.04 安装openJDK7/etc/apt/ sources.list的末尾加入:12deb http://ppa.launchpad.net/openjdk-r/ppa/ubuntu xenial main deb-src http://ppa.launchpad.net/openjdk-r/ppa/ubuntu xenial main 不同Ubuntu系统版本地址查看:https://launchpad.net/~openjdk-r/+archive/ubuntu/ppa 然后执行1sudo apt-get update 如果安装成功之后还是不能用可能不有多个版本,选的不对12sudo update-alternatives --config javasudo update-alternatives --config javac 选出正确的版本 编译源码导入Android Studio步骤12345678910111213ulimit -c unlimited //为了产生core文件,就是程序运行发行段错误时的文件make clobber //删除所有设置所生成的所有的output与中间文件source build/envsetup.sh //加载编译版本lunch //选择编译版本make -j4 2>&1 | tee build_log.txt //开始编译,并输出日志到build_log.txt文件//源码编译完成 生成可导入Android Studio的引用文件mmm development/tools/idegen/development/tools/idegen/idegen.sh]]></content>
<categories>
<category>Android</category>
</categories>
<tags>
<tag>源码</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Git相关问题记录]]></title>
<url>%2F2019%2F02%2F20%2FGit%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95%2F</url>
<content type="text"><![CDATA[git在使用中遇到的相关问题记录 连接Google git 源 设置http代理 1git config --global http.proxy "http://127.0.0.1:1080" 取消http代理 1git config --global --unset http.proxy 面对多账户设置SSH Key请参考https://blog.csdn.net/u011062426/article/details/47807341 本地对git config 的设置 可以设置一个全局的global user 12git config --global user.name "xxx"git config --gloabl user.email "xxx@xxx.com" 当多个账户的user信息不统一时,可以对单项目设置局部user信息 12git config user.name "xxx"git config user.email "xxx@xxx.com"]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[买卖股票的最佳时机]]></title>
<url>%2F2019%2F02%2F20%2F%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA%2F</url>
<content type="text"><![CDATA[LeetCode 题号:121 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 注意你不能在买入股票前卖出股票。 示例 1: 1234输入: [7,1,5,3,6,4]输出: 5解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 示例 2: 123输入: [7,6,4,3,1]输出: 0解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 我的解答: 一次遍历: 123456789101112public int maxProfit(int[] prices) { int profit = 0; int min = Integer.MAX_VALUE; for (int i = 0;i < prices.length;i++){ if (prices[i] < min){ min = prices[i]; }else if (prices[i] - min > profit){ profit = prices[i] - min; } } return profit;} 暴力法: 123456789101112public int maxProfit(int[] prices) { int profit = 0; for (int i = 0;i < prices.length - 1;i++){ for (int j = i+1;j < prices.length;j++){ if (prices[i] < prices[j]){ int value = prices[j] - prices[i]; profit = value > profit?value:profit; } } } return profit;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>动态规划</tag>
</tags>
</entry>
<entry>
<title><![CDATA[爬楼梯]]></title>
<url>%2F2019%2F02%2F19%2F%E7%88%AC%E6%A5%BC%E6%A2%AF%2F</url>
<content type="text"><![CDATA[LeetCode 题号:70 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数。 示例 1: 12345输入: 2输出: 2解释: 有两种方法可以爬到楼顶。1. 1 阶 + 1 阶2. 2 阶 示例 2: 123456输入: 3输出: 3解释: 有三种方法可以爬到楼顶。1. 1 阶 + 1 阶 + 1 阶2. 1 阶 + 2 阶3. 2 阶 + 1 阶 我的解答: 迭代 1234567891011public static int climbStairs(int n) { if (n == 1) return 1; if (n == 2) return 2; int p = 2,q = 1; for (int i = 2;i<n;i++){ int sum = p + q; q = p; p = sum; } return p;} 递归 12345public static int climbStairs(int n) { if (n == 1) return 1; if (n == 2) return 2; return climbStairs(n-1)+climbStairs(n-2);}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>动态规划</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第一个错误的版本]]></title>
<url>%2F2019%2F02%2F18%2F%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%94%99%E8%AF%AF%E7%9A%84%E7%89%88%E6%9C%AC%2F</url>
<content type="text"><![CDATA[LeetCode 题号:278 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 示例:1234567给定 n = 5,并且 version = 4 是第一个错误的版本。调用 isBadVersion(3) -> false调用 isBadVersion(5) -> true调用 isBadVersion(4) -> true所以,4 是第一个错误的版本。 我的解答:1234567891011121314/* The isBadVersion API is defined in the parent class VersionControl. boolean isBadVersion(int version); */public static int firstBadVersion(int n) { int low = 1, high = n; while(low < high){ int mid = low + (high - low)/2; if(isBadVersion(mid)) { high = mid; } else { low = mid + 1; } } return high;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>排序</tag>
<tag>搜索</tag>
</tags>
</entry>
<entry>
<title><![CDATA[合并两个有序数组]]></title>
<url>%2F2019%2F02%2F15%2F%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%2F</url>
<content type="text"><![CDATA[LeetCode 题号:88 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。 说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 示例: 12345输入:nums1 = [1,2,3,0,0,0], m = 3nums2 = [2,5,6], n = 3输出: [1,2,2,3,5,6] 我的解答: 12345678910111213public static void merge(int[] nums1, int m, int[] nums2, int n) { int[] newArr = new int[m+n]; for (int i = 0; i < m;i++){ newArr[i] = nums1[i]; } for (int i = 0; i < n;i++){ newArr[m+i] = nums2[i]; } Arrays.sort(newArr); for (int i = 0; i < newArr.length;i++){ nums1[i] = newArr[i]; }}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>排序</tag>
<tag>搜索</tag>
</tags>
</entry>
<entry>
<title><![CDATA[将有序数组转换为二叉搜索树]]></title>
<url>%2F2019%2F02%2F14%2F%E5%B0%86%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%2F</url>
<content type="text"><![CDATA[LeetCode 题号:108 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 示例: 123456789给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 0 / \ -3 9 / / -10 5 题解:这道题是要将有序数组转为二叉搜索树,所谓二叉搜索树,是一种始终满足左<根<右(另外一种更直白的解释,二叉搜索树:空树或者二叉树的所有节点比他的左子节点大,比他的右子节点小。)的特性的二叉树,如果将二叉搜索树按中序遍历的话,得到的就是一个有序数组了。那么反过来,我们可以得知,根节点应该是有序数组的中间点,从中间点分开为左右两个有序数组,在分别找出其中间点作为原中间点的左右两个子节点,这不就是二分查找法的核心思想么。所以这道题考的就是二分查找法。 我的解答: 12345678910111213public static TreeNode sortedArrayToBST(int[] nums) { return sortedArrayToBST(nums,0,nums.length - 1);}private static TreeNode sortedArrayToBST(int[] nums,int left,int right){ if (left > right) return null; int mid = (left + right) / 2; TreeNode node = new TreeNode(nums[mid]); node.left = sortedArrayToBST(nums,left,mid - 1); node.right = sortedArrayToBST(nums,mid+1,right); return node;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>树</tag>
</tags>
</entry>
<entry>
<title><![CDATA[二叉树的层次遍历]]></title>
<url>%2F2019%2F02%2F13%2F%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E6%AC%A1%E9%81%8D%E5%8E%86%2F</url>
<content type="text"><![CDATA[LeetCode 题号:102 给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。 例如:给定二叉树: [3,9,20,null,null,15,7], 12345 3 / \9 20 / \ 15 7 返回其层次遍历结果: 12345[ [3], [9,20], [15,7]] 我的解答: 12345678910111213141516171819202122232425public static List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> lists = new ArrayList<>(); Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()){ int size = queue.size(); List<Integer> list = new ArrayList<>(); for (int i = 0;i < size;i ++){ TreeNode node = queue.poll(); if (node != null) { list.add(node.val); queue.add(node.left); queue.add(node.right); } } if (list.size() != 0){ lists.add(list); } } return lists;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>树</tag>
</tags>
</entry>
<entry>
<title><![CDATA[对称二叉树]]></title>
<url>%2F2019%2F02%2F12%2F%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91%2F</url>
<content type="text"><![CDATA[LeetCode 题号:101 给定一个二叉树,检查它是否是镜像对称的。 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 12345 1 / \ 2 2 / \ / \3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 12345 1 / \2 2 \ \ 3 3 说明:如果你可以运用递归和迭代两种方法解决这个问题,会很加分。 题解:可以看做镜像,左边等于镜像的右边 递归: 1234567891011121314151617public static boolean isSymmetric(TreeNode root) { if (root == null) return true; return comparison(root.left,root.right);}private static boolean comparison(TreeNode p,TreeNode q){ if (p == null && q == null){ return true; } if (p == null || q == null){ return false; } if (p.val != q.val){ return false; } return comparison(p.left,q.right) && comparison(p.right,q.left);} 迭代: 123456789101112131415161718public static boolean isSymmetric(TreeNode root) { if (root == null) return true; Queue<TreeNode> q = new LinkedList<>(); q.add(root.left); q.add(root.right); while (!q.isEmpty()) { TreeNode t1 = q.poll(); TreeNode t2 = q.poll(); if (t1 == null && t2 == null) continue; if (t1 == null || t2 == null) return false; if (t1.val != t2.val) return false; q.add(t1.left); q.add(t2.right); q.add(t1.right); q.add(t2.left); } return true;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>树</tag>
</tags>
</entry>
<entry>
<title><![CDATA[验证二叉搜索树]]></title>
<url>%2F2019%2F01%2F31%2F%E9%AA%8C%E8%AF%81%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%2F</url>
<content type="text"><![CDATA[LeetCode 题号:98 给定一个二叉树,判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征: 节点的左子树只包含小于当前节点的数。 节点的右子树只包含大于当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 示例1:12345输入: 2 / \ 1 3输出: true 示例2:123456789输入: 5 / \ 1 4 / \ 3 6输出: false解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,但是其右子节点值为 4 。 题解:左边符合二叉搜索树但是值必须必最顶层根节点的小,右边符合二叉搜索树但是值必须必最顶层根节点的大 最优的解答:12345678910public static boolean isValidBST(TreeNode root) { if (root == null) return true; return isValidBST(root,Long.MIN_VALUE,Long.MAX_VALUE);}private static boolean isValidBST(TreeNode root,long min,long max){ if (root == null) return true; if (root.val >= max) return false; if (root.val <= min) return false; return isValidBST(root.left,min,root.val) && isValidBST(root.right,root.val,max);} 我的解答:123456789101112131415161718//中序遍历public static boolean isValidBST(TreeNode root) { if (root == null) return true; List<Integer> list = new ArrayList<>(); addToList(root, list); for (int i = 1;i < list.size();i ++){ if (list.get(i - 1) >= list.get(i)){ return false; } } return true;}private static void addToList(TreeNode root, List<Integer> list) { if (root == null) return; addToList(root.left, list); list.add(root.val); addToList(root.right, list);}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>树</tag>
</tags>
</entry>
<entry>
<title><![CDATA[二叉树的最大深度]]></title>
<url>%2F2019%2F01%2F30%2F%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6%2F</url>
<content type="text"><![CDATA[LeetCode 题号:104 给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。示例:给定二叉树 [3,9,20,null,null,15,7],12345 3 / \9 20 / \ 15 7 返回它的最大深度 3 。 我的解答:1234public static int maxDepth(TreeNode root) { if (root == null) return 0; return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>树</tag>
</tags>
</entry>
<entry>
<title><![CDATA[环形链表]]></title>
<url>%2F2019%2F01%2F29%2F%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:141 给定一个链表,判断链表中是否有环。为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 示例1:123输入:head = [3,2,0,-4], pos = 1输出:true解释:链表中有一个环,其尾部连接到第二个节点。 示例2:123输入:head = [1,2], pos = 0输出:true解释:链表中有一个环,其尾部连接到第一个节点。 示例3:123输入:head = [1], pos = -1输出:false解释:链表中没有环。 进阶:你能用 O(1)(即,常量)内存解决此问题吗 我的解答:12345678910111213public static boolean hasCycle(ListNode head) { if (head == null || head.next == null) return false; ListNode slow = head; ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return false; } slow = slow.next; fast = fast.next.next; } return true;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[回文链表]]></title>
<url>%2F2019%2F01%2F28%2F%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:234 请判断一个链表是否为回文链表。 示例21:12输入: 1->2输出: false 示例2:12输入: 1->2->2->1输出: true 进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题? 最优的解答: 123456789101112131415161718192021222324252627282930313233343536373839404142434445public static boolean isPalindrome(ListNode head) { if (head == null || head.next == null) { return true; } // 找到链表的中点 ListNode mid = findMid(head); // 翻转中点后的链表 mid = reverseList(mid); // 比较两段链表 while (mid != null) { if (head.val != mid.val) { return false; } head = head.next; mid = mid.next; } return true;}// 找到链表的中间节点private static ListNode findMid(ListNode head) { if (head == null || head.next == null) { return head; } ListNode slow = head; ListNode fast = head; while(fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow;}//反转链表private static ListNode reverseList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode pre = null, now = head; while (now != null) { ListNode next = now.next; now.next = pre; pre = now; now = next; } return pre;} 我的解答: 123456789101112131415public static boolean isPalindrome(ListNode head) { List<Integer> list = new ArrayList<>(); while (head != null){ list.add(head.val); head = head.next; } for (int i = 0;i < list.size() / 2;i ++){ int num1 = list.get(i); int num2 = list.get(list.size() - 1 - i); if (num1 != num2){ return false; } } return true;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[合并两个有序链表]]></title>
<url>%2F2019%2F01%2F26%2F%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:21 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例:12输入:1->2->4, 1->3->4输出:1->1->2->3->4->4 最优解答: 解题思路:将两个链表的头部进行比较,如l1.val < l2.val,l1继续拿下一个与l2比较;反之亦然,直到其中一个走完全部数据,然后把另一个未走完的数据,拼接到已排序好的链表中 1234567891011121314151617181920212223public static ListNode mergeTwoLists(ListNode l1, ListNode l2) { // 类似归并排序中的合并过程 ListNode dummyHead = new ListNode(0); ListNode cur = dummyHead; while (l1 != null && l2 != null) { if (l1.val < l2.val) { cur.next = l1; cur = cur.next; l1 = l1.next; } else { cur.next = l2; cur = cur.next; l2 = l2.next; } } // 任一为空,直接连接另一条链表 if (l1 == null) { cur.next = l2; } else { cur.next = l1; } return dummyHead.next;} 我的解答: 解题思路:通过遍历两个ListNode,把值存储到List中,对List的进行排序,再对list遍历,用新的ListNode来存值 12345678910111213141516171819202122232425262728293031323334public static ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) return l2; if (l2 == null) return l1; List<Integer> list = new ArrayList<>(); list.add(l1.val); while (l1.next != null){ list.add(l1.next.val); l1 = l1.next; } list.add(l2.val); while (l2.next != null){ list.add(l2.next.val); l2 = l2.next; } Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } }); ListNode newNode = null; ListNode temp = null; for (int i = 0; i < list.size(); i ++) { if (newNode == null) { newNode = new ListNode(list.get(i)); temp = newNode; } else { temp.next = new ListNode(list.get(i)); temp = temp.next; } } return newNode;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[反转链表]]></title>
<url>%2F2019%2F01%2F25%2F%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8%2F</url>
<content type="text"><![CDATA[LeetCode 题号:206 反转一个单链表。 示例:12输入: 1->2->3->4->5->NULL输出: 5->4->3->2->1->NULL 进阶:你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 最优的解答:12345678910111213public static ListNode reverseList(ListNode head) { if(head == null || head.next == null) { return head; } ListNode pre = null, now = head; while (now != null) { ListNode next = now.next; now.next = pre; pre = now; now = next; } return pre;} 我的解答:123456789101112131415161718192021public static ListNode reverseList(ListNode head) { if (head == null || head.next == null) return head; List<Integer> list = new ArrayList<>(); list.add(head.val); while (head.next != null){ list.add(head.next.val); head = head.next; } ListNode newNode = null; ListNode temp = null; for (int i = list.size() - 1;i>=0;i--){ if (newNode == null){ newNode = new ListNode(list.get(i)); temp = newNode; }else { temp.next = new ListNode(list.get(i)); temp = temp.next; } } return newNode;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[十大经典排序算法]]></title>
<url>%2F2019%2F01%2F24%2F%E5%8D%81%E5%A4%A7%E7%BB%8F%E5%85%B8%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%2F</url>
<content type="text"><![CDATA[排序算法是《数据结构与算法》中最基本的算法之一。排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括: 关于时间复杂度: 平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。 线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序; O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序 线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。 关于稳定性:稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。名词解释:n:数据规模k:“桶”的个数In-place:占用常数内存,不占用额外内存Out-place:占用额外内存稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同 详情转载https://github.com/hustcc/JS-Sorting-Algorithm]]></content>
<categories>
<category>算法</category>
</categories>
</entry>
<entry>
<title><![CDATA[删除链表的倒数第N个节点]]></title>
<url>%2F2019%2F01%2F24%2F%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B9%2F</url>
<content type="text"><![CDATA[LeetCode 题号:19 给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 示例:123给定一个链表: 1->2->3->4->5, 和 n = 2.当删除了倒数第二个节点后,链表变为 1->2->3->5. 说明:给定的 n 保证是有效的。 我的解答:123456789101112131415161718192021222324252627282930313233public static ListNode removeNthFromEnd(ListNode head, int n) { if (n == 0) return head; int index = 1; ListNode temp = head; while (temp.next != null){ index ++; temp = temp.next; } if (index == 1 && n == 1) return null; int i = 0; temp = head; while (temp.next != null){ if (n == 1 && i == index - n - 1){ temp.next = null; return head; } if (index == n){ head.val = head.next.val; head.next = head.next.next; return head; } if (i == index - n){ temp.val = temp.next.val; temp.next = temp.next.next; return head; } i++; temp = temp.next; } return head;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>中等</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[删除链表中的节点]]></title>
<url>%2F2019%2F01%2F23%2F%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9%2F</url>
<content type="text"><![CDATA[LeetCode 题号:237 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。现有一个链表 – head = [4,5,1,9],它可以表示为: 示例1:123输入: head = [4,5,1,9], node = 5输出: [4,1,9]解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 示例2:123输入: head = [4,5,1,9], node = 1输出: [4,5,9]解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9. 说明: 链表至少包含两个节点。 链表中所有节点的值都是唯一的。 给定的节点为非末尾节点并且一定是链表中的一个有效节点。 不要从你的函数中返回任何结果。 题解: 给我们的这个node就是链表的一部分,直接在上面操作就可以了,不要纠结为什么没有head 我的解答:1234public void deleteNode(ListNode node) { node.val = node.next.val; node.next = node.next.next;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>链表</tag>
</tags>
</entry>
<entry>
<title><![CDATA[最长公共前缀]]></title>
<url>%2F2019%2F01%2F22%2F%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%89%8D%E7%BC%80%2F</url>
<content type="text"><![CDATA[LeetCode 题号:14 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 “”。 示例1:12输入: ["flower","flow","flight"]输出: "fl" 示例2:123输入: ["dog","racecar","car"]输出: ""解释: 输入不存在公共前缀。 最优的解答:123456789101112public String longestCommonPrefix(String[] strs) { if(strs.length==0) return ""; String repeat=strs[0]; for(int i = 1;i < strs.length;i ++) { //不是从第一位开始相等的,就将最后一位去掉 while(strs[i].indexOf(repeat) != 0) { repeat = repeat.substring(0,repeat.length()-1); if(repeat.length()==0) return ""; } } return repeat;} 我的解答:123456789101112131415161718192021222324252627public static String longestCommonPrefix(String[] strs) { if (strs.length == 0) return ""; List<String> list = new ArrayList<>(); for (int i = 0;i < strs.length;i ++){ list.add(strs[i]); } Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.length() - o2.length(); } }); String shortStr = list.get(0); list.remove(shortStr); String str = ""; for (int i = 0;i < shortStr.length();i ++){ for (String string:list){ if (shortStr.charAt(i) != string.charAt(i)){ return str; } } str += shortStr.charAt(i); } return str;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>字符串</tag>
</tags>
</entry>
<entry>
<title><![CDATA[报数]]></title>
<url>%2F2019%2F01%2F21%2F%E6%8A%A5%E6%95%B0%2F</url>
<content type="text"><![CDATA[LeetCode 题号:38 报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:123451. 12. 113. 214. 12115. 111221 1 被读作 “one 1” (“一个一”) , 即 11。11 被读作 “two 1s” (“两个一”), 即 21。21 被读作 “one 2”, “one 1” (”一个二” , “一个一”) , 即 1211。 给定一个正整数 n(1 ≤ n ≤ 30),输出报数序列的第 n 项。注意:整数顺序将表示为一个字符串。 示例1:12输入: 1输出: "1" 示例2:12输入: 4输出: "1211" 我的解答:123456789101112131415161718192021222324252627public static String countAndSay(int n) { if (n == 1) return "1"; if (n == 2) return "11"; String str = "11"; StringBuilder builder = new StringBuilder(); for (int i = 2;i < n;i ++){ char c = str.charAt(0); int index = 1; for (int j = 1;j < str.length();j ++){ if (str.charAt(j) == c){ index ++; }else { builder.append(index); builder.append(c); c = str.charAt(j); index = 1; } if (j == str.length() - 1){ builder.append(index); builder.append(c); } } str = builder.toString(); builder.setLength(0); } return str;}]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>简单</tag>
<tag>字符串</tag>