21. 合并两个有序链表

图片.png

  1. //迭代做法
  2. class Solution {
  3. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  4. ListNode dummy=new ListNode();//创建1个虚拟节点 方便后面操作
  5. ListNode head=dummy;
  6. while(l1!=null&&l2!=null)
  7. {
  8. if(l1.val<l2.val)
  9. {
  10. head.next=l1;
  11. l1=l1.next;
  12. head=head.next;
  13. }
  14. else
  15. {
  16. head.next=l2;
  17. l2=l2.next;
  18. head=head.next;
  19. }
  20. }
  21. if(l1!=null)
  22. head.next=l1;
  23. if(l2!=null)
  24. head.next=l2;
  25. return dummy.next;
  26. }
  27. }
  28. //O(m+n) m,n分别是l1 l2的节点数目
  29. //O(1)
//递归做法
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null)
            return l2;
        else if(l2==null)
            return l1;
        else if(l1.val<l2.val)
        {
            l1.next=mergeTwoLists(l1.next,l2);
            return l1;
        }
        else
        {
            l2.next=mergeTwoLists(l1,l2.next);
            return l2;
        }

    }
}
//O(m+n) m,n分别是l1 l2的节点数目
//O(m+n)

23. 合并K个升序链表

图片.png

和合并两个升序链表的题目类似,这里主要的问题就是如何获得当前的K条链表中的K个头节点中的最小值,因为每一条链表都是升序的,因此每条链表的最小值一定是头节点 可以实验一个优先级队列(小顶堆,降序)加入K条链表的头节点,这样poll出来的就是当前的最小头节点 设链表有K条, 链表的节点总数是N 时间复杂度: 每次poll复杂度O(logK) 一共N次 所以O(NlogK) 空间复杂度:O(k) 优先级队列中最多存储K个节点

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> pq=new PriorityQueue<>(
            new Comparator<ListNode>()
            {
                public int compare(ListNode node1,ListNode node2)
                {
                    return node1.val-node2.val;
                }

            }
        );
        for(ListNode head:lists)
           if(head!=null)
                pq.offer(head);
        ListNode dummy=new ListNode();
        ListNode pre=dummy;
        while(!pq.isEmpty())
        {
            ListNode node=pq.poll();
            pre.next=node;
            pre=pre.next;
            if(node.next!=null)
                pq.offer(node.next);
        }
        return dummy.next;

    }
}

19. 删除链表的倒数第 N 个结点

图片.png

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {

        ListNode dummy=new ListNode();//加入虚拟节点 统一操作 不用单独考虑删除头节点的情况
        dummy.next=head;
        ListNode slow=dummy,fast=dummy;
        for(int i=0;i<=n;i++)//移动n+1步 因为加入了虚拟节点
            fast=fast.next;
        while(fast!=null)//找到倒数第n+1个节点 即待删除节点的前一个节点
        {
            fast=fast.next;
            slow=slow.next;
        }

        slow.next=slow.next.next;
        return dummy.next;

    }
}
//O(n)
//O(1)

876. 链表的中间结点

图片.png

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow=head,fast=head;//初始时快慢指针都指向头节点
        //快指针每次比慢指针多走1步 由于偶数节点是 返回中间的第2个节点 
        //代码可以将两种情况统一
        while(fast!=null&&fast.next!=null)
        {
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;//slow指向中间节点

    }
}
//O(n)
//O(1)

141. 环形链表

图片.png

思路1:实验set, 反复地添加节点,如果添加失败(遇到相同的节点)则说明有环,返回true 思路2:快慢指针,和前面一题代码类似,fast每次比slow多走一步,如果二者相遇说明有环,否则没有环

public class Solution {
    public boolean hasCycle(ListNode head) {
        HashSet<ListNode> set=new HashSet<>();
        while(head!=null)
        {
            if(!set.add(head))
                return true;
            head=head.next;
        }
        return false;

    }
}
//O(n)
//O(n)
public class Solution {
    public boolean hasCycle(ListNode head) {

        ListNode slow=head,fast=head;
        while(fast!=null&&fast.next!=null)
        {
            fast=fast.next.next;
            slow=slow.next;
            if(slow==fast)
                return true;
        }
        return false;

    }
}
//O(n)
//O(1)

142. 环形链表 II

图片.png

这一题不仅要求判断是否有环,还要直到环的入口在哪,参考下图,当slow和fast相遇时,设slow走了k步,则fast走了2k步,其中多走的k步其实就是环的长度,fast指针和slow指针相遇时fast指针只多走了1圈,因为fast的速度是slow的两倍

图片.png

假设环起点和相遇点距离为m, 则出发点巨鹿环起点距离是k-m 由于1圈的长度是k 因此相遇点到环起点的长度也是k-m, 因此相遇之后,slow从起点出发,fast从相遇点出发,此时二者速度相同,再次相遇的点就是环的入口点

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow=head,fast=head;
        while(fast!=null&&fast.next!=null)
        {
            fast=fast.next.next;
            slow=slow.next;
            if(slow==fast)
                break;
        }
        //如果fast满足这两个条件 说明没有环
        if(fast==null||fast.next==null)
            return null;
        slow=head;
        while(slow!=fast)
        {
            slow=slow.next;
            fast=fast.next;
        }
        return slow;

    }
}
//O(n)
//O(1)

160. 相交链表

图片.png
图片.png

  1. 如果两条链表没有交点,如示例3,则当p1==p2==null时循环结束,返回p1即返回null
  2. 如果两条链表有交点,记录公共部分的交点数目为x, A链表的节点数目是a, B链表的节点数目是b,

2.1 如果a==b 则p1走了a-x步 p2走了b-x步 a-x==b-x 没有走完直接相遇 2.2 如果a!=b 则p1走到交点处走了a+(b-x)步 p2走到交点处走了b+(a-x)步 二者也相等,也会相遇 也就是说p1 p2最多走a+b-x步就会相遇

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1=headA,p2=headB;
        while(p1!=p2)
        {
            p1=p1==null?headB:p1.next;
            p2=p2==null?headA:p2.next;
        }
        return p1;

    }
}
//O(n)
//O(1)

206. 反转链表

图片.png

思路1: 迭代 需要使用3个变量 pre cur next pre初始化为null,cur初始化为head 反转后的头节点就是pre

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre=null,cur=head,next;
        while(cur!=null)
        {
            next=cur.next;//next保存下一个节点的引用
            cur.next=pre;//当前节点的指针指向前一个节点  第一次指向null
            pre=cur;//当前节点变成前一个节点
            cur=next;//下一个节点变成当前节点
        }
        return pre;
    }
}
//O(n)
//O(1)

思路2: 递归

class Solution {
    public ListNode reverseList(ListNode head) {
         if(head==null||head.next==null)//当前节点为空或者只有1个节点直接返回
            return head;
        ListNode newHead=reverseList(head.next);//反转剩下的n-1个节点
        head.next.next=head;//假设head后面的节点已经反转完成 head的下一个节点应该指向head自己
        head.next=null;//head自己指向null
        return newHead;//返回新的头节点
    }
}
//O(n)
//O(n)

92. 反转链表 II

图片.png

思路1: 迭代 两遍扫描链表,先找到开始节点,再找到结束节点,然后按照上面一题的反准思路进行反转,反转完成以后需要改变区第left-1个节点和第left个节点的指向

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy=new ListNode(-1);
        dummy.next=head;
        ListNode pre=dummy;
        for(int i=1;i<left;i++)
        {
            pre=pre.next;//pre指向第left-1个节点
        }

        ListNode tail=pre.next;
        for(int i=left;i<=right;i++)
        {
            tail=tail.next;//tail指向第right+1个节点  在图中是节点5
        }
       ListNode pre2=null,cur=pre.next,next=null;
       //和普通的单链表反转类似 pre2最终指向反转后的头节点  在图中是节点4
       for(int i=left;i<=right;i++)
       {
           next=cur.next;
           cur.next=pre2;
           pre2=cur;
           cur=next;
       }
       //pre.next指向第left个节点  在图中即最后需要4-->5
       pre.next.next=tail;
       //pre指向反转后的头节点 在图中即1-->4
       pre.next=pre2;
       return dummy.next;

    }
}
//O(n)
//O(1)

思路2: 一遍扫描,只需要找出反转区间的开始节点,不用找出结束节点

图片.png
分为3个步骤:

  1. 当前节点指向下下一个节点
  2. 下一个节点指向当前节点
  3. 前一个节点指向下一个节点
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy=new ListNode(-1);
        dummy.next=head;
        ListNode pre=dummy;
        for(int i=1;i<left;i++)
        {
            pre=pre.next;//pre指向第left-1个节点
        }
       ListNode cur=pre.next,next;
       for(int i=left;i<right;i++)
       {
           next=cur.next;
           cur.next=next.next;//当前节点指向下下一个节点
           next.next=pre.next;//下一个节点指向当前节点
           pre.next=next;//前一个节点指向下一个节点
       }
       return dummy.next;

    }
}
//O(n)
//O(1)

递归: 先定义一个函数:reverseN(head,n) 用来反转链表的前n个节点 即反转区间为[1,n] 而对于题目要求的是区间[left,right] 如何将left转化为1? 依据相对性,left对于head而言是第left, 而对于head.next而言则是第left-1, 对于head.next.next而言是left-2…. 依次类推,最终会得到left==1, 此时转化为反转区间[1-n]个节点, 对于反转reverseN(head,n)继续递归划分reverseN(head.next,n-1) 假设后面head.next后面的都反转好了,只需要改变head的指向和head.next的指向即可

class Solution {
    ListNode suc;
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if(left==1)
        {
            return reverseN(head,right);
        }
        head.next=reverseBetween(head.next,left-1,right-1);
        return head;

    }
    public ListNode reverseN(ListNode head,int n)
    {
        if(n==1)
        {
            suc=head.next;//记录反转区间结束的下一个节点 当n==链表结点数时 suc=null 变成了普通的单链表反转
            return head;
        }
        ListNode newHead=reverseN(head.next,n-1);
        head.next.next=head;
        head.next=suc;//全部反转时这里是head.next=null  由于这里是部分反转因此需要指向后继节点
        return newHead;
    }

}
//O(n)
//O(n)