【剑指offer刷题记录 java版】链表双指针

本系列文章记录labuladong的算法小抄中剑指offer题目


【剑指offer刷题记录 java版】链表双指针

  • 剑指 Offer II 025. 链表中的两数相加
  • 剑指 Offer 25. 合并两个排序的链表
  • 剑指 Offer 52. 两个链表的第⼀个公共节点
  • 剑指 Offer II 021. 删除链表的倒数第 n 个结点
  • 剑指 Offer II 022. 链表中环的⼊⼝节点
  • 剑指 Offer II 023. 两个链表的第⼀个重合节点
  • 剑指 Offer II 078. 合并排序链表(困难)
  • 剑指 Offer 49. 丑数
  • 剑指 Offer 18. 删除链表的节点
  • 剑指 Offer II 061. 和最⼩的 k 个数对
  • 剑指 Offer II 027. 回⽂链表
  • 总结


剑指 Offer II 025. 链表中的两数相加

题目链接:https://leetcode.cn/problems/lMSNwu/
本题基础题:https://leetcode.cn/problems/add-two-numbers/ 基础题解法:

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode p=l1,q=l2;//双指针
        int temp = 0;//进位
        ListNode res = new ListNode();//虚拟头节点(用于返回)
        ListNode node = res;//结果指针
        while(p!=null || q!=null || temp!=0){
            int val = temp;//加上进位
            if(p!=null){
                val+=p.val;
                p=p.next;
            }
            if(q!=null){
                val+=q.val;
                q=q.next;
            }
            temp=val/10;//记录本次进位
            node.next=new ListNode();
            node=node.next;
            node.val=val%10;
        }
        return res.next;
    }
}

【剑指offer刷题记录 java版】链表双指针_第1张图片
【剑指offer刷题记录 java版】链表双指针_第2张图片
利用栈的特性存储链表每个节点的数据。

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Deque<Integer> s1 = new LinkedList();
        Deque<Integer> s2 = new LinkedList();
        while(l1!=null){
            s1.offerLast(l1.val);
            l1=l1.next;
        } 
        while(l2!=null){
            s2.offerLast(l2.val);
            l2=l2.next;
        } 
        int temp=0;//记录进位
        ListNode res = new ListNode();//结果指针
        
        while(!s1.isEmpty() || !s2.isEmpty() || temp!=0){
            int val = temp;
            if(!s1.isEmpty()){
                val += s1.pollLast();
            }
            if(!s2.isEmpty()){
                val += s2.pollLast();
            }
            temp = val/10;
            ListNode node = new ListNode(val%10);
            node.next = res.next;
            res.next = node;
        }
        return res.next;
    }
}

剑指 Offer 25. 合并两个排序的链表

题目链接:https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/
【剑指offer刷题记录 java版】链表双指针_第3张图片

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode p1=l1,p2=l2;
        ListNode res = new ListNode();
        ListNode node = res;
        while(p1!=null && p2!=null){
            if(p1.val<p2.val){
                node.next = p1;
                p1=p1.next;
            }else{
                node.next = p2;
                p2=p2.next;
            }
            node = node.next;
        }
        if(p1!=null){
            node.next = p1;
        }
        if(p2!=null){
            node.next = p2;
        }
        return res.next;
    }
}

剑指 Offer 52. 两个链表的第⼀个公共节点

题目链接:https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/
【剑指offer刷题记录 java版】链表双指针_第4张图片
【剑指offer刷题记录 java版】链表双指针_第5张图片
【剑指offer刷题记录 java版】链表双指针_第6张图片
在逻辑上连接两个链表,使相交部分再其后半部分对齐。
【剑指offer刷题记录 java版】链表双指针_第7张图片

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

剑指 Offer II 021. 删除链表的倒数第 n 个结点

题目链接:https://leetcode.cn/problems/SLwz0R/
【剑指offer刷题记录 java版】链表双指针_第8张图片【剑指offer刷题记录 java版】链表双指针_第9张图片
本题解法类似固定长度的滑动窗口。需要使用虚拟头节点避免空指针的状况。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 虚拟头结点
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        ListNode p1 = dummy;
        //先走n+1步
        for (int i = 0; i < n+1; i++) {
            p1 = p1.next;
        }
        ListNode p2 = dummy;
        while(p1!=null){
            p1=p1.next;
            p2=p2.next;
        }
        p2.next=p2.next.next;
        return dummy.next;
    }
}

剑指 Offer II 022. 链表中环的⼊⼝节点

基础题:https://leetcode.cn/problems/linked-list-cycle/ 链表找环解法:

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

题目链接:https://leetcode.cn/problems/c32eOV/
【剑指offer刷题记录 java版】链表双指针_第10张图片
【剑指offer刷题记录 java版】链表双指针_第11张图片
【剑指offer刷题记录 java版】链表双指针_第12张图片
先快慢移动,相交后把一个放回起点,另一个再交点,这两个指针以相同速度移动,第二次相交位置即为环的起点。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head, fast = head;
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast){
                slow=head;
                while(slow!=fast){
                    slow=slow.next;
                    fast=fast.next;
                }
                return slow;
            }
        }
        return null;
    }
}

剑指 Offer II 023. 两个链表的第⼀个重合节点

题目链接:https://leetcode.cn/problems/3u1WK4/
【剑指offer刷题记录 java版】链表双指针_第13张图片
【剑指offer刷题记录 java版】链表双指针_第14张图片
【剑指offer刷题记录 java版】链表双指针_第15张图片
进阶:能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?
【思路】两个指针分别遍历链表AB和链表BA,因为不同的元素数量一致,因此交点会在后半部分对齐。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1=headA,p2=headB;
        while(p2!=p1){
        	//遍历链表AB
            if(p1==null){
                p1=headB;
            }else{
                p1=p1.next;
            }
			//遍历链表AB
            if(p2==null){
                p2=headA;
            }else{
                p2=p2.next;
            }
        }
        return p1;//如果没有交点,最后两个指针都为null
    }
}

剑指 Offer II 078. 合并排序链表(困难)

题目链接:https://leetcode.cn/problems/vvXgSW/
【剑指offer刷题记录 java版】链表双指针_第16张图片
【剑指offer刷题记录 java版】链表双指针_第17张图片

利用优先级队列,每次输出当前最小的节点
优先级队列基础知识

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));
        // 将 k 个链表的头结点加入最小堆
        for (ListNode head : lists) {
            if (head != null)
                pq.add(head);
        }

        while (!pq.isEmpty()) {
            // 获取最小节点,接到结果链表中
            ListNode node = pq.poll();
            p.next = node;
            if (node.next != null) {
                pq.add(node.next);
            }
            // p 指针不断前进
            p = p.next;
        }
        return dummy.next;
    }
}

剑指 Offer 49. 丑数

基础题:263. 丑数
基础题解法:

class Solution {
    public boolean isUgly(int n) {
        if (n <= 0) return false;
        // 如果 n 是丑数,分解因子应该只有 2, 3, 5
        while(n%2==0){n/=2;}//该数内部还有因数2时,除以2
        while(n%3==0){n/=3;}//该数内部还有因数3时,除以3
        while(n%5==0){n/=5;}//该数内部还有因数5时,除以5
        return n==1;
    }
}

题目链接:https://leetcode.cn/problems/chou-shu-lcof/
【剑指offer刷题记录 java版】链表双指针_第18张图片
巧妙地合并三个链表,具体思路可以参考264. 丑数 II 中labuladong的可视化过程

class Solution {
    public int nthUglyNumber(int n) {
        // 可以理解为三个指向有序链表头结点的指针
        int p2 = 1, p3 = 1, p5 = 1;
        // 可以理解为三个有序链表的头节点的值
        int product2 = 1, product3 = 1, product5 = 1;
        // 可以理解为最终合并的有序链表(结果链表)
        int[] ugly = new int[n + 1];
        // 可以理解为结果链表上的指针
        int p = 1;

        // 开始合并三个有序链表
        while (p <= n) {
            // 取三个链表的最小结点
            int min = Math.min(Math.min(product2, product3), product5);
            // 接到结果链表上
            ugly[p] = min;
            p++;
            // 前进对应有序链表上的指针
            if (min == product2) {
                product2 = 2 * ugly[p2];
                p2++;
            }
            if (min == product3) {
                product3 = 3 * ugly[p3];
                p3++;
            }
            if (min == product5) {
                product5 = 5 * ugly[p5];
                p5++;
            }
        }
        // 返回第 n 个丑数
        return ugly[n];
    }
}

剑指 Offer 18. 删除链表的节点

题目链接:https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/
【剑指offer刷题记录 java版】链表双指针_第19张图片

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode dummy = new ListNode();
        dummy.next=head;
        ListNode p = dummy;
        while(p!=null && p.next!=null){
            if(p.next.val==val){
                p.next=p.next.next;
            }
            p=p.next;
        }
        return dummy.next;
    }
}

剑指 Offer II 061. 和最⼩的 k 个数对

题目链接:https://leetcode.cn/problems/qn8gGX/
【剑指offer刷题记录 java版】链表双指针_第20张图片
【剑指offer刷题记录 java版】链表双指针_第21张图片

本质也是合并K个有序链表,可以用优先级队列处理。

class Solution {
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        // 存储三元组 (num1[i], nums2[i], i)
        // i 记录 nums2 元素的索引位置,用于生成下一个节点
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> {
            // 按照数对的元素和升序排序
            return (a[0] + a[1]) - (b[0] + b[1]);
        });
        // 按照 23 题的逻辑初始化优先级队列(将每个链表的链表头加入优先级队列)
        for (int i = 0; i < nums1.length; i++) {
            pq.offer(new int[]{nums1[i], nums2[0], 0});
        }

        List<List<Integer>> res = new ArrayList<>();
        // 执行合并多个有序链表的逻辑
        while (!pq.isEmpty() && k > 0) {
            int[] cur = pq.poll();
            k--;
            // 链表中的下一个节点加入优先级队列
            int next_index = cur[2] + 1;
            if (next_index < nums2.length) {
                pq.add(new int[]{cur[0], nums2[next_index], next_index});
            }

            List<Integer> pair = new ArrayList<>();
            pair.add(cur[0]);
            pair.add(cur[1]);
            res.add(pair);
        }
        return res;
    }
}

剑指 Offer II 027. 回⽂链表

题目链接:https://leetcode.cn/problems/aMhZSa/
【剑指offer刷题记录 java版】链表双指针_第22张图片
【剑指offer刷题记录 java版】链表双指针_第23张图片

方法一:时间复杂度 O(n) ,空间复杂度 O(n) ,将值复制到数组中后用双指针法

class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> vals = new ArrayList<Integer>();

        // 将链表的值复制到数组中
        ListNode currentNode = head;
        while (currentNode != null) {
            vals.add(currentNode.val);
            currentNode = currentNode.next;
        }

        // 使用双指针判断是否回文
        int front = 0;
        int back = vals.size() - 1;
        while (front < back) {
            if (!vals.get(front).equals(vals.get(back))) {
                return false;
            }
            front++;
            back--;
        }
        return true;
    }
}

方法二:时间复杂度 O(n) ,空间复杂度 O(1) ,双指针找到中间节点,反转链表然后对比

class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode slow=head,fast=head;
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        if(fast!=null){
            // 链表有奇数个节点
            slow=slow.next;
        }else{
            // 链表有偶数个节点,什么都不做
        }

        // 从slow开始反转后半部分链表
        ListNode pre = null;
        ListNode cur = slow;
        while(cur!=null){
            ListNode next=cur.next;
            cur.next=pre;
            pre =cur;
            cur = next;
        }
        //此时pre为反转后链表的头节点
        while(pre!=null){
            if(head.val!=pre.val){
                return false;
            }
            head=head.next;
            pre=pre.next;
        }
        return true;
    }
}

总结

  1. 利用虚拟头节点可以简化链表的创建,创建时始终记得要有一个指针。
  2. 链表的增删改可以通过虚拟头节点来统一。
  3. 合并 k 个有序链表可以利用优先级队列来实现,流程是:先将所有链表的头节点传入优先级队列,没弹出一个就补上该链表的下一个元素,直至满足输出需求。

你可能感兴趣的:(剑指offer刷题记录,java,链表,开发语言)