本文最后更新于:2 个月前
                  
                
              
            
            
              
                
                
破冰 
漫画:什么是红黑树? - 掘金 (juejin.cn) 
 
脑力激荡 链表基础 基础知识(青铜挑战) 单链表基础及构造方法 链表的内部结构 
以下是我对单链表的理解:(2023/07/17午)  
链表,是用来存储数据的一种数据结构,其由若干个节点依次连接而成。
 
链表的构造 
链表的构造过程很简单: 
创建头节点,创建head指针指向头节点 依次创建每个节点,初始化其数据域,并令前驱节点的指针域指向该节点 链表创建完成,返回该链表的head指针  
下面给出具体的代码实现  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static  class  Node  {public  int  val;public  Node next;int  x) {null ;@Override public  String toString ()  {return  "Node{"  +"val="  + val +", next="  + next +'}' ;
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 private  static  Node initLinkedList (int [] array)  {Node  head  =  null , cur = null ;for  (int  i  =  0 ; i < array.length; i++) {Node  newNode  =  new  Node (array[i]);if  (i == 0 ) {else  {return  head;
1 2 3 4 5 6 public  static  void  main (String[] args)  {int [] a = {1 , 2 , 3 , 4 , 5 , 6 };Node  head  =  initLinkedList(a);
遍历链表 
打印链表:头指针依次向后移动,打印每个节点的数据域 获取链表长度:头指针依次向后移动,累加节点个数,打印链表长度 代码实现如下:(2023/07/17午)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  static  String toString (Node head)  {Node  current  =  head;StringBuilder  sb  =  new  StringBuilder ();while  (current != null ) {"\t" );return  sb.toString();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  static  int  getLength (Node head)  {int  length  =  0 ;Node  node  =  head;while  (node != null ) {return  length;
链表插入 
向链表中插入节点分以下三种情况: 
表头插入:创建新节点,新节点指针域指向原头节点;head指针指向新节点  
在表中插入:遍历到插入位置的前驱节点,依次为新节点分配后继节点和前驱节点  
表尾插入:可视为 2 的特殊情况,新节点的后继节点为 NULL  
代码设计如下:(2023/07/17午)  
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 public  static  Node insertNode (Node head, Node nodeInsert, int  position)  {if  (head == null ) {return  nodeInsert;int  size  =  getLength(head);if  (position > size + 1  || position < 1 ) {"位置参数越界" );return  head;if  (position == 1 ) {return  head;Node  pNode  =  head;int  count  =  1 ;while  (count < position - 1 ) {return  head;
链表删除 
删除链表节点同样分三种情况: 
删除表头元素:head指针指向要删除节点的后继节点  
删除表中元素:拿到要删除节点的前驱节点的指针域,指向要删除节点的后继节点  
删除表尾元素:可视为 2 的特殊情况,要删除节点的后继节点为 NULL  
代码设计如下:  
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 public  static  Node deleteNode (Node head, int  position)  {if  (head == null ) {return  null ;int  size  =  getLength(head);if  (position > size || position <= 0 ) {"输入的参数有误" );return  head;if  (position == 1 ) {return  head;Node  preNode  =  head;int  count  =  1 ;while  (count < position - 1 ) {Node  curNode  =  preNode.next;return  head;
双向链表设计 双向链表的内部结构 
以下是我对双向链表的理解(2023/07/17午)  
1 双向链表与单链表的最大区别,就是每个节点增加了一个前驱指针域,指向前驱节点
链表的构造 
遍历链表 
head指针依次向后移动,遍历每个节点,输出数据域的值:  
链表插入 
向链表中插入节点分以下三种情况: 
表头插入:新建新节点,原头节点作新节点的后继节点,新节点作为原头结点的前驱节点,head指针指向新节点  
表尾插入:新建新节点,原尾节点作新节点的前驱节点,新节点作为头结点的后继节点,tail指针指向新节点  
 
链表删除 
删除双向链表中的节点分以下三种情况: 
表头删除:head指针指向原头节点的后继节点,并将该后继节点的前驱指针置空(2023/07/17午)  
表尾删除:tail指针指向原尾节点的前驱节点,并将该前驱节点的后继指针置空  
 
实战训练(白银挑战) 两个链表第一个公共子节点 
前情提要:什么情况下,两条链表存在公共子节点呢?如下图所示:(2023/07/19午)  
显而易见,c1、c2、c3均为两链表的公共子节点,且c1是两链表的第一个公共子节点 我们先废话少说,给出四种解题思路: 
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 public  static  ListNode findFirstCommonNodeByMap (ListNode pHead1, ListNode pHead2)  {if  (pHead1 == null  || pHead2 == null ) {return  null ;ListNode  current1  =  pHead1;ListNode  current2  =  pHead2;new  HashMap <ListNode, Integer>();while  (current1 != null ) {null );while  (current2 != null ) {if  (hashMap.containsKey(current2))return  current2;return  null ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  static  ListNode findFirstCommonNodeBySet (ListNode headA, ListNode headB)  {new  HashSet <>();while  (headA != null ) {while  (headB != null ) {if  (set.contains(headB))return  headB;return  null ;
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 public  static  ListNode findFirstCommonNodeByStack (ListNode headA, ListNode headB)  {new  Stack <>();new  Stack <>();while  (headA != null ) {while  (headB != null ) {ListNode  preNode  =  null ;while  (stackB.size() > 0  && stackA.size() > 0 ) {if  (stackA.peek() == stackB.peek()) {else  {break ;return  preNode;
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  public  static  ListNode findFirstCommonNodeByCombine (ListNode pHead1, ListNode pHead2)  {if  (pHead1 == null  || pHead2 == null ) {return  null ;ListNode  p1  =  pHead1;ListNode  p2  =  pHead2;while  (p1 != p2) {if  (p1 != p2) {if  (p1 == null ) {if  (p2 == null ) {return  p1;
1 2 3 if  (p1 == null ) {
考虑这个问题:当前链表遍历结束后,什么情况下允许切换遍历另一条链表呢? 答案包括两种情况:未找到公共节点 / 第一次切换遍历链表结束 未找到公共节点很好理解,只有切换遍历另一条链表,才能判断是否有公共节点 第一次切换遍历链表结束,此时p1、p2指针均为null,说明两链表就没有公共节点,我们就结束链表的遍历 所以结束两链表的遍历(p1 == p2)有两种情况:第一个公共节点已找到/不存在公共节点 差和双指针  
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 public  static  ListNode findFirstCommonNodeBySub (ListNode pHead1, ListNode pHead2)  {if  (pHead1 == null  || pHead2 == null ) {return  null ;ListNode  current1  =  pHead1;ListNode  current2  =  pHead2;int  l1  =  0 , l2 = 0 ;while  (current1 != null ) {while  (current2 != null ) {int  sub  =  l1 > l2 ? l1 - l2 : l2 - l1;if  (l1 > l2) {int  a  =  0 ;while  (a < sub) {if  (l1 < l2) {int  a  =  0 ;while  (a < sub) {while  (current2 != current1) {return  current1;
上面代码里的注释,已经把解题思路解释的很清晰了 基于我个人的理解,下面讲解一下这些方法的共同点,也就是解题思路的形成过程:  
1 2 3 4 5 6 7 8 9 10 11 12 13 我们的目标是:查出两条链表的第一个公共节点/倒序遍历链表,选出第一组公共节点/ 最后一组公共节点,就找到了两链表的第一个公共节点
这就是查找两链表的第一个公共节点的解题思路了,希望对你有帮助  
回文链表的判断 
给出一个链表,判断其是否为回文链表,那什么是回文链表? 以下即为一条回文链表:  
即对回文链表正序遍历和倒序遍历,得到的结果是一样的 这种题解法很多,我们列举常见的、简单的且容易理解的解法: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  static  boolean  isPalindromeByAllStack (ListNode head)  {ListNode  temp  =  head;new  Stack <>();while  (temp != null ) {while  (head != null ) {if  (head.val != stack.pop()) {return  false ;return  true ;
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 public  static  boolean  isPalindromeByHalfStack (ListNode head)  {if  (head == null )return  true ;ListNode  temp  =  head;new  Stack <>();int  len  =  0 ;while  (temp != null ) {1 ;while  (len-- >= 0 ) {if  (head.val != stack.pop())return  false ;return  true ;
倒序链表法,代码如下: 根据原链表构造一条倒序链表,遍历这两条链表  
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 public  static  boolean  isPalindromeByReverseList (ListNode head)  {ListNode  newHead  =  head, temp = head;while  (temp != null ) {ListNode  node  =  new  ListNode (temp.val);while  (newHead != null  && head != null ) {if  (head.val != newHead.val)return  false ;return  true ;
此外还有双指针法(之后双指针专题练习结束后回来补充)、递归法(不推荐掌握,容易绕晕)  
 
合并两条有序链表 
常见的解法就是构造第三条链表,然后依次遍历两条有序链表,比较各节点大小,依次连接到新链表中,整个过程如下图所示:  
由于两条链表长度不一定相同,可能出现一条链表遍历完,另一条链表还没有的情况,这其实是一个优化点 具体代码如下:  
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 public  static  ListNode mergeTwoLists (ListNode list1, ListNode list2)  {ListNode  newHead  =  new  ListNode (-1 );ListNode  res  =  newHead;while  (list1 != null  || list2 != null ) {if  (list1 != null  && list2 != null ) {if  (list1.val < list2.val) {else  if  (list1.val > list2.val) {else  { else  if  (list1 != null  && list2 == null ) {else  if  (list1 == null  && list2 != null ) {return  res.next;
上面的解法当中,我们把两条链表是否都为空/只有一条为空放在了一个循环下,这次我们把它拆开来:  
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 public  static  ListNode mergeTwoLists2 (ListNode list1, ListNode list2)  {ListNode  newHead  =  new  ListNode (-1 );ListNode  res  =  newHead;while  (list1 != null  && list2 != null ) {if  (list1.val < list2.val) {else  if  (list1.val > list2.val) {else  { while  (list1 != null ) {while  (list2 != null ) {return  res.next;
一条链表合合并完成后,直接拼接剩余的另一条链表即可(2023/10/09晚)
 
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 public  static  ListNode mergeTwoListsMoreSimple (ListNode l1, ListNode l2)  {ListNode  prehead  =  new  ListNode (-1 );ListNode  prev  =  prehead;while  (l1 != null  && l2 != null ) {if  (l1.val <= l2.val) {else  {null  ? l2 : l1;return  prehead.next;
合并K个链表 
每次两条链表合成完毕后,返回新链表的头节点,再用这条列表,继续与剩余列表合成 (2023/10/09晚)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 public  static  ListNode mergeKLists (ListNode[] lists)  {ListNode  res  =  null ;for  (ListNode list : lists) {return  res;
简单的合并链表 
随便给你两条链表,你会怎么连接这两条链表?(比如:将链表b连接到链表a后面) 正确的思路只有一个,那就是拿到链表a的尾节点,拿到链表b的头节点,作:a.next = b,连接完成 举个例子:将链表a的[a,b]区间删掉,把链表b连接进去,代码如下:  
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 public  static  ListNode mergeInBetween (ListNode listA, int  a, int  b, ListNode listB)  {ListNode  preA  =  listA;ListNode  postA  =  listA;ListNode  postB  =  listB;int  i  =  0 , j = 0 ;while  (postA != null  && preA != null  && j < b) {if  (i < a - 1 ) {if  (j != b) {while  (postB.next != null ) {return  preA;
双指针 
寻找中间节点 
快慢指针均指向头节点 快指针一次跳俩步,慢指针一次跳一步,两指针同时移动 当快指针指向节点为空(偶数个节点)或快指针指向节点的后继节点为空(奇数个节点)时,两指针停止移动 此时,慢指针指向链表中间节点  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  static  ListNode middleNode (ListNode head)  {ListNode  slow  =  head, fast = head;while  (fast != null  && fast.next != null ) {return  slow;
寻找倒数第K个节点 
快慢指针均指向头节点 快指针跳到第K+1个节点,此时慢指针与快指针相距K个节点 快慢指针同时移动,当快指针指向链表末端(即空节点)时,两指针停止移动 此时,慢指针指向链表的倒数第K个节点  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  static  ListNode getKthFromEnd (ListNode head, int  k)  {ListNode  fast  =  head;ListNode  slow  =  head;while  (fast != null  && k > 0 ) {while  (fast != null ) {return  slow;
寻找倒数第 K 个节点还有两种方法:遍历链表法和压栈法 遍历链表:先遍历一遍链表,得到链表长度 L,再遍历一遍链表,取第 L-K+1个节点 压栈:将链表压入栈,再出栈,取第 K 个出栈的节点 这两种方法很好理解,具体代码择日实现(2023/07/24晚)  
旋转链表 
常见的情景题:把链表的每个节点,都向右移动K个位置 这个是有两种思路的:反转链表、转化为寻找倒数第 K-1 个节点 反转链表暂且不表,这里可以看看第二种方法:转化为寻找倒数第 K-1 个节点 把链表的每个节点,都向右移动K个位置 => 把链表的后 K 个节点,都旋转成前 K 个节点 那就把问题转换成了:转化为寻找倒数第 K-1 个节点: 此时慢指针指向了倒数第 K-1 个节点,快指针指向了链表的尾节点 倒数第 K 个节点为头节点(断掉慢指针指向节点的后继,快指针指向原头节点)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public  static  ListNode rotateRight (ListNode head, int  k)  {if  (head == null  || k == 0 ) {return  head;ListNode  temp  =  head;ListNode  fast  =  head;ListNode  slow  =  head;int  len  =  0 ;while  (head != null ) {if  (k % len == 0 ) {return  temp;while  ((k % len) > 0 ) {while  (fast.next != null ) {ListNode  res  =  slow.next;null ;return  res;
删除特定节点 
这类型题目本身不难,因为我们之前学过删除节点,但删除节点有两种情况:删除头节点和删除尾节点 这两种情况的处理方式是不一样的,所以我们提供一个全新的思路: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  static  ListNode removeElements (ListNode head, int  val)  {ListNode  dummyHead  =  new  ListNode (0 );ListNode  temp  =  dummyHead;while  (temp.next != null ) {if  (temp.next.val == val) {else  {return  dummyHead.next;
删除倒数第K个节点 
我们之前学过如何查找倒数第K个节点,它们本质上是一样的,我们还是提供三种思路: 
具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  static  ListNode removeNthFromEndByLength (ListNode head, int  n)  {ListNode  dummy  =  new  ListNode (0 );int  length  =  getLength(head);ListNode  cur  =  dummy;for  (int  i  =  1 ; i < length - n + 1 ; ++i) {return  dummy.next;
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 public  static  ListNode removeNthFromEndByStack (ListNode head, int  n)  {ListNode  dummy  =  new  ListNode (0 );new  LinkedList <ListNode>();ListNode  cur  =  dummy;while  (cur != null ) {for  (int  i  =  0 ; i < n; ++i) {ListNode  prev  =  stack.peek();assert  prev != null ;return  dummy.next;
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 public  static  ListNode removeNthFromEndByTwoPoints (ListNode head, int  n)  {ListNode  dummy  =  new  ListNode (0 );ListNode  first  =  head;ListNode  second  =  dummy;for  (int  i  =  0 ; i < n; ++i) {while  (first != null ) {assert  second.next != null ;return  dummy.next;
删除重复节点 
删除重复节点当然有两种情况了:仅留一个或者删除全部,废话少说,直接上代码: 
重复元素保留一个,在这种情况下,被删除的节点肯定不可能是头节点 (头节点删除时的处理是不一样的)2023/11/03晚  
所以此处不用考虑删除节点是头节点还是中间节点了 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  static  ListNode deleteDuplicate (ListNode head)  {if  (head == null ) {return  head;ListNode  cur  =  head;while  (cur.next != null ) {if  (cur.val == cur.next.val) {else  {return  head;
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 public  static  ListNode deleteDuplicates (ListNode head)  {if  (head == null ) {return  null ;ListNode  dummy  =  new  ListNode (0 );ListNode  cur  =  dummy;while  (cur.next != null  && cur.next.next != null ) {if  (cur.next.val == cur.next.next.val) {int  x  =  cur.next.val;while  (cur.next != null  && cur.next.val == x) {else  {return  dummy.next;
通关(过关挑战) 
题目内容: 算法训练营开课了,小伙伴们踊跃报名,请用链表来帮忙统计学员信息: 学院方向不同:Java、Python、C++,仅有一条链表,其前中后三部分,分别是:Java、Python、C++的同学 每种语言都会不断有学生进来,每次都要将对应的同学插入到对应的段的末尾 具体代码如下:(2023/07/27早)  
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 public  class  InsertStudent  {public  static  void  main (String[] args)  {ListNode  node1  =  new  ListNode ("Node 1" , "Java" );ListNode  node2  =  new  ListNode ("Node 2" , "Python" );ListNode  node3  =  new  ListNode ("Node 3" , "C++" );new  ListNode [3 ];0 ] = node1;1 ] = node2;2 ] = node3;ListNode  head  =  initLinkList(nodes);ListNode  node4  =  new  ListNode ("Node 4" , "Java" );ListNode  node5  =  new  ListNode ("Node 5" , "C++" );ListNode  node6  =  new  ListNode ("Node 6" , "Python" );ListNode  node8  =  new  ListNode ("Node 8" , "C++" );ListNode  node9  =  new  ListNode ("Node 9" , "Python" );ListNode  node7  =  new  ListNode ("Node 7" , "Java" );public  static  void  insertStudentByLanguage (ListNode node, ListNode head)  {ListNode  cur  =  head;String  language  =  node.language;switch  (language) {case  "Java" :while  (!cur.next.language.equals("Python" )) {break ;case  "Python" :while  (!cur.next.language.equals("C++" )) {break ;case  "C++" :while  (cur.next != null ) {break ;default :break ;public  static  void  printLinkList (ListNode head)  {ListNode  temp  =  head;while  (temp != null ) {"--> " );public  static  ListNode initLinkList (ListNode[] array)  {int  i  =  0 ;ListNode  head  =  null , cur = null ;while  (i < array.length) {ListNode  newNode  =  new  ListNode (array[i].name, array[i].language);if  (head == null ) {else  {return  head;static  class  ListNode  {public  String name;public  String language;public  ListNode next;public  ListNode (String name, String language)  {this .name = name;this .language = language;@Override public  String toString ()  {return  "ListNode{"  +"name='"  + name + '\''  +", language='"  + language + '\''  +'}' ;
反转链表 基础知识 + 基本操作(青铜挑战) 
常见的链表反转的方法:虚拟头节点、直接反转、递归
虚拟头节点,具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  static  ListNode reverseListByDummyNotCreate (ListNode head)  {ListNode  ans  =  new  ListNode (-1 );ListNode  cur  =  head;while  (cur != null ) {ListNode  next  =  cur.next;return  ans.next;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  static  ListNode reverseListSimple (ListNode head)  {ListNode  prev  =  null ;ListNode  curr  =  head;while  (curr != null ) {ListNode  next  =  curr.next;return  prev;
拓展训练(白银挑战) 区间反转链表 
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 public  static  ListNode reverseBetween2 (ListNode head, int  left, int  right)  {ListNode  dummyNode  =  new  ListNode (-1 );ListNode  pre  =  dummyNode;for  (int  i  =  0 ; i < left - 1 ; i++) {ListNode  cur  =  pre.next;for  (int  i  =  0 ; i < right - left; i++) {return  dummyNode.next;
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 public  static  ListNode reverseBetween (ListNode head, int  left, int  right)  {ListNode  dummyNode  =  new  ListNode (-1 );ListNode  pre  =  dummyNode;for  (int  i  =  0 ; i < left - 1 ; i++) {ListNode  rightNode  =  pre;for  (int  i  =  0 ; i < right - left + 1 ; i++) {ListNode  leftNode  =  pre.next;ListNode  succ  =  rightNode.next;null ;null ;return  dummyNode.next;
两两交换链表中的节点 
这就是区间反转链表的拓展了,无非是区间长度固定为2,一次性反转多个区间 具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public  static  ListNode swapPairs (ListNode head)  {ListNode  dummyHead  =  new  ListNode (0 );ListNode  cur  =  dummyHead;while  (cur.next != null  && cur.next.next != null ) {ListNode  node1  =  cur.next;ListNode  node2  =  cur.next.next;return  dummyHead.next;
链表相加 
1 2 3 4 输入:1  -> 3  -> 6       输出:4  -> 1  -> 3 2  -> 7  -> 7 136  + 277  = 413 
即将链表的数据域相连,当作数字进行相加操作,从低位开始相加,涉及到进位,有两种实现思路: 
具体代码如下:  
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 public  static  ListNode addInListByStack (ListNode head1, ListNode head2)  {new  Stack <ListNode>();new  Stack <ListNode>();while  (head1 != null ) {while  (head2 != null ) {ListNode  dummy  =  new  ListNode (-1 );int  carry  =  0 ;while  (!st1.empty() || !st2.empty() || carry != 0 ) {ListNode  a  =  new  ListNode (0 );ListNode  b  =  new  ListNode (0 );if  (!st1.empty()) {if  (!st2.empty()) {int  get_sum  =  a.val + b.val + carry;int  ans  =  get_sum % 10 ;10 ;ListNode  cur  =  new  ListNode (ans);return  dummy.next;
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 public  static  ListNode addInListByReverse (ListNode head1, ListNode head2)  {ListNode  head  =  new  ListNode (-1 );ListNode  cur  =  head;int  carry  =  0 ;while  (head1 != null  || head2 != null ) {int  val  =  carry;if  (head1 != null ) {if  (head2 != null ) {new  ListNode (val % 10 );10 ;if  (carry > 0 ) {new  ListNode (carry);return  reverse(head.next);
奇怪的力扣试题(链表相加) 
妈的,今晚再提交一道题就完成任务了,我还选择了这道题,原以为会很顺利(2023/11/10晚)  
 
🍖 力扣原题链接:2. 两数相加 - 力扣(LeetCode) 
 
逼着我认真分析了一遍构造链表的几种方法,捋了捋链表节点插入的各种场景(直接插入 / 虚拟头节点 ,前插 / 后插 ) 
没有解决问题,最后发现题目要求是这样的: 
 
妈的,什么破题,今晚不搞这道题了 🍟(2023/11/10晚)  
 
单链表加一 
1 2 输入:1  -> 3  -> 6 1  -> 3  -> 7 
本质上就是链表相加,只不过另外一条链表只有一个节点,从低位开始加一,涉及到进位,同样有两种实现思路: 
这里仅展示压栈法,具体代码如下:(2023/07/31晚)  
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 public  static  ListNode plusOne (ListNode head)  {new  Stack ();while  (head != null ) {int  carry  =  0 ;int  adder  =  1 ;ListNode  dummy  =  new  ListNode (0 );while  (!st.empty() || adder != 0  || carry > 0 ) {int  digit  =  st.pop();int  sum  =  digit + adder + carry;10  ? 1  : 0 ;10  ? sum - 10  : sum;ListNode  cur  =  new  ListNode (sum);0 ;return  dummy.next;
通关(过关挑战) 数组 基础知识(青铜挑战) 
有关线性表的内容,这里不过多介绍,详情可看讲义(2023/08/09早) 数组的基本操作:
初始化数组:  
1 2 3 4 5 6 7 8 public  static  void  initArray ()  {int [] arr = new  int [10 ];int [] arr2 = new  int []{1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };int [] arr3 = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  static  int  findByElement (int [] arr, int  size, int  element)  {for  (int  i  =  0 ; i < size; i++) {if  (arr[i] == element)return  element;return  -1 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public  static  int  addByElementSequence (int [] arr, int  size, int  element)  {if  (size >= arr.length)return  -1 ;int  index  =  size;for  (int  i  =  0 ; i < size; i++) {if  (element < arr[i]) {break ;for  (int  j  =  size; j > index; j--) {1 ];return  0 ;
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 public  static  int  removeByElement (int [] arr, int  size, int  key)  {int  index  =  -1 ;for  (int  i  =  0 ; i < size; i++) {if  (arr[i] == key) {break ;if  (index != -1 ) {for  (int  i  =  index + 1 ; i < size; i++) {1 ] = arr[i];return  size;
单调数组问题 
数组单调有两种情况:单调递增、单调递减
如何判断数组是否为单调数组呢?(2023/08/11早)  
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 public  static  boolean  isMonotonic (int [] nums)  {return  isSorted(nums, true ) || isSorted(nums, false );public  static  boolean  isSorted (int [] nums, boolean  increasing)  {int  n  =  nums.length;for  (int  i  =  0 ; i < n - 1 ; ++i) {if  (increasing) {if  (nums[i] > nums[i + 1 ]) {return  false ;else  {if  (nums[i] < nums[i + 1 ]) {return  false ;return  true ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  static  boolean  isMonotonic_2 (int [] nums)  {boolean  inc  =  true , dec = true ;int  n  =  nums.length;for  (int  i  =  0 ; i < n - 1 ; ++i) {if  (nums[i] > nums[i + 1 ]) {false ;if  (nums[i] < nums[i + 1 ]) {false ;return  inc || dec;
简单的二分查找 
在单调数组中查找目标元素,最便捷的方法就是二分查找:(2023/08/11早)  
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 public  static  int  search (int [] nums, int  target)  {  int  n  =  nums.length;  if  (n == 0 ) {  return  -1 ; int  left  =  0 , right = n - 1 ;   while  (left <= right) {  int  mid  =  left + (right - left) / 2 ; if  (nums[mid] == target) {  return  mid; else  if  (nums[mid] < target) {  1 ; else  {  1 ; return  -1 ; 
数组合并 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  static  void  merge1 (int [] nums1, int  nums1_len, int [] nums2, int  nums2_len)  {for  (int  i  =  0 ; i < nums2_len; ++i) {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  static  void  merge2 (int [] nums1, int  nums1_len, int [] nums2, int  nums2_len)  {int  i  =  nums1_len + nums2_len - 1 ;int  len1  =  nums1_len - 1 , len2 = nums2_len - 1 ;while  (len1 >= 0  && len2 >= 0 ) {if  (nums1[len1] <= nums2[len2])else  if  (nums1[len1] > nums2[len2])while  (len2 != -1 ) nums1[i--] = nums2[len2--];while  (len1 != -1 ) nums1[i--] = nums1[len1--];
实战训练(白银挑战) 双指针 
我们使用双指针,能够很清晰、方便地解决问题,接下来我们使用双指针来解决一些典型问题: 
 
删除重复元素 
给定一个数组,要求删除其中所有重复元素,重复元素保留一个 对于这种问题,我们使用快慢双指针来解决:
具体代码实现如下:(2023/08/13午)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  static  int  removeDuplicates (int [] nums)  {int  slow  =  0 ;for  (int  fast  =  1 ; fast < nums.length; fast++) {if (nums[fast] != nums[slow]){return  slow + 1 ;
删除指定元素 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  static  int  removeElement (int [] nums, int  val)  {int  slow  =  0 ;for  (int  fast  =  0 ; fast < nums.length; fast++) {if  (nums[fast] != val) {return  slow;
这里简单捋一捋对撞双指针的思路:(2023/08/13午) 
左指针在左,右指针在右,左指针向前移动 
如果左指针未找到指定元素,则左指针继续向前移动 
如果左指针找到了指定元素,就拿右指针所指元素覆盖,左指针向前移动,右指针向后移动 
循环执行,直到两指针碰撞 
即:左指针左侧维护了所有正确的元素值 
 
使用对撞双指针解决思路:(2023/08/15晚) 
设左右双指针
左指针向前移动,遇到指定元素就停下来
这时,将两指针所指元素交换,右指针向前一步
重复以上操作,直至两指针相撞
 
具体代码实现如下:(2023/08/13午)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  static  int  removeElement3 (int [] nums, int  val)  {int  right  =  nums.length - 1 ;for  (int  left  =  0 ; left < right; ) {if  (nums[left] == val) {return  right;
元素奇偶移动专题 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  static  int [] sortArrayByParity(int [] A) {int  left  =  0 , right = A.length - 1 ;while  (left < right) {if  (A[left] % 2  > A[right] % 2 ) {int  tmp  =  A[left];if  (A[left] % 2  == 0 ) left++;if  (A[right] % 2  == 1 ) right--;return  A;
数组轮转 
情景:
给你一个数组,将数组的每个元素,向右轮转k个位置,得到新数组,即:  
1 2 输入: nums = [1,2,3,4,5,6,7] , k = 3 [5,6,7,1,2,3,4] 
看起来好像挺简单,但怎么实现没思路,这里介绍一种简单的方法:两轮反转(2023/08/25早) 
首先,对整个数组实行翻转 
然后,从k处将数组分隔,分别翻转分隔后的两个数组 
这样就得到了最终结果 
 
具体代码如下:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  static  void  rotate (int [] nums, int  k)  {0 , nums.length - 1 );0 , k - 1 );1 );public  static  void  reverse (int [] nums, int  start, int  end)  {while  (start < end) {int  temp  =  nums[start];1 ;1 ;
数组的区间专题 
1 2 3 示例:0 ,1 ,2 ,4 ,5 ,7 ]"0->2" ,"4->5" ,"7" ]
实现思路:
定义快慢指针 
快指针向前移动 
快指针找到了不连续递增的数,慢指针指向该数,快指针继续移动 
快指针到达数组边缘,结束 
 
 
具体代码如下: 
 
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 public  static  List<String> summaryRanges (int [] nums)  {new  ArrayList <>();int  slow  =  0 ;for  (int  fast  =  0 ; fast < nums.length; fast++) {if  (fast + 1  == nums.length || nums[fast] + 1  != nums[fast + 1 ]) {StringBuilder  sb  =  new  StringBuilder ();if  (slow != fast) {"->" ).append(nums[fast]);1 ;return  res;
字符串替换空格问题 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  static  String replaceSpace1 (StringBuffer str)  {String  res  =  "" ;for  (int  i  =  0 ; i < str.length(); i++) {char  c  =  str.charAt(i);if  (c == ' ' )"%20" ;else return  res;
我们创建了一个新的字符串来完成👆,那么如果我们在原字符串上,扩充所需空间,不必开辟新的空间就能完成呢? 
思路如下:
扩充字符串长度 
定义快慢指针,快指针指向原长度末,慢指针指向现长度末 
快指针向前移动,检查每个字符 
如果该字符是为非空格字符,则慢指针维护该字符,两指针向前移动 
如果是空格,慢指针维护”20%”,两指针向前移动 
快指针到达数组边缘,结束 
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public  static  String replaceSpace2 (StringBuffer str)  {if  (str == null )return  null ;int  numOfblank  =  0 ;int  len  =  str.length();for  (int  i  =  0 ; i < len; i++) {  if  (str.charAt(i) == ' ' )2  * numOfblank); int  fast  =  len - 1 ;  int  slow  =  (len + 2  * numOfblank) - 1 ;while  (fast >= 0  && slow > fast) {char  c  =  str.charAt(fast);if  (c == ' ' ) {'0' );'2' );'%' );else  {return  str.toString();
栈 基础知识(青铜挑战) 
认识栈这个数据结构:先进先出
常用的栈操作有:
push(E e):入栈 
peek():弹出栈顶元素 
pop():出栈 
empty():判断栈是否为空 
 
我们尝试手动创建一个栈数据结构,并实现它的基础功能:  
基于数组实现栈 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 class  Mystack2 <T> {private  Object[] stack;private  int  top;public  Mystack2 (int  size)  {new  Object [size];public  void  push (T t)  {this .expandCapacity(top + 1 );this .stack[top] = t;public  T peek ()  {T  t  =  null ;if  (!this .empty())this .stack[top - 1 ];return  t;public  T pop ()  {T  t  =  this .peek();this .stack[top - 1 ] = null ;return  t;public  boolean  empty ()  {return  this .top <= 0 ;public  void  expandCapacity (int  size)  {int  capacity  =  this .stack.length;if  (size > capacity) {3  / 2  + 1 ;this .stack = Arrays.copyOf(stack, capacity);public  static  void  main (String[] args)  {new  Mystack2 <>(10 );"java" );"is" );"beautiful" );"language" );
基于链表实现栈 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class  ListStack2 <T> {private  Node head;public  void  push (T t)  {if  (t == null ) {throw  new  NullPointerException ("参数不能为空" );if  (head == null ) {new  Node (t);else  {Node  node  =  new  Node (t);public  T peek ()  {return  head.t;public  T pop ()  {return  head.t;public  boolean  empty ()  {return  head == null ;class  Node  {private  T t;private  Node next;public  Node (T t)  {this .t = t;
实战训练(白银挑战) 括号匹配问题 
情景:
给定一个字符串,仅包含”(“、”)”、”[“、”]”等字符,要求如下: 如果该字符中的左括号与右括号连续且成对出现,则说明该字符串有效,如下:  
实现一个算法,判断该字符串是否有效
实现思路:
构造一个HashMap,将左右括号以键值对形式存放进去 
构造一个栈Stack 
依次遍历字符串中的字符s,如果存在Map中的key值与s相等,则入栈 
如果该字符s不是key值,即不是左括号,则Stack出栈,计算value值是否与s相等 
不相等则说明括号不连续或不成对 ,该字符串是无效的 
 
这里利用的是栈的特性:先进先出,用来判断括号是否连续且成对(2023/08/26午) 具体代码实现:
 
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 static  boolean  isValid (String s)  {if  (s.length() <= 1 ) {return  false ;new  HashMap <>();'(' , ')' );'{' , '}' );'[' , ']' );new  Stack <>();for  (int  i  =  0 ; i < s.length(); i++) {char  item  =  s.charAt(i);if  (smap.containsKey(item)) {else  {if  (stack.isEmpty()) {return  false ;Character  left  =  stack.pop();char  rightChar  =  smap.get(left);if  (rightChar != item) {return  false ;return  stack.isEmpty();
最小栈 
实现快速获取栈中的最小元素
我们设计这样一个数据结构:
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 public  class  MinStack  {  private  Stack<Integer> stack;  private  Stack<Integer> minStack;  public  MinStack ()  {  new  Stack <>();  new  Stack <>();  public  void  push (int  x)  {  if  (minStack.isEmpty() || x <= minStack.peek()) {  public  void  pop ()  {  if  (stack.peek().equals(minStack.peek())) {  public  int  top ()  {  return  stack.peek();  public  int  getMin ()  {  return  minStack.peek();  
 
最大栈 
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 public  class  MaxStack  {  private  Stack<Integer> stack;  private  Stack<Integer> maxStack;  public  MaxStack ()  {  new  Stack <>();  new  Stack <>();  public  void  push (int  x)  {  if  (maxStack.isEmpty() || x >= maxStack.peek()) {  public  void  pop ()  {  if  (stack.peek().equals(maxStack.peek())) {  public  int  top ()  {  return  stack.peek();  public  int  getMax ()  {  return  maxStack.peek();  
解释:
对于实现最大栈的pop()方法: 
当你从主栈中弹出一个元素,你会检查这个元素是否是辅助栈的栈顶元素 。如果是,那么辅助栈的栈顶元素会被弹出。无论这个元素是否是辅助栈的栈顶元素,主栈的元素都会被弹出。 
这样做的目的是为了维护辅助栈的栈顶元素始终是主栈当前的最大元素 。当主栈的元素被弹出时,如果它不是最大的元素(即它不是辅助栈的栈顶元素),那么辅助栈的栈顶元素(即当前的最大元素)就不会被弹出。 
因此,通过这种方式,辅助栈可以持续地维护主栈中的最大值。 
 
 
实在理不清过程的,强烈建议画个图 ,很直观 ,一目了然 ,尤其是栈、队列这种数据结构(2023/11/10晚)  
 
通关(过关挑战) 
1 给你一个栈,依次入栈1 2 3 4 ,请问以下出栈顺序可能出现吗?为什么?
1 2 出栈顺序:2 3  4 1  √
1 2 出栈顺序:1 4  2 3  × 3  4依次入栈了,出栈4之后的出栈顺序只能是3 2,而不可能是2 3,故错误
多做这样的例子,你就能彻底明白栈这个数据结构了:先进先出  
 
队列和Hash 基础知识(青铜挑战) Hash 
Hash,又称散列,将任意长度的输入,通过散列算法,转换为固定长度的输出 
映射、扩容机制 
碰撞处理(Hash冲突)
开放寻址法 链地址法 再哈希法(布隆过滤) 
建立公共溢出区 
 
 
 
队列 
特点:先进先出(2023/09/03午) 用链表实现队列
具体代码如下:
 
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 public  class  LinkQueue  {private  Node front;private  Node rear;private  int  size;public  LinkQueue ()  {this .front = new  Node (0 );this .rear = new  Node (0 );public  void  push (int  value)  {Node  newNode  =  new  Node (value);Node  temp  =  front;while  (temp.next != null ) {public  int  pull ()  {if  (front.next == null ) {"队列已空" );Node  firstNode  =  front.next;return  firstNode.data;public  void  traverse ()  {Node  temp  =  front.next;while  (temp != null ) {"\t" );static  class  Node  {public  int  data;public  Node next;public  Node (int  data)  {this .data = data;public  static  void  main (String[] args)  {LinkQueue  linkQueue  =  new  LinkQueue ();1 );2 );3 );"第一个出队的元素为:"  + linkQueue.pull());"队列中的元素为:" );
实战训练(白银挑战) 用栈实现队列 
情景:使用栈实现队列
实现思路:(2023/09/03午) 
利用栈先进后出的特性,首先一个栈inStack将元素压栈,它负责 “进” 元素  
inStack再依次出栈,另一个栈outStack负责将出栈的元素压栈,它负责 “出” 元素  
 
具体代码如下:
 
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 class  MyQueue  {public  MyQueue ()  {new  LinkedList <Integer>();new  LinkedList <Integer>();public  void  push (int  x)  {public  int  pop ()  {if  (outStack.isEmpty()) {return  outStack.pop();public  int  peek ()  {if  (outStack.isEmpty()) {return  outStack.peek();public  boolean  empty ()  {return  inStack.isEmpty() && outStack.isEmpty();private  void  in2out ()  {while  (!inStack.isEmpty()) {
用队列实现栈 
初始化阶段 :创建两个队列,queue1和queue2。这两个队列开始时都是空的。topElement变量用来跟踪当前栈顶的元素。
push操作 :当执行push操作时,将新元素添加到queue1的尾部。同时,因为这个新元素就是当前的栈顶元素,所以更新topElement为这个新元素。
pop操作 :当执行pop操作时,首先将queue1中的所有元素(除了最后一个)依次弹出并添加到queue2中。这样做的效果是,queue2中的元素就是queue1中元素的逆序,即最后一个进入的元素现在成为了第一个。然后,将queue1和queue2交换,使得queue2成为了主要的队列。最后,从queue2中弹出的最后一个元素就是原来的栈顶元素,返回这个元素。
peek操作 :这个操作只需要返回当前的topElement即可,不需要进行任何操作。
isEmpty操作 :检查queue1是否为空,如果为空,那么栈就是空的,返回true;否则返回false。
核心就是:两队列交换,queue1始终负责入栈,queue始终负责出栈  
 
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 public  class  StackUsingQueues  {  private  Queue<Integer> queue1;  private  Queue<Integer> queue2;  private  int  topElement;  public  StackUsingQueues ()  {  new  LinkedList <>();  new  LinkedList <>();  public  void  push (int  x)  {  public  int  pop ()  {  while (queue1.size() > 1 ) {  int  poppedElement  =  queue1.remove();  return  poppedElement;  public  int  peek ()  {  return  topElement;  public  boolean  isEmpty ()  {  return  queue1.isEmpty();  
还是看不明白的,强烈建议画个图 ,一目了然,就算忘了也能快速回忆起来:(2023/11/10晚)  
 
两数之和 
1 2 3 输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:2 + 7 = 9, 返回二者下标 
解决思路有三种,这里统一讲:
遍历数组,依次两两相加 ,判断是否等于 target。缺点:双层循环嵌套 —> 时间复杂度大  
建立一张Hash表,存储 target - x,存在即返回,不存在即存储 x,继续遍历 —> 遍历过的数字已经存储到map中了,减少了遍历元素的个数  
排序数组,将二层循环改为二分查找  
 
 
具体代码如下: 
 
1 2 3 4 5 6 7 8 9 10 11 public  static  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 ];
1 2 3 4 5 6 7 8 9 10 11 public  int [] twoSum3(int [] nums, int  target) {new  HashMap <>();for  (int  i  =  0 ; i < nums.length; i++) {if  (map.containsKey(target - nums[i])) {return  new  int []{i, map.get(target - nums[i])};return  new  int [0 ];
三数之和 二叉树遍历(层数优先) 基础知识(青铜挑战) 
实战训练(白银挑战) 简单的层序遍历 
基本的层序遍历思路很清晰:
给你一个二叉树根节点,你需要创建一个队列 queue 来遍历节点,一个链表 list 来存储节点的数据域,即值 
首先将根节点入队 
队列 queue 出队,将该节点值存入 list,再依次将左右孩子节点入队 
重复以上操作,每个节点出对后,都存储该节点值到 list 中,再依次将左右孩子节点入队,直到队列 queue为空 
这样得到的数据链表 list 就是按层序遍历的顺序得到的 
 
 
具体代码如下:(2023/09/09午)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  static  List<Integer> simpleLevelOrder (TreeNode root)  {if  (root == null ) {return  new  ArrayList <Integer>();new  ArrayList <Integer>();new  LinkedList <TreeNode>();while  (queue.size() > 0 ) {TreeNode  t  =  queue.remove();if  (t.left != null ) {if  (t.right != null ) {return  res;
层序遍历分层 
层序遍历我们做到了,这里添加一个要求:对层序遍历的节点值分层处理,即二叉树每层的节点值分别存放进一个链表 list 中 
这个代码怎么写呢?很简单的,按这个思路走:
我们之前层序遍历时,每出队一个节点,都把其值存入 list 链表中,然后入队其孩子节点 
在开始出队某一层的节点时,此时队列的节点数,就是二叉树这一层的节点数 
那我们根据可以某时刻队列容量来遍历队列,将这层的节点全部出队,并且把节点值存入该层独有的 list 中 
当然了,每个节点出队后,要将自己的孩子节点依次入队 
这样,当队列为空时,我们得到了各层的节点链表 list,返回一个包含各层 list 的 list 即可 
 
 
具体代码如下:(2023/09/09晚)  
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 public  static  List<List<Integer>> level102Order (TreeNode root)  {if  (root == null ) {return  new  ArrayList <List<Integer>>();new  ArrayList <List<Integer>>();new  LinkedList <TreeNode>();while  (queue.size() > 0 ) {int  size  =  queue.size();new  ArrayList <Integer>();for  (int  i  =  0 ; i < size; ++i) {TreeNode  t  =  queue.remove();if  (t.left != null ) {if  (t.right != null ) {return  res;
自底向上的层序遍历 
在前面学习的基础上,实现这个要求就很简单了
在拿到各层节点值的 list 后,按头插的方式,插入链表 list 中,就实现了自底向上的层序遍历了(2023/09/09晚) 具体代码如下:  
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 public  static  List<List<Integer>> levelOrderBottom (TreeNode root)  {new  LinkedList <List<Integer>>();if  (root == null ) {return  levelOrder;new  LinkedList <TreeNode>();while  (!queue.isEmpty()) {new  ArrayList <Integer>();int  size  =  queue.size();for  (int  i  =  0 ; i < size; i++) {TreeNode  node  =  queue.poll();TreeNode  left  =  node.left, right = node.right;if  (left != null ) {if  (right != null ) {0 , level);return  levelOrder;
锯齿形层序遍历 
1 2 3 4 5 6 7 二叉树如下:
在基本的层序遍历的基础上,如何实现锯齿形层序遍历? 
这样的思路能否完成呢:其他条件不变,改变存放节点值的顺序 如何?(2023/09/10午) 
我们这里将 list 该换为 queue  ,因为它可以提供了不同的入队方式 
具体代码如下: 
 
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 public  static  List<List<Integer>> zigzagLevelOrder2 (TreeNode root)  {new  LinkedList <>();if  (root == null )return  ans;new  LinkedList <>();boolean  isOrderLeft  =  true ;while  (!queue.isEmpty()) {new  LinkedList <>();int  size  =  queue.size();for  (int  i  =  0 ; i < size; i++) {TreeNode  poll  =  queue.poll();if  (isOrderLeft) {else  {if  (poll.left != null ) {if  (poll.left != null ) {new  LinkedList <Integer>(levelList));return  ans;
N叉数的层序遍历 
问题:将二叉树的情景改为N叉数,遍历结果也是很简单的,即将左右子节点改为若干子节点(2023/09/10午) 具体代码如下:
 
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 public  static  List<List<Integer>> nLevelOrder (NTreeNode root)  {new  ArrayList <>();new  ArrayDeque <>();if  (root != null )while  (!queue.isEmpty()) {new  ArrayDeque <>();new  ArrayList <>();while  (!queue.isEmpty()) {NTreeNode  cur  =  queue.pollFirst();for  (NTreeNode chd : cur.children) {if  (chd != null )return  ans;
获取每一层的最大数(扩展) 
出队每层节点时,累加节点值,求平均值即可:(2023/09/11早)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  static  List<Double> averageOfLevels (TreeNode root)  {new  ArrayList <>();if  (root == null ) return  res;new  LinkedList <>();while  (list.size() != 0 ) {int  len  =  list.size();double  sum  =  0 ;for  (int  i  =  0 ; i < len; i++) {TreeNode  node  =  list.poll();if  (node.left != null ) list.add(node.left);if  (node.right != null ) list.add(node.right);return  res;
获取每一层的平均数(扩展) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  static  List<Double> largestValues2 (TreeNode root)  {new  ArrayList <>();if  (root == null ) return  res;new  LinkedList <>();while  (list.size() != 0 ) {int  len  =  list.size();double  max  =  Double.MIN_VALUE;for  (int  i  =  0 ; i < len; i++) {TreeNode  node  =  list.poll();if  (node.left != null ) list.add(node.left);if  (node.right != null ) list.add(node.right);return  res;
获取二叉树的右视图(扩展) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public  static  List<Integer> rightSideView (TreeNode root)  {new  ArrayList <>();if  (root == null ) {return  res;new  LinkedList <>();while  (!queue.isEmpty()) {int  size  =  queue.size();for  (int  i  =  0 ; i < size; i++) {TreeNode  node  =  queue.poll();if  (node.left != null ) {if  (node.right != null ) {if  (i == size - 1 ) {  return  res;
通关(过关挑战) 
情景:获取一个二叉树的左视图(2023/09/12午)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public  static  List<Integer> rightSideView (TreeNode root)  {new  ArrayList <>();if  (root == null ) {return  res;new  LinkedList <>();while  (!queue.isEmpty()) {int  size  =  queue.size();for  (int  i  =  0 ; i < size; i++) {TreeNode  node  =  queue.poll();if  (node.left != null ) {if  (node.right != null ) {if  (i == 0 ) {  return  res;
二叉树遍历(深度优先) 基础知识(青铜挑战) 
实战训练(白银挑战) 递归实现二叉树的前、中、后序遍历 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  static  void  preOrder (TreeNode root, List<Integer> res)  {if  (root == null ) {return ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  public  static  void  inOrder (TreeNode root, List<Integer> res)  {if  (root == null ) {return ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  static  void  postOrder (TreeNode root, List<Integer> res)  {if  (root == null ) {return ;
迭代实现二叉树的前、中、后序遍历 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  static  List<Integer> preOrderTraversal (TreeNode root)  {new  ArrayList <Integer>();if  (root == null ) {return  res;new  LinkedList <TreeNode>();TreeNode  node  =  root;while  (!stack.isEmpty() || node != null ) {while  (node != null ) {return  res;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  static  List<Integer> inorderTraversal (TreeNode root)  {new  ArrayList <Integer>();new  LinkedList <TreeNode>();while  (root != null  || !stack.isEmpty()) {while  (root != null ) {return  res;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  static  List<Integer> postOrderTraversal (TreeNode root)  {new  ArrayList <>();if  (root == null ) return  res;new  Stack <>();TreeNode  node  =  root;while  (!stack.isEmpty() || node != null ) {while  (node != null ) {return  res;
通关(过关挑战) 
理解中序遍历的递归过程,挺难的,努努力吧(2023/09/12午)  
二叉树深度优先经典问题 
这块儿内容我个人认为非常抽象,虽然重要,但我还是决定先跳过这部分了,脑壳疼(2023/09/12午)  
最大深度问题 判断平衡树 最小深度 N叉数的最大深度 二分查找和搜索数 基础知识(青铜挑战) 
了解顺序查找、以及二分查找的简单实现。经典的顺序查找: 
 
简单的二分查找 
简单的二分查找,具体代码如下:(2023/09/12午)  
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 public  static  int  binarySearch1 (int [] array, int  low, int  high, int  target)  {while  (low <= high) {int  mid  =  (low + high) / 2 ;if  (array[mid] == target) {return  mid;else  if  (array[mid] > target) {1 ;else  {1 ;return  -1 ;
待优化有两点:解决溢出问题 、除法->移位(提高运算效率)  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public  static  int  binarySearch1 (int [] array, int  low, int  high, int  target)  {while  (low <= high) {int  mid  =  low + ((high - low) >> 1 ); if  (array[mid] == target) {return  mid;else  if  (array[mid] > target) {1 ;else  {1 ;return  -1 ;
上面提到的二分查找是最常用的循环法 ,还有一种递归法: (2023/09/12午 ) 
 
二分查找(递归法) 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 public  static  int  binarySearch2 (int [] array, int  low, int  high, int  target)  {if  (low < high) {int  mid=low + ((high-low) >> 1 );if  (array[mid] == target) {return  mid;  if  (array[mid] > target) {return  binarySearch2(array, low, mid - 1 , target);else  {return  binarySearch2(array, mid + 1 , high, target);return  -1 ;   
含重复元素的二分查找 
思路:
找到指定元素 target 后,继续向左查找,mid–, 
直到到达数组左边界,或者 nums[mid] 不等于 target 为止  
返回 nums[mid++] 
 
 
 
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 public  static  int  search1 (int [] nums, int  target)  {if  (nums == null  || nums.length == 0 )return  -1 ;int  left  =  0 ;if  (nums[0 ] == target) {return  0 ;int  right  =  nums.length - 1 ;while  (left <= right) {int  mid  =  left + (right - left) / 2 ;if  (nums[mid] < target) {1 ;else  if  (nums[mid] > target) {1 ;else  {while  (mid != 0  && nums[mid] == target)return  mid + 1 ;return  -1 ;
实战训练(白银挑战) 山脉数组峰顶查找 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  int  findPeakElement (int [] arr)  {  int  n  =  arr.length;  if  (n == 1 ) {  return  0 ; for  (int  i  =  0 ; i < n - 1 ; ++i) {  if  (arr[i] > arr[i + 1 ]) {  return  i; return  n - 1 ;  
旋转数组最小数字 1 2 3 输入: nums = [4,4,4,5,1,2,3] 1 [1,2,3,4,5] ,旋转3 次得到输入数组
最小值就在中轴线,仍然是考虑到了进行节点左右大小的比较,并且在二分结束之后,直接拿到 nums[low],即为最小值 具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  static  int  findMin (int [] nums)  {int  low  =  0 ;int  high  =  nums.length - 1 ;while  (low < high) {int  pivot  =  low + ((high - low) >> 1 );if  (nums[pivot] < nums[high]) {else  {1 ;return  nums[low];
有序数组缺失数字 
简单,二分查找,当 nums[i] != i 时,就拿到了这个数字(2023/09/15晚) 具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  static  int  solve (int [] a)  {int  left  =  0 ;int  right  =  a.length - 1 ;while  (left < right) {int  mid  =  (left + right) / 2 ;if  (a[mid] == mid) {1 ;else  {return  left;
优化求平方根 快速排序 基础知识(青铜挑战) 
快速排序通过多次比较与交换来完成排序。而这个过程又被分为了多次重复单趟排序,接下来我们先从每一趟的排序讲起。
快速排序思想很简单,也很重要:
在一个无序数组中取一个数key,每一趟排序的最终目的是:让key的左边的所有数小于key,key的右边都大于key(假设排升序)
 接下来,我们对key的左右区间进行单趟排序,可以预见的是,每次排序都固定好了一个数。而当我们对细分的左右区间进行单趟排序,最终整个数组都会化为有序
最常用的快排实现方法:快慢指针法 
取最左边的数为key 
定义两个快慢指针,都从key的下一位往右走 
fast每走一步判断一下它指向的元素是否小于key,若小于则交换fast和slow位置的元素,并且让slow向前走 
直到fast走到底,结束循环 
最后让slow和key位置的值交换,再返回key的位置 
 
具体代码如下:(2023/09/19午)  
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 public  void  quickSort (int [] nums, int  start, int  end)  {if  (start >= end) {return ;int  pivot  =  nums[start];int  slow  =  start;int  fast  =  start + 1 ;while  (fast <= end) {if  (nums[fast] < pivot) {1 );1 , end);private  void  swap (int [] nums, int  i, int  j)  {int  temp  =  nums[i];
实战训练(白银挑战) 数组中K大的数 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public  static  void  quicksort (int [] nums, int  left, int  right)  {int  start  =  left;int  end  =  right;int  pivot  =  nums[(end + start) / 2 ]; while  (start < end) {while  (nums[start] > pivot) {while  (nums[end] < pivot) {if  (start >= end) {break ;int  temp  =  nums[start];if  (nums[start] == pivot) {if  (nums[end] == pivot) {if  (start == end) { if  (left < end) {if  (right > start) {
归并排序 
好难好难,需要先理解思路,再考虑编码实现:
归并排序简单来讲,就是将大的序列视为若干小的数组,利用归并思想实现排序的方法。 
分治策略(分:把问题分成小的问题分别求解;治:将分的阶段得到的各答案合在一起) 
 
 
 
位运算 基础知识(青铜排序) 
原码、反码、补码 
与运算 &,或运算 |,异或运算 ^,取反运算 ! 
移位运算:左移、算术右移、逻辑右移 
移位运算与乘除法的关系 
 
字符串 基础知识(青铜挑战) 转换成小写字母 
遍历该字符串,依次转换为小写,存入新的字符串中
具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 public  static  String toLowerCase (String s)  {int  n  =  s.length();char [] chars = s.toCharArray();for  (int  i  =  0 ; i < n; ++i) {if  (chars[i] >= 65  && chars[i] <= 90 ) {32 ;String  str  =  new  String (chars);return  str;
实战训练(白银挑战) 反转字符串 
思路:
使用双指针,一个在前,一个在后,相向移动,遍历字符串 
在遍历的过程中,将两指针指向的元素交换位置 
直到两指针相遇 
 
具体代码如下:(2023/09/30早)  
1 2 3 4 5 6 7 8 public  static  void  reverseString (char [] s)  {int  n  =  s.length;for  (int  left  =  0 , right = n - 1 ; left < right; ++left, --right) {char  tmp  =  s[left];
K个一组反转 
仅反转字母 
我们有两种思路:
压栈法: 
遍整个字符串,将字母依次压入栈中 
创建一个新的字符串对象,再次遍历该字符串:如果是非字母,将该字符放入新字符串;如果是字母,则从存入弹出的栈顶元素 
直到遍历结束 
 
具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  static  String reverseOnlyLetters (String S)  {new  Stack ();for  (char  c : S.toCharArray())if  (Character.isLetter(c))StringBuilder  ans  =  new  StringBuilder ();for  (char  c : S.toCharArray()) {if  (Character.isLetter(c))else return  ans.toString();
双指针法: 
定义两个指针,一个从头向后遍历,一个从后向前,维护字母字符 
创建一个新的字符串对象,前指针遍历字符串 
如果是非字母,则将该字符放入新字符串;如果是字母,则移动后指针,找到字母,将该字母存入新的字符串 
直到前指针遍历结束 
 
具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  static  String reverseOnlyLetters2 (String S)  {if  (S == null  || S.length() == 0 ){return   S;StringBuffer  ans  =  new  StringBuffer ();int  j  =   S.length() - 1 ;for  (int  i  =  0 ; i < S.length(); i++) {if (Character.isLetter(S.charAt(i))){while  (!Character.isLetter(S.charAt(j)))else  {return  ans.toString();
反转字符串里的单词 
可以使用语言提供的方法解决:
将字符串按空格分开,转换为数组/集合 
使用 reserve 方法,反转数组/集合元素 
再将数组/集合转换为字符串 
 
具体代码如下:(2023/09/30早)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  static  String reverseWords2 (String s)  {if  (s == null  || s.length() == 0 ) {return  s;"\\s+" );return  String.join(" " , wordList);
验证回文串 
1 2 A  man, a  plan, a  canal: Panama
我们仍然使用双指针法解决:
首先要做的就是遍历一遍字符串,把所有的字母都放到新的字符串中 
再使用双指针相向遍历新字符串,遍历的的过程中,判断两指针指向元素是否相同 
直到两指针相遇,说明是回文字符串 
 
具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 public  static  boolean  isPalindrome (String s)  {StringBuffer  sgood  =  new  StringBuffer ();int  length  =  s.length();for  (int  i  =  0 ; i < length; i++) {char  ch  =  s.charAt(i);if  (Character.isLetterOrDigit(ch)) {StringBuffer  sgood_rev  =  new  StringBuffer (sgood).reverse();return  sgood.toString().equals(sgood_rev.toString());
字符串里的第一个唯一字符 
最好用的方法:Hash法
定义一个 Map 集合,我们遍历一遍字符串,使用 Map 记录每个字符出现过的次数 
再次遍历该 Map,取到第一个出现次数大于1的元素 
 
具体代码如下:(2023/09/30早)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  static  int  firstUniqChar (String s)  {if  (s == null  || s.length() == 0 ) {return  0 ;new  HashMap <Character, Integer>();for  (int  i  =  0 ; i < s.length(); ++i) {char  ch  =  s.charAt(i);0 ) + 1 );for  (int  i  =  0 ; i < s.length(); ++i) {if  (frequency.get(s.charAt(i)) == 1 ) {return  i;return  -1 ;
判断是否互为字符重排 
1 2 3 4 5 6 7 8 9 10 public  static  boolean  checkPermutation (String s1, String s2)  {char [] s1Chars = s1.toCharArray();char [] s2Chars = s2.toCharArray();return  new  String (s1Chars).equals(new  String (s2Chars));
Map法:
分别统计两个字符串中,各个字符出现的次数 
再次比较字符出现次数,只有每个字符出现的次数都一样,才能算两字符串互为字符重排 
 
具体代码如下:(2023/09/30早)  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public  static  boolean  checkPermutation2 (String s1, String s2)  {if  (s1.length() != s2.length()) {return  false ;char [] s1chars = s1.toCharArray();for  (char  s1char : s1chars) {if  (!s2Map.containsKey(s1char) || (int ) s1Map.get(s1char) != (int ) s2Map.get(s1char)) {return  false ;return  true ;private  static  HashMap<Character, Integer> getMap (String str)  {new  HashMap <>();char [] chars = str.toCharArray();for  (char  aChar : chars) {0 ) + 1 );return  map;
堆结构 基础知识(青铜挑战) 
堆的概念 :堆是一种数据结构,按照完全二叉树的存储顺序,将数据存储在一个一维数组中
大顶堆 :任意节点值均大于它的左右节点值   小顶堆 :任意节点值均大于它的左右节点值 
堆的构造过程 :按照层次将所有元素一次添入二叉树中,再不断调整,最终使其符合堆结构
堆中插入元素 :确认插入位置能够保持原二叉树为完全二叉树,再自底向上调整,保证每一层的子树都符合堆结构
堆中删除元素 :一般都是删除堆顶元素,将堆顶元素与二叉树最后一个元素对调,删除堆顶元素后,再依次调整每一层的各子树
 
实战训练(白银挑战) 数组中查找第K大的元素 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  static  int  findKthLargest (int [] nums, int  k)  {if  (k > nums.length) {return  -1 ;int  len  =  nums.length;new  PriorityQueue <>(k, (a, b) -> a - b);for  (int  i  =  0 ; i < k; i++) {for  (int  i  =  k; i < len; i++) {Integer  topEle  =  minHeap.peek();if  (nums[i] > topEle) {return  minHeap.peek();
堆排序 
堆排序是什么原理呢?
构造大顶堆,依次取出堆顶元素;每取出一个堆顶元素后,重新调整堆,这样得到的序列就是从大到小排序的(降序排序) 小顶堆同理,得到的序列就是从小到大排序的(升序排序) 升序用小 ,降序用大  (2023/09/30晚) 
 
合并K个有序链表 图算法 基础知识(青铜挑战) 
有向图、无向图 
边、弧 
节点的度、入度、出度 
描述节点之间的关系:<V1,V2>  (V1,V2)  
路径、回路 
权、网 
连通图、连通分量、强连通图 
生成树、生成森林 
 
实战训练(白银挑战) 图的存储 图的遍历 最小生成树问题 最短路径问题 排序算法 
冒泡排序 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 public  static  int [] bubbleSort(int [] arr) {for  (int  i  =  1 ; i < arr.length; i++) {boolean  flag  =  true ;for  (int  j  =  0 ; j < arr.length - i; j++) {if  (arr[j] > arr[j + 1 ]) {int  tmp  =  arr[j];1 ];1 ] = tmp;false ;if  (flag) {break ;return  arr;
选择排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  static  int [] selectionSort(int [] arr) {for  (int  i  =  0 ; i < arr.length - 1 ; i++) {int  minIndex  =  i;for  (int  j  =  i + 1 ; j < arr.length; j++) {if  (arr[j] < arr[minIndex]) {if  (minIndex != i) {int  tmp  =  arr[i];return  arr;
插入排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  static  int [] insertionSort(int [] arr) {for  (int  i  =  1 ; i < arr.length; i++) {int  preIndex  =  i - 1 ;int  current  =  arr[i];while  (preIndex >= 0  && current < arr[preIndex]) {1 ] = arr[preIndex];1 ;1 ] = current;return  arr;
快速排序 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 public  static  void  quickSort (int [] arr, int  low, int  high)  {if  (low >= high) {return ;int  point  =  arr[low];int  slow  =  low;int  fast  =  low + 1 ;while  (fast <= high) {if  (arr[fast] < point) {int  temp  =  arr[slow];int  temp  =  arr[slow];1 );1 , high);
各排序算法比较 
贪心 
小试牛刀 分发饼干 
你有几个孩子和几摞饼干,孩子有胃口,饼干有数量,尽可能分发饼干以满足胃口大的孩子 
解题思路:
要尽可能满足胃口大的孩子,我们不妨把孩子们按胃口排序,从胃口最大的孩子开始,分发饼干 
当然,与之对应的,每一摞饼干也要按照数量排好序 
从胃口大的孩子开始,从数量多的饼干开始,依次给孩子分配 
如果饼干不满足该孩子的胃口(此时饼干数量最多了),那就分配给下一个孩子(他的胃口更小),看能不能满足这个孩子的胃口 
以此类推,饼干不满足孩子胃口,就找下一个孩子;满足了,再拿下一摞饼干进行分配 
这样就能保证:能够合理分配这些饼干来尽可能满足胃口大的孩子  (2023/10/19晚)  
 
 
具体代码如下: 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public  static  int  findContentChildren (int [] g, int [] s)  {int  count  =  0 ;int  start  =  s.length - 1 ;for  (int  index  =  g.length - 1 ; index >= 0 ; index--) {if  (start >= 0  && g[index] <= s[start]) {return  count;
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 public  static  int  findContentChildren (int [] g, int [] s)  {int  count  =  0 ;int  index  =  g.length - 1 ;int  start  =  s.length - 1 ;while  (index >= 0 ) {if  (start >= 0  && g[index] <= s[start]) {return  count;
柠檬水找零 
情景: 
你在卖柠檬水,一杯柠檬水 5 块钱,顾客排队购买你的柠檬水 
每位顾客只买一杯,他们给你的金额有5块、10块和20块 
最初你手头的余额为0,确保给每位顾客正确找零 
 
解题思路: 
顾客给你5块钱,你就照收不误
顾客给你10块钱,你得找他5块钱
顾客给你20块钱,你得找他5块钱和10块钱(优先),或者找他3张5块钱(其次)
也就是说,20块钱是不会用来找零钱的,我们就不关注手头20块钱的数目了
如果你找不了零钱,那一定是5块和10块其一不够找零钱了,我们只需关注这两种面值即可
确保给每位顾客正确找零,就要保证每处理一个顾客(每次找完零钱)后,手头的5块和10块不能“欠费”(2023/10/19晚)  
具体代码如下:
 
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 public  static  boolean  lemonadeChange (int [] bills)  {int  cash_5  =  0 ;int  cash_10  =  0 ;for  (int  i  =  0 ; i < bills.length; i++) {if  (bills[i] == 5 ) {else  if  (bills[i] == 10 ) {else  if  (bills[i] == 20 ) {if  (cash_10 > 0 ) {else  {3 ;if  (cash_5 < 0  || cash_10 < 0 ) return  false ;return  true ;
分发糖果 
情景: 
你有几个孩子,糖果是无限制的,每个孩子都有各自的评分 
要求根据他们的评分合理地给他们分发糖果 ,并要求用最少的糖果数完成 
 
解题思路:
具体代码如下:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  static  int  candy (int [] ratings)  {int [] candyVec = new  int [ratings.length];0 ] = 1 ;for  (int  i  =  1 ; i < ratings.length; i++) {if  (ratings[i] > ratings[i - 1 ]) {1 ] + 1 ;else  {1 ;for  (int  i  =  ratings.length - 2 ; i >= 0 ; i--) {if  (ratings[i] > ratings[i + 1 ]) {1 ] + 1 );int  ans  =  0 ;for  (int  s : candyVec) {return  ans;
高频问题 判断区间是否重叠 
输入:intervals = [[0, 30], [15, 20], [5, 10]]
解释:存在重叠区间
详情如下图所示:
 
解题思路:(2023/10/22晚) 
遍历每一个区间,相邻的两个区间作比较 
如果前区间的尾部大于后区间的首部,就说明两区间重叠了 
 
具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 public  static  boolean  canAttendMeetings (int [][] intervals)  {0 ] - v2[0 ]);for  (int  i  =  1 ; i < intervals.length; i++) {if  (intervals[i][0 ] < intervals[i - 1 ][1 ]) {return  false ;return  true ;
区间合并 
输入:intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]
输出:[[1, 6], [8, 10], [15, 18]]
解释:区间 [1, 3] 和 [2, 6] 重叠, 将它们合并为 [1, 6]
详情如下图所示:
 
解题思路(2023/10/22晚) 
还是遍历每一个区间,判断相邻的两个区间是否重合 
如果不重合,就继续向后遍历区间;如果重合,就合并两区间· 
判断是否重合就能确定是否需要合并了,那么具体该如何合并呢?我们拿一个新的数组来维护合并区间后的新数组
合并:合并后的区间尾部取重合区间尾部的最大的那个,继续向后遍历区间 
维护新的区间,一直比较新数组的尾部,直到不合并才开辟新区间(++idx) 
 
 
 
具体代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  static  int [][] merge(int [][] intervals) {0 ] - v2[0 ]);int [][] res = new  int [intervals.length][2 ];int  idx  =  -1 ;for  (int [] interval : intervals) {if  (idx == -1  || interval[0 ] > res[idx][1 ]) {else  {1 ] = Math.max(res[idx][1 ], interval[1 ]);return  Arrays.copyOf(res, idx + 1 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0 ] - v2[0 ]);int [][] res = new  int [intervals.length][2 ];int  idx  =  0 ;if  (intervals.length > 0 ) {0 ];for  (int  i  =  1 ; i < intervals.length; i++) {if  (intervals[i][0 ] > res[idx][1 ]) {else  {1 ] = Math.max(res[idx][1 ], intervals[i][1 ]);return  Arrays.copyOf(res, idx + 1 );
区间插入 
输入:intervals = [[1, 3], [6, 9]], newInterval = [2, 5]
输出:[1, 5], [6, 9]
解释:新区间 [2, 5] 和 [1, 3] 重叠, 因此合并成为 [1, 5]
详情可参考下图:
 
解题思路(2023/10/22晚) 
我们仍然拿一个新数组来维护结果集 
遍历所有区间,将新区间左边且不重合的区间加入结果集(区间尾部小于新区间首部,就说明该区间在新区间的左边且不重合) 
再次遍历所有区间,合并与新区间重合的区间(在经过第一轮遍历筛选后,这次遍历时,区间首部小于新区间尾部的都属于重合) 
最后剩下的区间就属于新区间右边且不重合的区间了,直接挨个放进结果集就行 
 
具体代码如下:  
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 public  static  int [][] insert(int [][] intervals, int [] newInterval) {int [][] res = new  int [intervals.length + 1 ][2 ];int  idx  =  0 ;int  i  =  0 ;while  (i < intervals.length && intervals[i][1 ] < newInterval[0 ]) {while  (i < intervals.length && intervals[i][0 ] <= newInterval[1 ]) {0 ] = Math.min(intervals[i][0 ], newInterval[0 ]);1 ] = Math.max(intervals[i][1 ], newInterval[1 ]);while  (i < intervals.length) {return  Arrays.copyOf(res, idx);
滑动窗口 高频问题 最小覆盖子串 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 new  HashMap <>();new  HashMap <>();for  (char  c : t.toCharArray()) 0 ) + 1 );int  left  =  0 , right = 0 ;int  valid  =  0 ; int  start  =  0 , len = Integer.MAX_VALUE;while  (right < s.length()) {char  c  =  s.charAt(right);if  (need.containsKey(c)) {0 ) + 1 );if  (window.get(c).equals(need.get(c)))while  (valid == need.size()) {if  (right - left < len) {char  d  =  s.charAt(left);if  (need.containsKey(d)) {if  (window.get(d).equals(need.get(d)))1 );return  len == Integer.MAX_VALUE ?""  : s.substring(start, start + len);
字符串排列 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 HashMap<Character, Integer> need = new  HashMap <>();new  HashMap <>();for  (int  i  =  0 ; i < t.length(); i++) {char  c  =  t.charAt(i);0 ) + 1 );int  left  =  0 , right = 0 ;int  valid  =  0 ;while  (right < s.length()) {char  c  =  s.charAt(right);if  (need.containsKey(c)) {0 ) + 1 );if  (window.get(c).equals(need.get(c)))while  (right - left >= t.length()) {if  (valid == need.size())return  true ;char  d  =  s.charAt(left);if  (need.containsKey(d)) {if  (window.get(d).equals(need.get(d)))0 ) - 1 );return  false ;
找所有字母异位词 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 Map<Character, Integer> need = new  HashMap <>();new  HashMap <>();for  (int  i  =  0 ; i < t.length(); i++) {char  c  =  t.charAt(i);0 ) + 1 );int  left  =  0 , right = 0 ;int  valid  =  0 ;new  ArrayList <>(); while  (right < s.length()) {char  c  =  s.charAt(right);if  (need.containsKey(c)) {0 ) + 1 );if  (window.get(c).equals(need.get(c))) {while  (right - left >= t.length()) {if  (valid == need.size()) {char  d  =  s.charAt(left);if  (need.containsKey(d)) {if  (window.get(d).equals(need.get(d))) {1 );return  res;
最长无重复字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Map<Character, Integer> window = new  HashMap <>();int  left  =  0 , right = 0 ;int  res  =  0 ; while  (right < s.length()) {char  c  =  s.charAt(right);0 ) + 1 );while  (window.get(c) > 1 ) {char  d  =  s.charAt(left);1 );return  res;