forked from algorithm020/algorithm020
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWeek1.java
More file actions
2491 lines (2344 loc) · 113 KB
/
Copy pathWeek1.java
File metadata and controls
2491 lines (2344 loc) · 113 KB
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
package algorithm;
import java.util.*;
/**
* @ClassName Week1
* @Description
* @Author Administrator
* @Date 2020/11/22 5:09
* @Version 1.0
**/
public class Week1 {
/**
* 链表相关文章汇总
* 题号 题目 题解 难度等级
* 19 删除链表的倒数第N个节点 两种实现+图解 中等
* 21 合并两个有序链表 两种实现+图解 简单
* 23 合并K个升序链表 四种实现+图解 困难
* 24 两两交换链表中的节点 三种实现+图解 中等
* 25 K 个一组翻转链表 两种实现+图解 困难
* 61 旋转链表 两种实现+图解 中等
* 82 删除排序链表中的重复元素 II 三种实现+图解 中等
* 83 删除排序链表中的重复元素 两种实现+图解 简单
* 141 二叉树展开为链表 四种实现+图解 中等
* 138 复制带随机指针的链表 两种实现+图解 中等
* 141 环形链表 两种实现+图解 简单
* 160 相交链表 两种实现+图解 简单
* 203 移除链表元素 两种实现+图解 简单
* 206 反转链表 两种实现+图解 简单
* 234 回文链表 图解 简单
* 237 删除链表中的节点 图解 简单
* 876 链表的中间结点 图解 简单
* *****************************************************************************************************************
* 第1周 第3课 | 数组、链表、跳表
* *****************************************************************************************************************
* 参考链接
* Java 源码分析(ArrayList)
* Linked List 的标准实现代码
* Linked List 示例代码
* Java 源码分析(LinkedList)
* LRU Cache - Linked list: LRU 缓存机制
* Redis - Skip List:跳跃表、为啥 Redis 使用跳表(Skip List)而不是使用 Red-Black?
* *****************************************************************************************************************
* Array 实战题目
* 两数之和(近半年内,字节跳动在面试中考查此题达到 152 次)
* 盛最多水的容器(腾讯、百度、字节跳动在近半年内面试常考)
* 移动零(华为、字节跳动在近半年内面试常考)
* 爬楼梯(阿里巴巴、腾讯、字节跳动在半年内面试常考)
* 三数之和(国内、国际大厂历年面试高频老题)
* Linked List 实战题目
* 反转链表(字节跳动、亚马逊在半年内面试常考)
* 两两交换链表中的节点(阿里巴巴、字节跳动在半年内面试常考)
* 环形链表(阿里巴巴、字节跳动、腾讯在半年内面试常考)
* 环形链表 II
* K 个一组翻转链表(字节跳动、猿辅导在半年内面试常考)
* *****************************************************************************************************************
*/
/**
* 206. 反转链表(字节跳动、亚马逊在半年内面试常考)
* 反转一个单链表。
* *****************************************************************************************************************
* 示例:
* 输入: 1->2->3->4->5->NULL
* 输出: 5->4->3->2->1->NULL
* 进阶:
* 你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
* *****************************************************************************************************************
* 利用外部空间
* 这种方式很简单,先申请一个动态扩容的数组或者容器,比如 ArrayList 这样的。
* 然后不断遍历链表,将链表中的元素添加到这个容器中。
* 再利用容器自身的 API,反转整个容器,这样就达到反转的效果了。
* 最后同时遍历容器和链表,将链表中的值改为容器中的值。
* 因为此时容器的值是:
* 5 4 3 2 1
* 链表按这个顺序重新被设置一边,就达到要求啦。
* 当然你可以可以再新创建 N 个节点,然后再返回,这样也可以达到目的。
* 这种方式很简单,但你在面试中这么做的话,面试官 100% 会追问是否有更优的方式,比如不用外部空间。所以我就不做代码和动画演示了,下面来看看如何用 O(1)O(1) 空间复杂度来实现这道题。
*
* 双指针迭代
* 我们可以申请两个指针,第一个指针叫 pre,最初是指向 null 的。
* 第二个指针 cur 指向 head,然后不断遍历 cur。
* 每次迭代到 cur,都将 cur 的 next 指向 pre,然后 pre 和 cur 前进一位。
* 都迭代完了(cur 变成 null 了),pre 就是最后一个节点了。
* 动画演示如下:
* 链接:https://leetcode-cn.com/problems/reverse-linked-list/solution/dong-hua-yan-shi-206-fan-zhuan-lian-biao-by-user74/
* 动画演示中其实省略了一个tmp变量,这个tmp变量会将cur的下一个节点保存起来,考虑到一张动画放太多变量会很混乱,所以我就没加了,具体详细执行过程,请点击下面的幻灯片查看。
* 链接:https://leetcode-cn.com/problems/reverse-linked-list/solution/dong-hua-yan-shi-206-fan-zhuan-lian-biao-by-user74/
* 206. Reverse Linked List
* https://leetcode.com/problems/reverse-linked-list/
*/
public ListNode reverseList(ListNode head) {
//申请节点,pre和 cur,pre指向null
ListNode pre = null;
ListNode cur = head;
ListNode tmp = null;
while (cur != null) {
//记录当前节点的下一个节点
tmp = cur.next;
//然后将当前节点指向pre
cur.next = pre;
//pre和cur节点都前进一位
pre = cur;
cur = tmp;
}
return pre;
}
/**
* 递归解法
* 这题有个很骚气的递归解法,递归解法很不好理解,这里最好配合代码和动画一起理解。
* 递归的两个条件:
*
* 终止条件是当前节点或者下一个节点==null
* 在函数内部,改变节点的指向,也就是 head 的下一个节点指向 head 递归函数那句
*
* head.next.next = head
* 很不好理解,其实就是 head 的下一个节点指向head。
* 递归函数中每次返回的 cur 其实只最后一个节点,在递归函数内部,改变的是当前节点的指向。
* 动画演示如下:
* 链接:https://leetcode-cn.com/problems/reverse-linked-list/solution/dong-hua-yan-shi-206-fan-zhuan-lian-biao-by-user74/
* 幻灯片演示
* 感谢@zhuuuu-2的建议,递归的解法光看动画比较容易理解,但真到了代码层面理解起来可能会有些困难,我补充了下递归调用的详细执行过程。
* 以1->2->3->4->5这个链表为例,整个递归调用的执行过程,对应到代码层面(用java做示范)是怎么执行的,以及递归的调用栈都列出来了,请点击下面的幻灯片查看吧。
* 链接:https://leetcode-cn.com/problems/reverse-linked-list/solution/dong-hua-yan-shi-206-fan-zhuan-lian-biao-by-user74/
*/
public ListNode reverseList2(ListNode head) {
//递归终止条件是当前为空,或者下一个节点为空
if (head == null || head.next == null) {
return head;
}
//这里的cur就是最后一个节点
ListNode cur = reverseList(head.next);
//这里请配合动画演示理解
//如果链表是 1->2->3->4->5,那么此时的cur就是5
//而head是4,head的下一个是5,下下一个是空
//所以head.next.next 就是5->4
head.next.next = head;
//防止链表循环,需要将head.next设置为空
head.next = null;
//每层递归函数都返回cur,也就是最后一个节点
return cur;
}
/**
* 方法一 迭代
* 解题思路
* 通过迭代 将 1->2->3->4->5->∮ 转换成 ∮<-1<-2<-3<-4<-5
* 如图执行过程
* https://leetcode-cn.com/problems/reverse-linked-list/solution/die-dai-by-javaniuniu/
*/
public ListNode reverseList3(ListNode head) {
// 申请两个链表 一个空链表,一个完整的链表
ListNode pre = null;
ListNode curr = head;
while (curr != null) {
ListNode temp = curr.next;
curr.next = pre; // 当前链表指向 新链表
pre = curr; // 赋值给新链表
curr = temp;
}
return pre;
}
/**
* We always put a node's previous node as one's next,
* Take 1 -> 2 -> 3 -> N for example, we reverse the list by
* put 1's previous node null as 1's next,
* put 2's previous node 1 as 2's next,
* put 3's previous node 2 as 3's next,
* return 3 // put null's previous node 3 as null's next
* The code is as follows:
*/
public ListNode reverseList4(ListNode head) {
return putPreAfterNode(head, null);
}
private ListNode putPreAfterNode(ListNode node, ListNode pre) {
if (node == null) {
return pre;
}
ListNode next = node.next;
node.next = pre;
return putPreAfterNode(next, node);
}
/**
* 24. 两两交换链表中的节点(阿里巴巴、字节跳动在半年内面试常考)
* 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
* 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
* *****************************************************************************************************************
* 示例 1:
*
* 输入:head = [1,2,3,4]
* 输出:[2,1,4,3]
* 示例 2:
*
* 输入:head = []
* 输出:[]
* 示例 3:
*
* 输入:head = [1]
* 输出:[1]
* *****************************************************************************************************************
* 解题思路
* 标签:链表
* 本题的递归和非递归解法其实原理类似,都是更新每两个点的链表形态完成整个链表的调整
* 其中递归解法可以作为典型的递归解决思路进行讲解
* 递归写法要观察本级递归的解决过程,形成抽象模型,因为递归本质就是不断重复相同的事情。而不是去思考完整的调用栈,一级又一级,无从下手。如图所示,我们应该关注一级调用小单元的情况,也就是单个f(x)。
* 链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/hua-jie-suan-fa-24-liang-liang-jiao-huan-lian-biao/
* 其中我们应该关心的主要有三点:
* 返回值
* 调用单元做了什么
* 终止条件
* 在本题中:
* 返回值:交换完成的子链表
* 调用单元:设需要交换的两个点为 head 和 next,head 连接后面交换完成的子链表,next 连接 head,完成交换
* 终止条件:head 为空指针或者 next 为空指针,也就是当前无节点或者只有一个节点,无法进行交换
* *****************************************************************************************************************
* 代码
* 递归解法
* 链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/hua-jie-suan-fa-24-liang-liang-jiao-huan-lian-biao/
*/
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
return next;
}
/**
* 24. Swap Nodes in Pairs
* Given a linked list, swap every two adjacent nodes and return its head.
* 非递归解法
*/
public ListNode swapPairs2(ListNode head) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode temp = pre;
while (temp.next != null && temp.next.next != null) {
ListNode start = temp.next;
ListNode end = temp.next.next;
temp.next = end;
start.next = end.next;
end.next = start;
temp = start;
}
return pre.next;
}
/**
* 141. Linked List Cycle
* Given head, the head of a linked list, determine if the linked list has a cycle in it.
* There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to. Note that pos is not passed as a parameter.
* Return true if there is a cycle in the linked list. Otherwise, return false.
* *****************************************************************************************************************
* Example 1:
* Input: head = [3,2,0,-4], pos = 1
* Output: true
* Explanation: There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed).
*
* Example 2:
* Input: head = [1,2], pos = 0
* Output: true
* Explanation: There is a cycle in the linked list, where the tail connects to the 0th node.
*
* Example 3:
* Input: head = [1], pos = -1
* Output: false
* Explanation: There is no cycle in the linked list.
* *****************************************************************************************************************
* Use two pointers, walker and runner.
* walker moves step by step. runner moves two steps at time.
* if the Linked List has a cycle walker and runner will meet at some point.
*/
public boolean hasCycle(ListNode head) {
if (head == null) return false;
ListNode walker = head;
ListNode runner = head;
while (runner.next != null && runner.next.next != null) {
walker = walker.next;
runner = runner.next.next;
if (walker == runner) return true;
}
return false;
}
/**
* 141. 环形链表
* 给定一个链表,判断链表中是否有环。
* 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
* 如果链表中存在环,则返回 true 。 否则,返回 false 。
* 进阶:
* 你能用 O(1)(即,常量)内存解决此问题吗?
* https://leetcode-cn.com/problems/linked-list-cycle/solution/141-huan-xing-lian-biao-shuang-zhi-zhen-4uc9q/
* *****************************************************************************************************************
* The if(head==null) check can be further removed.
* star 18179 Reputation
*/
public boolean hasCycle2(ListNode head) {
ListNode walker = head;
ListNode runner = head;
while (runner != null && runner.next != null) {
walker = walker.next;
runner = runner.next.next;
if (walker == runner) return true;
}
return false;
}
/**
* 思路
* 可以使用快慢指针法, 分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
* 为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢?
* 首先第一点: fast指针一定先进入环中,如果fast 指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
* 那么来看一下,为什么fast指针和slow指针一定会相遇呢?
* 可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
* 会发现最终都是这种情况, 如下图:
* 链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/141-huan-xing-lian-biao-shuang-zhi-zhen-4uc9q/
* fast和slow各自再走一步, fast和slow就相遇了
* 这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
* 动画如下:
* 链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/141-huan-xing-lian-biao-shuang-zhi-zhen-4uc9q/
* C++代码如下
* class Solution {
* public:
* bool hasCycle(ListNode *head) {
* ListNode* fast = head;
* ListNode* slow = head;
* while(fast != NULL && fast->next != NULL) {
* slow = slow->next;
* fast = fast->next->next;
* // 快慢指针相遇,说明有环
* if (slow == fast) return true;
* }
* return false;
* }
* };
* 扩展
* 做完这道题目,可以在做做142.环形链表II,不仅仅要找环,还要找环的入口。
* 142.环形链表II题解:链表:环找到了,那入口呢?
* 我已经陆续将我的题解按照由浅入深的刷题顺序编排起来,整理成册,这份刷题顺序和题解在公众号里已经陪伴了上万录友。
* PDF中不仅有刷题大纲、刷题顺序,还有详细图解,每一本PDF发布之后都广受好评,这也是Carl花费大量时间写题解的动力。
* 链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/141-huan-xing-lian-biao-shuang-zhi-zhen-4uc9q/
*/
/**
* 142. Linked List Cycle II
* Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
* There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to. Note that pos is not passed as a parameter.
* Notice that you should not modify the linked list.
* *****************************************************************************************************************
* Example 1:
* Input: head = [3,2,0,-4], pos = 1
* Output: tail connects to node index 1
* Explanation: There is a cycle in the linked list, where tail connects to the second node.
*
* Example 2:
* Input: head = [1,2], pos = 0
* Output: tail connects to node index 0
* Explanation: There is a cycle in the linked list, where tail connects to the first node.
*
* Example 3:
* Input: head = [1], pos = -1
* Output: no cycle
* Explanation: There is no cycle in the linked list.
* *****************************************************************************************************************
* https://leetcode.com/problems/linked-list-cycle-ii/
*/
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
ListNode slow2 = head;
while (slow2 != slow) {
slow = slow.next;
slow2 = slow2.next;
}
return slow;
}
}
return null;
}
/**
* 142. 环形链表 II
* 给定一个链表,返回链表开始入环的第一个节点。如果链表无环,则返回null。
* 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
* 说明:不允许修改给定的链表。
* 进阶:
* 你是否可以使用 O(1) 空间解决此题?
* *****************************************************************************************************************
* Python:
* class Solution(object):
* def detectCycle(self, head):
* fast, slow = head, head
* while True:
* if not (fast and fast.next): return
* fast, slow = fast.next.next, slow.next
* if fast == slow: break
* fast = head
* while fast != slow:
* fast, slow = fast.next, slow.next
* return fast
*
* 链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/
* *****************************************************************************************************************
* 环形链表 II(双指针法,清晰图解)
* 解题思路:
* 这类链表题目一般都是使用双指针法解决的,例如寻找距离尾部第K个节点、寻找环入口、寻找公共尾部入口等。
* 算法流程:
* 1.双指针第一次相遇: 设两指针 fast,slow 指向链表头部 head,fast 每轮走 2 步,slow 每轮走 1 步;
* 第一种结果: fast 指针走过链表末端,说明链表无环,直接返回 null;
* TIPS: 若有环,两指针一定会相遇。因为每走 1 轮,fast 与 slow 的间距 +1,fast 终会追上 slow;
* 第二种结果: 当fast == slow时, 两指针在环中 第一次相遇 。下面分析此时fast 与 slow走过的 步数关系 :
* 设链表共有 a+b 个节点,其中 链表头部到链表入口 有 a 个节点(不计链表入口节点), 链表环 有 b 个节点(这里需要注意,a 和 b 是未知数,例如图解上链表 a=4 , b=5);设两指针分别走了 f,s 步,则有:
* fast 走的步数是slow步数的 2 倍,即 f = 2s;(解析: fast 每轮走 2 步)
* fast 比 slow多走了 n 个环的长度,即 f = s + nb;( 解析: 双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走 环的长度整数倍 );
* 以上两式相减得:f = 2nb,s = nb,即fast和slow 指针分别走了 2n,n 个 环的周长 (注意: n 是未知数,不同链表的情况不同)。
* 2.目前情况分析:
* 如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=a+nb(先走 a 步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点)。
* 而目前,slow 指针走过的步数为 nb 步。因此,我们只要想办法让 slow 再走 a 步停下来,就可以到环的入口。
* 但是我们不知道 a 的值,该怎么办?依然是使用双指针法。我们构建一个指针,此指针需要有以下性质:此指针和slow 一起向前走 a 步后,两者在入口节点重合。那么从哪里走到入口节点需要 aa 步?答案是链表头部head。
* 3.双指针第二次相遇:
* slow指针 位置不变 ,将fast指针重新 指向链表头部节点 ;slow和fast同时每轮向前走 1 步;
* TIPS:此时 f = 0,s = nb ;
* 当 fast 指针走到f = a 步时,slow 指针走到步s = a+nb ,此时 两指针重合,并同时指向链表环入口 。
* 返回slow指针指向的节点。
*
* 复杂度分析:
* 时间复杂度 O(N) :第二次相遇中,慢指针须走步数 a < a + b ;第一次相遇中,慢指针须走步数 a + b - x < a + b ,其中 x 为双指针重合点与环入口距离;因此总体为线性复杂度;
* 空间复杂度 O(1) :双指针使用常数大小的额外空间。
*
* 我觉得讲的很清楚了 get到的关键点是:
* 1.走a+nb步一定是在环入口
* 2.第一次相遇时慢指针已经走了nb步
* 链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/
*/
public ListNode detectCycle2(ListNode head) {
ListNode fast = head, slow = head;
while (true) {
if (fast == null || fast.next == null) return null;
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
fast = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
/**
* 环形链表 II(哈希表 / 快慢指针(再看)☀
* 方法一:哈希表(存储第一次出现的节点)
* 哈希表:从头开始遍历链表,若当前遍历的节点没有出现过,则加入到哈希表中;若第一次出现当前节点在哈希表中出现过,则该节点一定是环的开始,返回即可。
*
* 链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-iiha-xi-biao-kuai-ma-ki2r/
*/
public ListNode detectCycle3(ListNode head) {
Set<ListNode> set = new HashSet<>();
while (head != null) {
if (set.contains(head)) {
return head;
} else {
set.add(head);
head = head.next;
}
}
return null;
}
/**
* 方法二:快慢指针(看看看)
* 首先判断链表中是否存在环,利用快慢指针:fast 指针一次走两步,slow 指针一次走一步。
* 若链表中存在环,则 fast 指针和 slow 指针一定会相遇,即 fast == slow,此时令其中一个指针重新指向头节点 head 并且两个指针每次只走一步,则当两个指针再次相遇时,一定为环的开始;否则,返回 null 即可。
* 链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-iiha-xi-biao-kuai-ma-ki2r/
*/
public ListNode detectCycle4(ListNode head) {
if (head == null) return null;
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
fast = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
return null;
}
/**
* K 个一组翻转链表(字节跳动、猿辅导在半年内面试常考)
* 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
* k 是一个正整数,它的值小于或等于链表的长度。
* 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
* 进阶:
* 你可以设计一个只使用常数额外空间的算法来解决此问题吗?
* 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
* 示例 1:
* 输入:head = [1,2,3,4,5], k = 2
* 输出:[2,1,4,3,5]
* 示例 2:
* 输入:head = [1,2,3,4,5], k = 3
* 输出:[3,2,1,4,5]
* 示例 3:
* 输入:head = [1,2,3,4,5], k = 1
* 输出:[1,2,3,4,5]
* 示例 4:
* 输入:head = [1], k = 1
* 输出:[1]
* *****************************************************************************************************************
* 一图胜千言,根据图片看代码,马上就懂了
* 步骤分解:
* 1.链表分区为已翻转部分+待翻转部分+未翻转部分
* 2.每次翻转前,要确定翻转链表的范围,这个必须通过 k 此循环来确定
* 3.需记录翻转链表前驱和后继,方便翻转完成后把已翻转部分和未翻转部分连接起来
* 4.初始需要两个变量 pre 和 end,pre 代表待翻转链表的前驱,end 代表待翻转链表的末尾
* 5.经过k此循环,end 到达末尾,记录待翻转链表的后继 next = end.next
* 6.翻转链表,然后将三部分链表连接起来,然后重置 pre 和 end 指针,然后进入下一次循环
* 7.特殊情况,当翻转部分长度不足 k 时,在定位 end 完成后,end==null,已经到达末尾,说明题目已完成,直接返回即可
* 8.时间复杂度为 O(n*K) 最好的情况为 O(n) 最差的情况未 O(n^2)
* 9.空间复杂度为 O(1) 除了几个必须的节点指针外,我们并没有占用其他空间
* 链接:https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/tu-jie-kge-yi-zu-fan-zhuan-lian-biao-by-user7208t/
*/
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
ListNode end = dummy;
while (end.next != null) {
for (int i = 0; i < k && end != null; i++) end = end.next;
if (end == null) break;
ListNode start = pre.next;
ListNode next = end.next;
end.next = null;
pre.next = reverse(start);
start.next = next;
pre = start;
end = pre;
}
return dummy.next;
}
private ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = pre;
pre = curr;
curr = next;
}
return pre;
}
/**
* 25. Reverse Nodes in k-Group
* Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.
* k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes, in the end, should remain as it is.
* Follow up:
* Could you solve the problem in O(1) extra memory space?
* You may not alter the values in the list's nodes, only nodes itself may be changed.
* *****************************************************************************************************************
* Example 1:
* Input: head = [1,2,3,4,5], k = 2
* Output: [2,1,4,3,5]
*
* Example 2:
* Input: head = [1,2,3,4,5], k = 3
* Output: [3,2,1,4,5]
*
* Example 3:
* Input: head = [1,2,3,4,5], k = 1
* Output: [1,2,3,4,5]
*
* Example 4:
* Input: head = [1], k = 1
* Output: [1]
*/
public ListNode reverseKGroup2(ListNode head, int k) {
int n = 0;
for (ListNode i = head; i != null; n++, i = i.next) ;
ListNode dmy = new ListNode(0);
dmy.next = head;
for (ListNode prev = dmy, tail = head; n >= k; n -= k) {
for (int i = 1; i < k; i++) {
ListNode next = tail.next.next;
tail.next.next = prev.next;
prev.next = tail.next;
tail.next = next;
}
prev = tail;
tail = tail.next;
}
return dmy.next;
}
/**
* 1. 两数之和(近半年内,字节跳动在面试中考查此题达到 152 次)
* 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
* 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
* 你可以按任意顺序返回答案。
* *****************************************************************************************************************
* 示例 1:
* 输入:nums = [2,7,11,15], target = 9
* 输出:[0,1]
* 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
* 示例 2:
* 输入:nums = [3,2,4], target = 6
* 输出:[1,2]
* 示例 3:
* 输入:nums = [3,3], target = 6
* 输出:[0,1]
*/
public int[] twoSum(int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[0];
}
/**
* 方法二:哈希表
* 思路及算法
* 注意到方法一的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。
* 使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)。
* 这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。
* 链接:https://leetcode-cn.com/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-solution/
*/
public int[] twoSum2(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (hashtable.containsKey(target - nums[i])) {
return new int[]{hashtable.get(target - nums[i]), i};
}
hashtable.put(nums[i], i);
}
return new int[0];
}
/**
* 方法3:超哥
*/
public int[] twoSum3(int[] nums, int target) {
int[] a = new int[2];
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
a[0] = i;
a[1] = j;
return a;
}
}
}
return new int[0];
}
/**
* 11. 盛最多水的容器(腾讯、百度、字节跳动在近半年内面试常考)
* 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点(i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
* 说明:你不能倾斜容器。
* https://leetcode-cn.com/problems/container-with-most-water/solution/container-with-most-water-shuang-zhi-zhen-fa-yi-do/
*/
public int maxArea(int[] height) {
int i = 0, j = height.length - 1, res = 0;
while (i < j) {
res = height[i] < height[j] ?
Math.max(res, (j - i) * height[i++]) :
Math.max(res, (j - i) * height[j--]);
}
return res;
}
/**
* 双指针法的第二钟解法
*/
public int maxArea2(int[] height) {
int max = 0;
for (int i = 0, j = height.length - 1; i < j; ) {
int minHeight = height[i] < height[j] ? height[i++] : height[j--];
// int area = (j-i+1) * minHeight;
// max = Math.max(max , area);
max = Math.max(max, (j - i + 1) * minHeight);
}
return max;
}
/**
* 用一句话概括双指针解法的要点:指针每一次移动,都意味着排除掉了一个柱子。
* 如下图所示,在一开始,我们考虑相距最远的两个柱子所能容纳水的面积。水的宽度是两根柱子之间的距离 d = 8 ;水的高度取决于两根柱子之间较短的那个,即左边柱子的高度 h = 3 。水的面积就是 3 * 8 = 24 。
* 链接:https://leetcode-cn.com/problems/container-with-most-water/solution/on-shuang-zhi-zhen-jie-fa-li-jie-zheng-que-xing-tu/
* *****************************************************************************************************************
* 双指针法正确性证明
* https://leetcode-cn.com/problems/container-with-most-water/solution/shuang-zhi-zhen-fa-zheng-que-xing-zheng-ming-by-r3/
*/
public int maxArea3(int[] height) {
int res = 0;
int i = 0;
int j = height.length - 1;
while (i < j) {
int area = (j - i) * Math.min(height[i], height[j]);
res = Math.max(res, area);
if (height[i] < height[j]) {
i++;
} else {
j--;
}
}
return res;
}
/**
* 283. 移动零(华为、字节跳动在近半年内面试常考)
* 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
* *****************************************************************************************************************
* 示例:
*
* 输入: [0,1,0,3,12]
* 输出: [1,3,12,0,0]
*/
public void moveZeroes(int[] nums) {
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
nums[j] = nums[i];
if (i != j) {
nums[i] = 0;
}
System.out.println("nums[" + i + "]=" + nums[i] + " nums[" + j + "]=" + nums[j]);
j++;
}
}
// 输出移动零后的数组
for (int i = 0; i < nums.length; i++) {
System.out.print("nums[" + i + "] = " + nums[i] + " ");
}
System.out.println();
}
public void moveZeroes2(int[] nums) {
for (int lastNonZeroFoundAt = 0, cur = 0; cur < nums.length; cur++) {
if (nums[cur] != 0) {
swap(nums[lastNonZeroFoundAt++], nums[cur]);
}
}
}
private void swap(int num, int num1) {
}
/**
* 三种方式解决,都击败了100%的用户
* https://leetcode-cn.com/problems/move-zeroes/solution/san-chong-fang-shi-jie-jue-du-ji-bai-liao-100de-yo/
* *****************************************************************************************************************
* 1,把非0的往前挪
* 把非0的往前挪,挪完之后,后面的就都是0了,然后在用0覆盖后面的。这种是最容易理解也是最容易想到的,代码比较简单,这里就以示例为例画个图来看下
*/
public void moveZeroes3(int[] nums) {
if (nums == null || nums.length == 0)
return;
int index = 0;
//一次遍历,把非零的都往前挪
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0)
nums[index++] = nums[i];
}
//后面的都是0,
while (index < nums.length) {
nums[index++] = 0;
}
}
/**
* 2,参照双指针解决
* 这里可以参照双指针的思路解决,指针j是一直往后移动的,如果指向的值不等于0才对他进行操作。而i统计的是前面0的个数,我们可以把j-i看做另一个指针,它是指向前面第一个0的位置,然后我们让j指向的值和j-i指向的值交换
*/
public void moveZeroes4(int[] nums) {
int i = 0;//统计前面0的个数
for (int j = 0; j < nums.length; j++) {
if (nums[j] == 0) {//如果当前数字是0就不操作
i++;
} else if (i != 0) {
//否则,把当前数字放到最前面那个0的位置,然后再把
//当前位置设为0
nums[j - i] = nums[j];
nums[j] = 0;
}
}
}
public void moveZeroes5(int[] nums) {
int i = 0;
for (int j = 0; j < nums.length; j++) {
//只要不为0就往前挪
if (nums[j] != 0) {
//i指向的值和j指向的值交换
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
i++;
}
}
}
/**
* Shift non-zero values as far forward as possible
* Fill remaining space with zeros
*/
public void moveZeroes6(int[] nums) {
if (nums == null || nums.length == 0) return;
int insertPos = 0;
for (int num : nums) {
if (num != 0) nums[insertPos++] = num;
}
while (insertPos < nums.length) {
nums[insertPos++] = 0;
}
}
/**
* THE EASIEST but UNUSUAL snowball JAVA solution BEATS 100% (O(n)) + clear explanation
* The idea is that we go through the array and gather all zeros on our road.
* https://leetcode.com/problems/move-zeroes/discuss/172432/THE-EASIEST-but-UNUSUAL-snowball-JAVA-solution-BEATS-100-(O(n))-%2B-clear-explanation
*/
public void moveZeroes7(int[] nums) {
int snowBallSize = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == 0) {
snowBallSize++;
} else if (snowBallSize > 0) {
int t = nums[i];
nums[i] = 0;
nums[i - snowBallSize] = t;
}
}
}
/**
* 70. Climbing Stairs
* You are climbing a staircase. It takes n steps to reach the top.
* Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
* *****************************************************************************************************************
* Example 1:
* Input: n = 2
* Output: 2
* Explanation: There are two ways to climb to the top.
* 1. 1 step + 1 step
* 2. 2 steps
* Example 2:
* Input: n = 3
* Output: 3
* Explanation: There are three ways to climb to the top.
* 1. 1 step + 1 step + 1 step
* 2. 1 step + 2 steps
* 3. 2 steps + 1 step
* *****************************************************************************************************************
* The problem seems to be a dynamic programming one. Hint: the tag also suggests that!
* Here are the steps to get the solution incrementally.
*
* Base cases:
* if n <= 0, then the number of ways should be zero.
* if n == 1, then there is only way to climb the stair.
* if n == 2, then there are two ways to climb the stairs. One solution is one step by another; the other one is two steps at one time.
*
* The key intuition to solve the problem is that given a number of stairs n, if we know the number ways to get to the points [n-1] and [n-2] respectively, denoted as n1 and n2 , then the total ways to get to the point [n] is n1 + n2. Because from the [n-1] point, we can take one single step to reach [n]. And from the [n-2] point, we could take two steps to get there.
*
* The solutions calculated by the above approach are complete and non-redundant. The two solution sets (n1 and n2) cover all the possible cases on how the final step is taken. And there would be NO overlapping among the final solutions constructed from these two solution sets, because they differ in the final step.
*
* Now given the above intuition, one can construct an array where each node stores the solution for each number n. Or if we look at it closer, it is clear that this is basically a fibonacci number, with the starting numbers as 1 and 2, instead of 1 and 1.
*
* The implementation in Java as follows:
*/
public int climbStairs(int n) {
// base cases
if (n <= 0) return 0;
if (n == 1) return 1;
if (n == 2) return 2;
int one_step_before = 2;
int two_steps_before = 1;
int all_ways = 0;
for (int i = 2; i < n; i++) {
all_ways = one_step_before + two_steps_before;
two_steps_before = one_step_before;
one_step_before = all_ways;
}
return all_ways;
}
/**
* 3-4 short lines in every language
* Same simple algorithm written in every offered language. Variable a tells you the number of ways to reach the current step, and b tells you the number of ways to reach the next step. So for the situation one step further up, the old b becomes the new a, and the new b is the old a+b, since that new step can be reached by climbing 1 step from what b represented or 2 steps from what a represented.
*
* Ruby wins, and "the C languages" all look the same.
*
* Ruby (60 ms)
*
* def climb_stairs(n)
* a = b = 1
* n.times { a, b = b, a+b }
* a
* end
* C++ (0 ms)
*
* int climbStairs(int n) {
* int a = 1, b = 1;
* while (n--)
* a = (b += a) - a;
* return a;
* }
* Java (208 ms)
*
* public int climbStairs(int n) {
* int a = 1, b = 1;
* while (n-- > 0)
* a = (b += a) - a;
* return a;
* }
* Python (52 ms)
*
* def climbStairs(self, n):
* a = b = 1
* for _ in range(n):
* a, b = b, a + b
* return a
* C (0 ms)
*
* int climbStairs(int n) {
* int a = 1, b = 1;
* while (n--)
* a = (b += a) - a;
* return a;
* }
* C# (48 ms)
*
* public int ClimbStairs(int n) {
* int a = 1, b = 1;
* while (n-- > 0)
* a = (b += a) - a;
* return a;
* }
* Javascript (116 ms)
*
* var climbStairs = function(n) {
* a = b = 1
* while (n--)
* a = (b += a) - a
* return a
* };
*/
public int climbStairs2(int n) {
int a = 1, b = 1;
while (n-- > 0)
a = (b += a) - a;
return a;
}
public int climbStairs3(int n) {
if (n <= 2) {
return n;
}
int f1 = 1, f2 = 2, f3 = 3;
for (int i = 3; i <= n; i++) {
f3 = f1 + f2;
f1 = f2;
f2 = f3;
}
return f3;
}