刷题笔记3——单链表

单链表刷题小结

  • 注意头节点的使用
  • java中没有直接访问内存地址的用法,每个节点都是引用,可以 .val 或者 .next进行访问
  • 如果是需要返回结果,最好是返回一个新建的链表,如果返回旧的结果那么在head长度为1时是无论怎样都消不掉的,我遇到这样的情况比如说,p2.next = p1; p2.next = p2.next.next; return head; 那么我这样操作目的是想通过p2来删除链表中的某个节点,大但是当head长度为1时,p2的改变是改变不了head的,就算p2=null,head仍然长度为1
  • 学会了优先级队列这个概念
  • 还有一些比较新奇的思路,比如补全链表,双指针,都需要常常回顾

21. 合并两个有序链表

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1), p = dummy;
        ListNode p1 = l1, 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 = p1;
        }

        if (p2!=null){
            p.next = p2;
        }

        return dummy.next;
    }
}

86. 分隔链表

class Solution {
    public ListNode partition(ListNode head, int x) {
        
        ListNode p0 = head; 
        ListNode dummy1 = new ListNode(-1), p1 = dummy1;
        ListNode dummy2 = new ListNode(-1), p2 = dummy2;

        while(p0 != null){
            if(p0.val < x){
                p1.next = p0;
                p1 = p1.next;
                p0 = p0.next;
                p1.next = null;
            }
            else{
                p2.next = p0;
                p2 = p2.next;
                p0 = p0.next;
                p2.next = null;
            }
        }

        p1.next = dummy2.next;
        return dummy1.next;

    }
}

23. 合并 K 个升序链表

  • 这个题直接看的时候,思路就是给k个链表都安排一个指针,取三个指针中最小的值给到新的列表,需要注意读取链表的数量,或者将链表指针存到数组里边;要不就是递归?
  • 但是难点是在于求k个节点的最小节点???答案中是用优先级队列(二叉堆)数据结构。每次移动一个指针到下一个位置,堆的数据结构可以直接给出最小的值而不需要重新排列,原先的时间复杂度是M序列N总序列长度,现在的复杂度是O(NlogM)
- lists.length 求长度不需要加括号
- // 优先队列的用法
- PriorityQueue<ListNode> pq = new PriorityQueue<>(lists.length,(a,b)->(a.val-b.val));
- // 定义方式
- ListNode dummy = new ListNode(-1);
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length==0){
            return null;
        }

        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        
        PriorityQueue<ListNode> pq = new PriorityQueue<>(lists.length,(a,b)->(a.val-b.val)); // 如果是负数,a小

        for (ListNode head : lists){
            if(head!=null){
                pq.add(head);
            }
        }

        while(!pq.isEmpty()){
            ListNode temp = pq.poll();
            p.next = temp;
            if(temp.next!=null){
                pq.add(temp.next);
            }
            p = p.next;
            p.next = null;
        }

        return dummy.next;


    }
}

19. 删除链表的倒数第 N 个结点(被困住一晚上的结果返回问题)

  • 考虑的方法是:在进行正向读取的时候,就将整个列表逆序,要么就是新读取一个节点就插入到前边,读取结束之后从头节点开始读取k个即可(反思:这种情况只能读到k节点的值,无法读到next)
  • 如果是删除该节点,那么就是设置成双向节点,读到尾巴之后,返回来修改倒数第k个(很繁琐)
  • 正确答案是两个人赛跑,其中一个人先放到k处,然后两人同时开始,在两人速度一致的情况下,一人到达

我的方法:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode p1 = head;
        ListNode p2 = new ListNode(0, head);
        ListNode p3 = p2;
        // 安全,这里我的思路是只有走n-1步骤,才会正确达到第n个数,所以我的p1一直指向了安全区域
        for (int i=0;i<(n-1);i++){
            p1 = p1.next;
        }
		// 当p1的下一个是null,那说明p1到了结尾节点
        while(p1.next!=null){
            p1 = p1.next;
            p2 = p2.next;
        }
		// 在此过程中 p1 和 p2 的长度一直为n+1,所以当p1到达结尾,p2在倒数n+1上停着,这时将p2后边的数字删除掉即可
        // 在删除的时候尤其注意,不能直接返回head,否则当只有一个值的时候,永远无法被删除,所以需要重新开启一个链表即p2,最后的返回结果也是p2 
        p2.next = (p2.next).next;
        return p3.next;
    }
}

答案结果

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        ListNode first = head;
        ListNode second = dummy;
        // first可以指向n个数的后边null,first和second之间的距离是n+2,所以需要first到达null,second才到达n+1
        for (int i = 0; i < n; ++i) {
            first = first.next;
        }
        while (first != null) {
            first = first.next;
            second = second.next;
        }
        second.next = second.next.next;
        ListNode ans = dummy.next;
        return ans;
    }
}

876. 链表的中间结点

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode p1 = head;
        ListNode p2 = head;
        
        while(p2.next!=null){
            p1 = p1.next;
            p2 = p2.next;
            if(p2.next!=null){
                p2 = p2.next;
            }
        }
        return p1;

    }
}

142. 环形链表 II (一遍AC)

  • 这个思路之前有学过,所以简单推导一下就得到了:简单来说就是环形操场跑步的问题,同时出发,一个速度为v另一个速度为2v,两人相遇的时候,说明快的人比慢的人多走了一圈,相遇的点也正好是快的人一半路程的位置。
  • 这对比环形链表问题也是同理的,即开始节点到相遇节点距离 = 相遇节点环形到下次该节点 = 环形的长度
  • 有了以上推断,那么在开始位置设置指针 p1, 相遇位置设置指针p2, 两个指针相遇的地方就是这个豁口啦
  • 引用labuladong的图解
    刷题笔记3——单链表_第1张图片
    刷题笔记3——单链表_第2张图片
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode p1 = head;
        ListNode p2 = head;
        if (head==null) return null;
        while(true){
            if(p2.next == null){
                return null;
            }
            else if(p2.next.next == null){
                return null;
            }
            else{
                p1 = p1.next;
                p2 = p2.next.next;
            }
            if (p1 == p2){
                break;
            }
        }
        ListNode p3 = head;
        while(p2!=p3){
            p2 = p2.next;
            p3 = p3.next;
        }
        return p2;
    }
}

160. 相交链表

  • 直接想法是,遍历一个链表存为hash,遍历另一个链表检查是否有重复,但是这样的时间复杂度为 O(m+n).,空间复杂度为O(n).
  • 答案的思路是补齐,因为现在的问题是两个链表不同步,如果在每个链表的前边加上与另一链表相同的长度(里边的值无所谓),那么两个链表在评估的时候就是对齐的,这里不用担心前边加的链表会影响结果,因为前边的链表是在不对齐的状态下,如果真的有相同位置的值相同,那么说明两条链表的长度是一样的,因此不会对结果产生影响。
  • leecode评论区:对的人错过了还是会相遇, 错的人相遇了也是NULL
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA;
        ListNode p2 = headB;
        int flag1 = 0;
        int flag2 = 0;

        if (headA == null || headB == null) return null;

        while(p1 != null || flag1==0){
            if (flag1==0 && p1 == null){
                p1 = headB;
                flag1 = 1;
            }

            if(flag2==0 && p2 == null){
                p2 = headA;
                flag2 = 1;
            }

            if (p1 == p2) return p1;
            p1 = p1.next;
            p2 = p2.next;
        }

        return null;
    }
}
  • 官方题解

  • 最开始不太理解,为什么没有处理 第一次为null和第二次为null的情况,

    • 第一次:如果在过程中二者同时为null,那么其实不需要再遍历拼接段之后的链表,因为两个链表的长度一致,遍历过一次没有重复那么返回null即可
    • 第二次:如果前期都没有匹配,那么最后Pa = null Pb= null一定是同时到达的,满足截至条件
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) return null;
        
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
}

  • 看到题解中还有一种很巧妙的思路就是把某一链表的首尾连接起来,变成上述寻找豁口的问题,但是这样会改变链表原有的结构

刷题笔记3——单链表_第3张图片

你可能感兴趣的:(笔记,数据结构,链表,leetcode,算法)