算法刷题 链表专题_day02

116 阅读3分钟

旋转链表

  • 思路 : 旋转 k 个位置 , 第 k 个位置 即为新头, 1 -> 2 -> 3 -> 4 旋转2 个 4 -> 1 -> 2 -> 3 3 -> 4 -> 1-> 2 使得链表尾部连接头部 这样就可以循环遍历 找到 链表的 第 k - 1 个位置 断开后面的位置 即可完成旋转链表 。
  • 细节: 考虑到 k 可能大、移动次数过多、取k % n(链表长度) 效果是等效的。
  • 快慢指针法: 两个指针 f 和 s 、 f 先走 k 步,s 在动 ,f 走到头 、 s 刚好指向分界点、可以参考删除链表的第N个节点 把链表从分界点断开 、将尾部 f 指向 head 即可。
  • 双指针图解

cf209e3372f0dad3f7e2764470efbe7.jpg

code

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null || k == 0) return head;

        // 记录 newHead 的 前一个节点 用于 置空 防止 成环
        ListNode newHead = head;
        // 链表尾部 连接原来链表
        ListNode listTail = null;
        int n = 0;
        while (newHead != null) {
            n ++;
            // null 的前驱节点 即为 链表尾部
            listTail = newHead;
            newHead = newHead.next;
        }
        newHead = head;
        // 要走的步数
        int stp = k % n;
        if (stp == 0) return head;
        // 首尾连接 之后断开
        listTail.next = head;
        //  又换之后 遍历链表
        for (int i = 0 ; i < n - stp ; i ++) {
            listTail = listTail.next;
        }
        newHead = listTail.next;
        listTail.next = null;
        return newHead;
    }
}

快慢指针法

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null || k == 0) return head;
        // 调整之前 合法性判断
        ListNode f = head;
        int n = 0;
        while (f != null) {
            f = f.next;
            n ++;
        }
        k = k % n;
        if (k == 0) return head;
        f = head;
        ListNode s = head;
        for (int i = 0; i < k; i ++,f = f.next);   
        for ( ; f.next != null; s = s.next,f = f.next);
        f.next = head;
        head = s.next;
        s.next = null;
        return head;    
    }
}

 重排链表

  • 题意 :L1 -> L2 -> L3 -> ··· Ln 转化为 L1 --> Ln --> L2 --> Ln - 1 ···
  • 思路 : 很明显看到 正序 逆序 穿插放置的方式重排链表 所以正序 逆序 应该各占一半 画图模拟 奇数情况 和 偶数情况

5cb6f396488b99b0c2da9eccdcd8120.jpg

5d7fbd94fb040acb3ca1f704398b035.jpg

code (代码详细注释 )

class Solution {
    public void reorderList(ListNode head) {
        ListNode s = head;
        ListNode f = head;
        // 偶数 取 中下节点 
        // f.next != null && f.next.next != null  保证偶数节点 一定会 走到 链表尾部的前一个节点上(这仅仅是我的观点)
        // 奇数 取 中间节点
        for(; f.next != null && f.next.next != null; s = s.next , f = f.next.next );
        // 现在要做的是 翻转 [s , f] 区间的所有节点
        ListNode pre = s;
        s = s.next;
        // 断开 防止 有环
        pre.next = null; 
        // f 作为 反转节点的头节点
        f =   reserve(head,s,f) ;

        // System.out.print("翻转后的链表  为 : ");

        // while ( f != null) {
        //     System.out.print (f.val + " -- > ");
        //     f = f.next;
        // }
       pre = head;

        // 现在开始 合并 链表 
        ListNode nextP = null;
        ListNode nextF = null;
        while (pre != null && f != null) {
            nextP = pre.next;
            nextF = f.next;

            pre.next = f;
            pre = nextP;

            f.next = pre;
            f = nextF;
        }
    }

    // 类似 翻转链表 2 的 题目 code
    private ListNode reserve(ListNode head , ListNode l , ListNode r) {
        // r 是否走到尾部 、 没有走到尾部, 让他遍历到尾部 
        // 这个函数的目的是 翻转 [l,r] 区间上的所有 节点
        for (; r.next != null; r = r.next);
        ListNode next = null;
        ListNode pre = null;
        while ( l != null ) {
            next = l.next;
            l.next = pre;
            pre = l;
            l = next;
        }
        return pre;
    }   
}

合并两个有序链表

  • 思路 : 这个题比较简单 双指针 分别指向两个链表 合并即可。

code

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
       if (list1 == null ) return list2;
       if (list2 == null ) return list1;
        ListNode p1 = list1;
        ListNode p2 = list2;
        // 不知道谁的头小 避免复杂的逻辑判断 开辟一个新的 dump 节点
        ListNode dump = new ListNode();
        ListNode p = dump;
        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 dump.next;
    }
}

相交链表

  • 思路 :想象两根绳子 打结 、 打结处 拎起来 打结点 到 两个绳子的某一处一定是长度相等的。 利用这个思路 。 求出长短差 长绳子走掉 相差的路程,与短绳子一起奔向节点。如果具有这样的节点 两个节点一定同时到达 判断 哈希地址是否相等即可。

code

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) return null;
        int lthA = 0;
        int lthB = 0;
        ListNode pA = headA;
        ListNode pB = headB;
        while (pA != null) {
            lthA ++;
            pA = pA.next;
        }
        while (pB != null) {
            lthB ++;
            pB = pB.next;
        }
        pA = headA;
        pB = headB;
        int diff  = 0;
        if (lthA > lthB) {
            diff = lthA - lthB;
            while (diff -- > 0) {
                pA = pA.next;
            }
        }else {
            diff = lthB - lthA;
            while (diff -- > 0) {
                pB = pB.next;
            }
        }
        while (pA != null && pB != null) {
            // 有可能 刚巧 走完diff 就相遇了 就是这么巧
            if (pA == pB) return pA;
            pA = pA.next;
            pB = pB.next;
        }
        return null;
    }
}

一篇简洁的代码 coding 感觉原理上部分code 相同 但是不太好想 labuladong 题解地址

环形链表

  • 思路: 快慢指针法 快指针两步 、 慢指针 一步、 快指针相对于 慢指针永远快一步、也就是说 可以想象为 在 慢指针到达入环点 如果是环的话, 可以想象为 慢指针站那不动啦,快指针一步一步去找他相遇。
  • 细节 要保证 f 不为空 进入循环 保证循环的可行性。f.next.next 的 存在条件是 f.next 不为空。

code

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) return false; 
        ListNode s = head;
        ListNode f = head.next.next;
       
        while (f != null && f.next != null) {
            if (s == f) return true;
            s = s.next;
            f = f.next.next;
        }
        return false;
    }
}