反转单向链表(206题)
方法一
构建一个新链表,从旧链表依次拿到每个节点,创建新节点添加至新链表头部,完成后新链表即是倒序的。
1 2 3 4 5 6 7 8 9 10
| public ListNode reverseList1(ListNode head) { ListNode res = null; ListNode p = head; while(p != null){ res = new ListNode(p.val, res); p= p.next; } 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 25 26 27 28 29 30 31 32 33 34 35
| public ListNode reverseList2(ListNode head){ List list1 = new List(head); List list2 = new List(null);
while(true) { ListNode first = list1.removeFirst(); if(first == null){ break; } list2.addFirst(first); } return list2.head; }
static class List{ ListNode head;
public List(ListNode head) { this.head = head; }
public void addFirst(ListNode first){ first.next = head; head = first; }
public ListNode removeFirst() { ListNode first = head; if(first != null){ head = first.next; } return first; } }
|
评价:更加面向对象,如果实际写代码非刷题,更多会那么做。方法三(递归)
递归,利用归时倒序的特性,完成让5->4,4->3,…
写一个递归方法,返回值用来拿到最后一个节点
1 2 3 4 5 6 7 8 9 10
| public ListNode reverseList3(ListNode head) { if(head == null || head.next == null) { return head; } ListNode res = reverseList3(head.next); head.next.next = head; head.next = null; return res; }
|
评价:单向链表没有prev指针,但是利用递归的特性[记住了]链表倒过来的顺序方法四
从链表每次拿到第二个节点,将其从链表断开,插入头部,直到它为null结束
1、设置指针head(旧头)、n1(新头)、o2(旧老二),分别指向第一、第一、第二节点。
2、将o2节点从链表中断开,即o1节点指向第三节点
3、o2节点链入链表头部
4、n1指向o2
5、o2指向o1的下一个节点
6、重复以上2~5步,直到o2指向null
7应该考虑边界条件,即链表中不满足两个元素时,无需走以上逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public ListNode reverseList4(ListNode head) { if(head == null || head.next == null) { return head; }
ListNode o2 = head.next; ListNode n1 = head; while(o2 != null) { head.next = o2.next; o2.next = n1; n1 = o2; o2 = head.next; } return n1; }
|
评价:最难但是最高效的方法,死了好多脑细胞啊/(ㄒoㄒ)/~~
方法五
把链表分成两部分,思路就是不断从链表2的头,往链表1的头搬移
1、n1指向null,代表新链表一开始没有元素,o1指向原链表的首节点
2、开始循环,o2指向原链表次节点
3、搬移
4、指针复位
5、重复2~4步
6、当o1 = null时退出循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public ListNode reverseList5(ListNode head) { if(head == null || head.next == null) { return head; } ListNode n1 = null; while(head.next != null){ ListNode o2 = head.next; head.next = n1; n1 = head; head = o2; } return n1; }
|
总结:稀里糊涂的,感觉好难好难啊,为什么看一道题不会一道题…根据值删除节点(203题)
普通方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public ListNode removeElements(ListNode head, int val) { ListNode s = new ListNode(-1, head); ListNode p1 = s; ListNode p2; while((p2 = p1.next) != null){
if(p2.val == val) { p1.next = p2.next; }else{ p1 = p1.next; } } return s.next; }
|
递归
我真的要被递归搞烦死了,真的烦死了,气死我了!!!(╯▔皿▔)╯思路,递归函数负责返回:从当前节点(我)开始,完成删除的链表
1、若我与v相等,应当返回下一个节点递归结果
2、若我与v不等,应该返回我,但我的next应该更新(让我能带上后续删过的节点)
1 2 3 4 5 6 7 8 9 10 11
| public ListNode removeElements(ListNode head, int val) { if(head == null) { return null; } if(head.val == val) { return removeElements(head.next, val); }else { head.next = removeElements(head.next, val); return head; } }
|
删除链表的倒数第N个节点(19题)
链表求解
没得说,现在链表都是简单方法了吗?我不敢苟同,这样显得我很笨哈哈哈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public ListNode removeNthFromEnd(ListNode head, int n) { ListNode s = new ListNode(-1, head); recursion(s, n); return s.next; }
public int recursion(ListNode p, int n) { if(p == null) { return 0; } int nth = recursion(p.next, n); if(nth == n) { p.next = p.next.next; } return nth + 1; }
|
快慢指针法(双指针)
写一下思路,要不然下一次看都不知道自己在干什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| n = 2 p1 p2为设置的指针 p1 p2 s -> 1 -> 2 -> 3 -> 4 -> 5 -> null
p2 s -> 1 -> 2 -> 3 -> 4 -> 5 -> null
p2 先让p2走三步 s -> 1 -> 2 -> 3 -> 4 -> 5 -> null
p1 p2 开始同时移动 s -> 1 -> 2 -> 3 -> 4 -> 5 -> null
p1 p2 让p2移动到null为止,刚刚好p1指针就可以做倒数第二的删除操作了,太牛了 s -> 1 -> 2 -> 3 -> 4 -> 5 -> null
public ListNode removeNthFromEnd(ListNode head, int n) { ListNode s = new ListNode(-1, head); ListNode p1 = s; ListNode p2 = s; for(int i = 0; i < n + 1; i++) { p2 = p2.next; } while(p2 != null) { p1 = p1.next; p2 = p2.next; } p1.next = p1.next.next; return s.next; }
|
删除有序链表的重复节点(83题)
双指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public ListNode deleteDuplicates(ListNode head) { ListNode p1 = head; ListNode p2; if(head == null || head.next == null) { return head; } while((p2 = p1.next) != null){ if(p1.val == p2.val) { p1.next = p2.next; } else { p1 = p1.next; } } return head; }
|
递归
我最爱的递归又来了(微笑🙂)
思路
递归负责返回:从当前节点(我)开始,完成去重的链表
1、若我与next重复,返回next
2、若我与next不重复,返回我,但next需要更新
1 2 3 4 5 6 7 8 9 10 11 12
| public ListNode deleteDuplicates(ListNode head) { if(head == null || head.next == null){ return head; } if(head.val == head.next.val) { return deleteDuplicates(head.next); }else { head.next = deleteDuplicates(head.next); return head; } }
|
删除排序链表的重复元素(82题)
注意和上一题区别的是重复的元素一个都不留
递归
我最爱的递归又来了(微笑)
思路:
递归函数负责返回:从当前节点(我)开始,完成去重的链表
1、若我与next重复,一直找到下一个不重复的节点,已它的返回值为准
2、若我与next不重复,返回我,重视更新next
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public ListNode deleteDuplicates(ListNode head) { if(head == null || head.next == null) { return head; } if(head.val == head.next.val) { ListNode x = head.next.next; while(x != null && x.val == head.val) { x = x.next; } return deleteDuplicates(x); }else { head.next = deleteDuplicates(head.next); return head; } }
|
带哨兵三指针
s:哨兵
p1:要删除的前一个节点指针
p2:重复的第一个节点
p3:移动到不重复的第一个节点
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 ListNode deleteDuplicates(ListNode head) { if(head == null || head.next == null) { return head; } ListNode s = new ListNode(-1, head); ListNode p1 = s; ListNode p2, p3; while((p2 = p1.next) != null && (p3 = p2.next) != null){ if(p2.val == p3.val) { while((p3=p3.next) != null && p3.val == p2.val) { } p1.next = p3; }else { p1 = p1.next; } } return s.next; }
|
合并有序链表(21题)
方法一(大小比较链入法)
- 谁小,把谁链给新的链表p,p和小的都向后平移一位
- 当p1, p2有一个为null,退出循环,把不为null的链给p
自己写出来了,开心哈哈哈
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 ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode s = new ListNode(-1, null); ListNode p = s; ListNode p1 = l1; ListNode p2 = l2; while(p1 != null && p2 != null){ if(p1.val <= p2.val) { p.next = p1; p1 = p1.next; }else { p.next = p2; p2 = p2.next; } p = p.next; } if(p1 == null){ p.next = p2; } if(p2 == null) { p.next = p1; } return s.next; }
|
递归
我最爱的递归又来了(微笑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public ListNode mergeTwoLists(ListNode p1, ListNode p2) { if(p2 == null) { return p1; } if(p1 == null) { return p2; } if(p1.val < p2.val) { p1.next = mergeTwoLists(p1.next, p2); return p1; } else { p2.next = mergeTwoLists(p1, p2.next); return p2; } }
|
合并K个升序链表(23题)
多路递归
听老师讲吧,我真的好笨/(ㄒoㄒ)/~~
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
| public ListNode mergeKLists(ListNode[] lists) { if(lists.length == 0) { return null; } return split(lists, 0, lists.length - 1); }
public ListNode split(ListNode[] lists, int i, int j){ if(i == j) { return list[i]; } int m =(i+j) >>> 1; ListNode left = split(lists, i, m); ListNode right = split(lists, m+1, j); return mergeTwoLists(left, right); }
public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode s = new ListNode(-1, null); ListNode p = s; ListNode p1 = l1; ListNode p2 = l2; while(p1 != null && p2 != null){ if(p1.val <= p2.val) { p.next = p1; p1 = p1.next; }else { p.next = p2; p2 = p2.next; } p = p.next; } if(p1 == null){ p.next = p2; } if(p2 == null) { p.next = p1; } return s.next; }
|
本题的思路是典型的分而治之问题(分,治,合)
分:一个问题在内部被切分成的多个子问题,每个子规模比原来减少了k倍(此题是减少了一半)
治:解决问题,要根据分来解决问题
合:每个子问题解决完之后就合并成原来的问题找链表的中间节点(876题)
快慢指针
顶级思路:
一个指针p1走一步,一个指针p2走两步
走的快的分两种情况
1、它的p2.next到null了,那么此时p1所在位置就是中间节点位置
2、p2走到null了,那么此时p1所在位置就是中间节点位置
1 2 3 4 5 6 7 8 9 10 11
| public ListNode middleNode(ListNode head) { ListNode p1 = head; ListNode p2 = head; while(p2 != null && p2.next != null) { p1 = p1.next; p2 = p2.next; p2 = p2.next; } return p1; }
|
回文链表(234题)
方法一
步骤1、找中间点
步骤2、中间节点后半段链表反转
步骤3、反转后链表与原链表逐一比对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public boolean isPalindrome(ListNode head) { ListNode middle = mideele(head); ListNode reverse = reverse(middle); while(reverse != null) { if(reverse.val != head.val) { return false; } reverse = reverse.next; head = head.next; } return true; }
public ListNode middle(ListNode head) { ListNode p1 = head; ListNode p2 = head; while(p2 != null && p2.next != null) { p1 = p1.next; p2 = p2.next; p2 = p2.head; } return p1; }
public ListNode reverse(ListNode o1) { ListNode n1 = null; while(o1 != null) { ListNode o2 = o1.next; o1.next = n1; n1 = o1; o1 = o2; } return n1; }
|
优化:快慢指针找中间值和反转链表同时做到放在一个循环里面
步骤1、找中间点同时中间节点前半段链表反转、
步骤2、反转后前半个链表与原链表逐一比对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public boolean isPalindrome(ListNode head) { ListNode p1 = head; ListNode p2 = head; ListNode n1 = null; ListNode o1 = head; while(p2 != null && p2.next != null) { p1 = p1.next; p2 = p2.next; p2 = p2.next;
if(p2 != null) { p1 = p1.next; }
o1.next = n1; n1 = o1; o1 = p1; } while(n1 != null) { if(n1.val != p1.val) { return false; } n1 = n1.next; p1 = p1.next; } return true; }
|
判断是否有环形链表(141题)
龟兔算法判断
思路:
- 龟一次走一步,兔子一次走两步
- 当兔子能走到终点时,不存在环
- 当兔子能够追上龟时,则可以判定存在环链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public boolean hasCycle(ListNode head) { ListNode h = head; ListNode t = head; while(h != null && h.next != null) { t = t.next; h = h.next.next; if(h == t) { return true; } } return false; }
|
确定环的入口位置(142题)
龟兔算法进阶判断
进阶算法:判断环的入口
- 从他们第一次相遇开始,龟回到起点,兔子保持原位不变
- 龟和兔子一次都只走一步
- 当再次相遇时,相遇点就是环的入口 解释:
- 设起点到环入口走a步,绕环一周的长度为b
- 那么从起点开始,走a + 绕环n圈,都能找到环的入口(顶级思路)
- 第一次相遇时:
- 兔走了a + 绕环n圈 + k,k是他们相遇距环入口位置
- 龟走了a + 绕环n圈 + k, 当然它绕的圈数比兔少
- 兔走的距离是龟的两倍,所以龟走的 = 兔走的 - 龟走的 = 环绕n圈(顶级思路)
- 而前面分析过,如果走了a + 绕环n圈,都能找到环入口,因此相遇点开始,再走a步,就是环入口
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 ListNode detectCycle(ListNode head) { ListNode h = head; ListNode t = head; while(h != null && h.next != null) { t = t.next; h = h.next.next; if(h == t) { t = head; while(true) { if(t == h) { return t; } t = t.next; h = h.next; } } } return null; }
|
合并有序数组(88题变形)
方法一:递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public static void merge(int[] a1, int i, int iEnd, int j, int jEnd, int[] a2, int k){ if(i > iENd) { System.arraycopy(a1, j, a2, k, jEmd - j + 1); } if(j > jEnd) { System.arraycopy(a1, i, a2, k, iEmd - i + 1); } if(a1[i] < a1[j]) { a2[k] = a1[i]; merge(a1, i+1, iEnd, j, jEnd, a2, k+1); }else { a2[k] = a1[j]; merge(a1, i, iEnd, j+1, jEnd, a2, k+1); } }
|
方法二
和方法一逻辑一致,也是比较
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 merge(int[] a1, int i, int iEnd, int j, int jEnd, int[] a2) { int k = 0; while(i <= iEnd && j <= jEnd) { if(a1[i] < a1[j]) { a2[k] = a1[i]; i++; } else { a2[k] = a1[j]; j++; } k++; } if(i > iEnd) { System.arraycopy(a1, j, a2, k, jEmd - j + 1); } if(j > jEnd) { System.arraycopy(a1, i, a2, k, iEmd - i + 1); } }
|