查找两个相交链表的第一个相交节点

题目:输入两个链表,找出它们的第一个公共节点。
示例:① listA = {4,1,8,4,5}
istB = {5,0,1,8,4,5}
结果返回 1 。

② listA = {}
listB = {1,2,3}
返回 null。

思路: ① 判断两个链表是否为空,如果有一个为空,直接返回null。
② 计算两个列表的长度,算出长度差,记作 dist。
③ 声明两个快慢引用 slow和fast,让快引用fast指向长链表。
④ 让快引用fast,先往前走dist的距离。
⑤ fast、slow同时向前走,边走边判断两个引用是否指向同一个位置。
要注意:while循环里的条件判断。

image.png
代码实现:

  1. public class Solution {
  2. public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
  3. // 声明两个快慢指针。
  4. ListNode fast = null;
  5. ListNode slow = null;
  6. if(pHead1==null || pHead2==null){
  7. return null;
  8. }
  9. // // 先计算A链表和B链表的长度。
  10. int listA_length = getLength(pHead1);
  11. int listB_length = getLength(pHead2);
  12. // 计算长度差
  13. int dist = Math.abs(listA_length-listB_length);
  14. if(listA_length > listB_length){
  15. fast = pHead1;
  16. slow = pHead2;
  17. }else{
  18. fast = pHead1;
  19. slow = pHead2;
  20. }
  21. //让快指针 先走 dist
  22. for(int i = 0;i<dist;i++){
  23. fast = fast.next;
  24. }
  25. // 快慢引用同时向前走,如果两个引用指向的地址值相同,则说明当前节点是我们的相交节点。
  26. while(slow!=null && fast!=null ){
  27. if(fast==slow){
  28. return fast;
  29. }
  30. fast = fast.next;
  31. slow = slow.next;
  32. }
  33. return null;
  34. }
  35. public int getLength(ListNode a){
  36. ListNode ptr = a;
  37. int length = 0;
  38. if(ptr==null) return 0;
  39. while(ptr!=null){
  40. ptr = ptr.next;
  41. length++;
  42. }
  43. return length;
  44. }
  45. }

反转链表

题目: 输入一个链表,将其反转,并返回头结点。
示例: 输入:{1,2,3}
输出:{3,2,1}
思路:
① 判断链表是否为空,若为空,返回null。
② 定义三个引用,分别记为 prev、cur、next。表示前驱、当前、后继
③ 让cur指向原链表第一个节点。prev置为空,next置为空。
④ 在不断的遍历链表时,通过这三个引用,改变链表结构。
⑤ 即就是 用next保存当前链表的下一个节点,以便下次循环找到位置。将cur的next指向prev,然后移动prev至cur位置,cur又移动到next的位置。从而达到反转链表的目的。
image.png

在不断的遍历中,通过改变cur与prev之间的关系,从而进行反转链表的操作。

image.png
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
具体代码:

  1. public ListNode ReverseList(ListNode head) {
  2. ListNode cur = head;
  3. if(cur==null){
  4. return null;
  5. }
  6. ListNode prev = null;
  7. ListNode next = null;
  8. while(cur!=null){
  9. next = cur.next;
  10. cur.next = prev;
  11. prev = cur;
  12. cur = next;
  13. }
  14. return prev;
  15. }

合并两个有序链表

题目:将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的,且合并后新链表依然有序。

思路: ① 首先需要判断,给定的两个链表A、B是否为空。如果都为空,返回null。如果A为空,则返回B,如果B为空,则返回A。
② 比较两个A、B链表的第一个节点,若A链表的第一个节点小,则把A链表中第一个节点置为新链表的第一个节点。反之,将B链表中的第一个节点置为新链表的第一个节点。此时需要一个变量,指向新链表的第一个节点 记作:newHead。
③ 通过遍历比较两个链表中的节点值,哪个小,就把哪个移动到新链表中。

④ 给定一个临时指针指向新链表的第一个节点,记作 tmp。这个临时指针用于遍历新链表,并向新链表中插入值。

image.png

比较l1和l2的第一个节点,l1的小,此时l1向后走,newHead指向2。
image.png
image.png

接着 遍历l1和l2,哪个小,就插入到新链表中。

代码实现:

  1. public ListNode mergeTwoLists (ListNode l1, ListNode l2) {
  2. ListNode newHead = null; // 新链表的第一个节点
  3. // 先判断两链表是否为空
  4. if(l1 == null && l2 == null){
  5. return null;
  6. }
  7. //若l1为空,则直接返回l2
  8. if(l1 == null){
  9. return l2;
  10. }
  11. //若l2为空,则直接返回l1
  12. if(l2 == null){
  13. return l1;
  14. }
  15. // 比较两个链表第一个节点的大小,较小的做为新链表的第一个节点
  16. if(l1.val <=l2.val){
  17. newHead = l1;
  18. l1 = l1.next;
  19. }else{
  20. newHead = l2;
  21. l2 = l2.next;
  22. }
  23. ListNode tmp = newHead; // 用于遍历新链表
  24. while(l1!=null && l2!=null){
  25. if(l1.val < l2.val){
  26. tmp.next = l1;
  27. l1 = l1.next;
  28. }else{
  29. tmp.next = l2;
  30. l2 = l2.next;
  31. }
  32. tmp = tmp.next;
  33. }
  34. // 跳出while循环无非就是两种情况
  35. // 要么l1为空,说明已经把l1遍历完了,则把l2剩下的插入
  36. if(l1==null){
  37. tmp.next = l2;
  38. }
  39. if(l2==null){
  40. tmp.next = l1;
  41. }
  42. return newHead;
  43. }

判断链表中是否有环

题目: 判断一个链表中是否有环。
示例: image.png
该链表的环是由: 4 5 6 7 8组成的。

思路:
① 判断是否有环问题。我们可以把它看成类似于你追我跑的问题。
② 我们可以定义两个快慢引用,分别记作 slow、fast
③ 我们让快引用fast每次向前走两个,慢引用slow每次向前走一个。
④ 当slow==fast时,就说明,链表中有环。
⑤ 注意特殊情况: 当链表为空,或者链表中只有一个节点时,不构成环。
注意while循环里的条件判断

代码实现:

  1. public boolean hasCycle(ListNode head) {
  2. if(head==null || head.next == null){
  3. return false;
  4. }
  5. ListNode fast = head.next;
  6. ListNode slow = head;
  7. while(fast!=null && fast.next!=null && slow!=null && fast!=slow){
  8. fast = fast.next.next;
  9. slow = slow.next;
  10. }
  11. if(fast==slow){
  12. return true;
  13. }
  14. return false;
  15. }

删除链表中重复的元素

题目:
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次

示例:给出的链表为1->1->2 返回 1→2.
给出的链表为1→1→2→3→3,返回1→2→3.

思路:
① 先判断链表是否为空,若为空,直接返回null。
② 如果链表不为空,我们给定一个引用,记作tmp。目的是遍历链表
③ 通过遍历,判断前一个节点和下一个节点是否相等。即 tmp.val == tmp.next.val
④ 如果相等,则让tmp所指的节点的next域 指向这个节点的下一个的下一个,即tmp.next.next
⑤ 因为链表是有序的,如果不相等,则让tmp指向tmp的下一个节点,然后继续判断。
注意 while循环里的判断条件

比如: 1 -> 1 -> 1 -> 2
① ② ③ ④
节点① 和节点②相同,则让节点① 指向节点③,这个时候还不能让tmp进行tmp = tmp.next操作。
因为防止后面还有相同的元素,所以,这里我们继续比较。 此时tmp指向①。tmp的next指向③
再比较tmp.val == tmp.next.val? 如果相同,则继续删除,此时 节点①指向节点④ 然后在比较,①和④的值不相同,此时再让tmp = tmp.next;

代码实现:

  1. public ListNode deleteDuplicates (ListNode head) {
  2. if(head == null){return null;}
  3. ListNode tmp = head;
  4. while(tmp.next!=null){
  5. if(tmp.val == tmp.next.val){
  6. tmp.next = tmp.next.next;
  7. }else{
  8. tmp = tmp.next;
  9. }
  10. }
  11. return head;
  12. }

链表中环的入口节点

题目:
给出一个链表,找出该链表中环的入口节点。
示例:
该链表的环的入口节点为 2 。
单链表 - 图8
思路:
① 链表是否为空,若空,返回null。
② 若不空,判断是否存在环,若不存在环 返回null
定义两个指针slow,fast。让slow和fast都指向链表的头部,让fast每次往前走两步,slow每次往前走一步。如果fast和slow相遇了,则说明存在环。
③ 定义一个指针,用于标记slow和fast相遇的节点,记作meetNode。
④ 此时新定义一个新指针,指向链表的头部。记作 tmp。同时让tmp和meetNode以相同的步长往前走
⑤ 若tmp 与 meetNode 相等,则说明,tmp和meetNode共同指向的位置 就是环的入口节点。

证明
X,Y,Z分别为链表起始位置,环开始位置和两指针相遇位置。慢指针的速度设为 x,则快指针的速度为2x。
快慢指针均从X出发,在Z相遇。此时,慢指针走的距离为:(a+b),快指针走的距离为: a+b+n(b+c)。
所以, (a+b)/x = a+b+n(b+c)/2x,化简得:2(a+b) = a+b+n(b+c)
推出:
a = (n-1)b+nc = (n-1)(b+c)+c
即将此两指针分别放在起始位置和相遇位置,并以相同速度前进,当一个指针走完距离a时,另一个指针恰好走出 n-1圈加c的距离。
代码实现:

  1. public class Solution {
  2. public ListNode isCycle(ListNode head){
  3. if(head==null || head.next==null){
  4. return null;
  5. }
  6. ListNode slow = head;
  7. ListNode fast = head;
  8. while(slow!=null && fast!=null && fast.next!=null){
  9. slow = slow.next;
  10. fast = fast.next.next;
  11. if(slow == fast){
  12. return slow;
  13. }
  14. }
  15. return null;
  16. }
  17. public ListNode detectCycle(ListNode head) {
  18. ListNode meetNode = isCycle(head);
  19. ListNode tmp = head;
  20. if(meetNode != null){
  21. while(tmp!=meetNode){
  22. tmp = tmp.next;
  23. meetNode = meetNode.next;
  24. }
  25. return meetNode;
  26. }
  27. return null;
  28. }
  29. }

链表中倒数第k个节点

题目:
输入一个链表,输出该链表中倒数第k个结点。
如果该链表长度小于k,请返回空。
示例:
1->2->3->4->5->6,输出倒数第3个结点,输出结果为:4。

思路一:使用双指针
倒数第k个节点,就是正数第n-k+1个节点,我们可以定义两个指针分别记为:slow,fast。让fast先走k-1步。然后快慢指针一起向前走,当快指针走到最后一个节点时,慢指针所指的就是我们所要查找的节点。

代码实现:

  1. public ListNode FindKthToTail (ListNode pHead, int k) {
  2. ListNode tmp = pHead;
  3. int num = 0;
  4. while(tmp!=null){
  5. num++;
  6. tmp = tmp.next;
  7. }
  8. if(k>num||k<=0){
  9. return null;
  10. }
  11. ListNode fast = pHead;
  12. ListNode slow = pHead;
  13. for(int i = 0; i<k-1;i++){
  14. fast = fast.next;
  15. }
  16. while(fast.next!=null){
  17. slow = slow.next;
  18. fast = fast.next;
  19. }
  20. return slow;
  21. }

思路二:使用栈
我们可以让链表中的节点全部入栈,倒数第k个节点,就是栈中第k个节点。
代码实现:

  1. public ListNode FindKthToTail (ListNode pHead, int k) {
  2. if(k<=0){
  3. return null;
  4. }
  5. int tag = 0;
  6. int num = 0;
  7. Stack<ListNode> stack = new Stack<>();
  8. ListNode tmp = pHead;
  9. ListNode ptr = null;
  10. while(tmp!=null){
  11. num++;
  12. stack.push(tmp);
  13. tmp = tmp.next;
  14. }
  15. if(k>num){
  16. return null;
  17. }
  18. while(tag!=k && !stack.empty()){
  19. tag++;
  20. ptr = stack.pop();
  21. }
  22. return ptr;
  23. }

链表中的节点每k个一组进行翻转

题目:
将给出的链表中的节点每k个一组翻转,返回翻转后的链表如果链表中的节点数不是k的倍数,将最后剩下的节点保持原样你不能更改节点中的值,只能更改节点本身。要求空间复杂度O(1)

示例:
给定的链表是1→2→3→4→5
对于 k=2, 你应该返回2→1→4→3→5
对于 k=3, 你应该返回3→2→1→4→5

思路:
① 确定每次要翻转节点的个数,假设k=2,则说明,每回要翻转节点的个数为2个。这里需要一个变量,用于记作要翻转链表的范围,记作end。
② 我们可以给定一个假节点,它相当于整个链表的头节点。
③ 我们确定end的时候,需要进行for循环,例:对于链表 1->2 -> 3 -> 4 -> 5来说,如果k=2,则我们每两个节点进行翻转。而end的作用就是找到每组链表的尾节点。
④ 当我们翻转链表时,可以给定一个指针:start,记录这一组链表的第一个节点,于是,这一组链表现在有了start,end两个指针。然后我们执行reverse()函数,翻转该组链表,当翻转完成后,我们需要拼接后面的链表。
然后再次让end找到下一组链表的尾结点。

图示:蓝色表示要翻转的链表,红色表示待翻转的链表,黄色表示已翻转的链表
image.png

我们指定k=2。

image.png
代码实现:

  1. public ListNode reverseKGroup (ListNode head, int k) {
  2. if(head == null || head.next==null){
  3. return head;
  4. }
  5. // 定义一个假的节点
  6. ListNode dummy = new ListNode(0);
  7. // 假节点指向head
  8. dummy.next = head;
  9. // 初始化 pre和end指向dummy,pre指每次要翻转链表的第一个节点的上一个节点
  10. // end指每次要反转的链表的尾结点
  11. ListNode pre = dummy;
  12. ListNode end = dummy;
  13. while(end.next!=null){
  14. for(int i = 0; i<k && end!=null; i++){//这里的条件end!=null是为了防止还没有遍历k次,end
  15. //就为null,说明,剩下大的这组链表的个数小于k。
  16. end = end.next;
  17. }
  18. // 如果end为空,即需要反转的链表个数小于k,不反转
  19. if(end==null){
  20. break;
  21. }
  22. //先记录下end,next,方便后面连接链表
  23. ListNode next = end.next;
  24. // 与后面的链表断开连接。
  25. end.next = null;
  26. ListNode start = pre.next; //记录要进行反转的链表的头节点
  27. pre.next = reverse(start); //翻转链表,pre.next指向翻转后的链表。
  28. // 翻转下一组链表。
  29. start.next = next;
  30. pre = start;
  31. end = start;
  32. }
  33. return dummy.next;
  34. }
  35. private ListNode reverse(ListNode head){
  36. ListNode cur = head;
  37. ListNode behind = null;
  38. ListNode front = null;
  39. while(cur!=null){
  40. front = cur.next;
  41. cur.next = behind;
  42. behind = cur;
  43. cur = front;
  44. }
  45. return behind;
  46. }

删除链表中的重复元素(重复的元素全删完)

题目:
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。

示例: 给出的链表为1→2→3→3→4→4→5, 返回1→2→5.
给出的链表为1→1→1→2→3, 返回2→3.

思路:
双指针法。
定义一个假节点,demmy,类似于头结点的作用。
demmy.next = head;
① 定义一个cur用于记录当前节点
② 定义一个prev,用于记录cur的前一个节点。

③ 遍历链表,当cur.val != cur.next.val 时,cur和prev同时向前。
④ 当cur.next == cur.next.val时,定义一个指针temp。
该指针的作用是,向后遍历,直到找到一个节点的值不等于cur的值为止。
然后将prev.next 指向 temp。cur指向temp。

代码实现:

  1. public ListNode deleteDuplicates (ListNode head) {
  2. ListNode demmy = new ListNode(-1);
  3. demmy.next = head;
  4. ListNode cur = head;
  5. ListNode prev = demmy;
  6. while(cur!=null && cur.next!=null){
  7. if(cur.val == cur.next.val){
  8. ListNode temp = cur.next;
  9. while(temp!=null&&temp.val==cur.val){
  10. temp = temp.next;
  11. }
  12. prev.next = temp;
  13. cur = temp;
  14. }else{
  15. cur = cur.next;
  16. prev = prev.next;
  17. }
  18. }
  19. return demmy.next;
  20. }