算法题解(链表篇)

链表基础

什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点是又两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链接的入口点称为列表的头结点也就是head。

链表的类型:

单链表

单链表中的节点只能指向节点的下一个节点。
在这里插入图片描述
双链表

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

双链表 既可以向前查询也可以向后查询。在这里插入图片描述
循环链表

循环链表,顾名思义,就是链表首尾相连。

循环链表可以用来解决约瑟夫环问题。
在这里插入图片描述
链表的存储方式

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

链表是通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。在这里插入图片描述
链表的定义

public class ListNode{ 	
 	int value;
 	ListNode next;
 	ListNode(){};
 	ListNode(int value) { this.value = value; }
 	ListNode(int value, ListNode next){
 		this.value = value;
 		this.next = next;
 	}
 }

链表的操作:

删除节点

在这里插入图片描述

添加节点
在这里插入图片描述


性能分析
在这里插入图片描述

141. 环形链表 - 2.12 (快慢指针)

141. 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

在这里插入图片描述


解析://快慢指针,慢指针走一步,快指针走两步

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

//快慢指针,慢指针走一步,快指针走两步
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow = head; //慢指针
        ListNode fast = head; //快指针
        while(fast != null && fast.next != null){
            slow = slow.next; //慢指针走一步
            fast = fast.next.next; //快指针走两步
            if(slow == fast) return true; //如果相等,说明有环
        }
        return false;
    }
}

在这里插入图片描述

142. 环形链表 II - 2.12 (快慢指针)

142. 环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

在这里插入图片描述


解析:快慢指针,相遇后将慢指针指向head,然后快慢指针同时移动,相遇即为入环节点

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

//快慢指针,相遇后将慢指针指向head,然后快慢指针同时移动,相遇即为入环节点
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head; //慢指针
        ListNode fast = head; //快指针
        while(fast != null && fast.next != null){
            slow = slow.next; //慢指针走一步
            fast = fast.next.next; //快指针走两步
            if(slow == fast) break; //如果相等,先跳出循环
        }
        //如果fast为空,则没有环
        if(fast == null || fast.next == null) {
            return null;
        }

        slow = head; //慢指针指向head
        while(slow != fast){ //同时移动,快指针在环内走一圈,慢指针从头开始走,相遇时即跳出循环
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

在这里插入图片描述

876. 链表的中间结点 - 2.12(快慢指针)

876. 链表的中间结点
在这里插入图片描述


解析:快慢指针,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。

/**
 * 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; }
 * }
 */

//快慢指针,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。
class Solution {
    public ListNode middleNode(ListNode head) {
        // 快慢指针初始化指向 head
        ListNode slow = head;
        ListNode fast = head;
        // 快指针走到末尾时停止
        while (fast != null && fast.next != null) {
            // 慢指针走一步,快指针走两步
            slow = slow.next;
            fast = fast.next.next;
        }
        // 慢指针指向中点
        return slow;
    }
}

在这里插入图片描述

203. 移除链表元素 - 简单 - 10/18(遍历查找)

203. 移除链表元素 - 简单
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

在这里插入图片描述在这里插入图片描述


解析:设置一个虚拟头结点在进行删除操作。

在这里插入图片描述

/**
 * 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 removeElements(ListNode head, int val) {
        // 因为删除可能涉及到头节点,所以设置dummy节点,统一操作
        ListNode dummy = new ListNode(0);
        dummy.next = head; 
        ListNode h = dummy;
        
        while(h.next != null){
            if(h.next.val == val){ //如果找到val,则跳过
                h.next = h.next.next;
            } else {
                h = h.next; //没有找到就移动到下一个
            }
        }
        return dummy.next;
    }
}

在这里插入图片描述

707. 设计链表 - 中等 - 10/18

707. 设计链表 - 中等

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

在这里插入图片描述

//单链表
class ListNode{
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val = val;
    }
}

class MyLinkedList {
    int size;
    ListNode head;
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
    
    public int get(int index) {
        if(index < 0 || index >= size) return -1;
        ListNode curr = head;
         //因为包含一个虚拟头节点,所以查找第 index+1 个节点
        for(int i=0; i<index+1; ++i){
            curr = curr.next;
        }
        return curr.val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        if(index > size) return;
        if(index < 0) index = 0;
        ++size; //大小+1
        ListNode pred = head;
        for(int i=0; i<index; i++){
            pred = pred.next;
        }
        //创建新的节点
        ListNode toAdd = new ListNode(val);
        toAdd.next = pred.next;
        pred.next = toAdd;
    }
    
    public void deleteAtIndex(int index) {
        if(index<0 || index>=size) return;
        --size;
        ListNode pred = head;
        for(int i=0; i<index; i++){
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

在这里插入图片描述

206. 反转链表 - 中等 - 10/19(双指针 | 递归)

206. 反转链表 - 中等

在这里插入图片描述在这里插入图片描述


解法一: 双指针法

首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。

然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。

为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。

接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。

最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。

在这里插入图片描述

在这里插入图片描述

/**
 * 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 reverseList(ListNode head) {
        ListNode pred = null; //前一个结点
        ListNode curr = head; //当前结点
        ListNode temp = null; //临时保存结点
        while(curr != null){
            temp = curr.next; //保存下一个结点
            curr.next = pred; //反转
            pred = curr; //移动
            curr = temp; //移动
        }
        return pred;
        

    }
}

在这里插入图片描述

解法二:递归反转链表

/**
 * 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 reverseList(ListNode head) {
        //如果链表只有一个节点的时候反转也是它自己,直接返回即可
        if(head == null || head.next == null) return head;
        ListNode last = reverseList(head.next);
        head.next.next = head;
        head.next = null; //链表的末尾要指向 null
        return last;
    }
}

在这里插入图片描述

92. 反转链表 II(反转链表的一部分) - 2.13(头插法)

92. 反转链表 II
在这里插入图片描述


解析:

1、我们定义两个指针,分别称之为 g(guard 守卫) 和 p(point)。
我们首先根据方法的参数 m 确定 g 和 p 的位置。将 g 移动到第一个要反转的节点的前面,将 p 移动到第一个要反转的节点的位置上。我们以 m=2,n=4为例。
2、将 p 后面的元素删除,然后添加到 g 的后面。也即头插法。
3、根据 m 和 n 重复步骤(2)
4、返回 dummyHead.next

在这里插入图片描述

在这里插入图片描述

/**
 * 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 reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(-1); //虚拟头节点
        dummy.next = head;

        ListNode begin = dummy;  //开始反转的位置
        ListNode end = dummy.next; //结束反转的位置

        //将指针移动到相应的位置
        for(int i=0; i<left-1; i++){ 
            begin = begin.next;
            end = end.next;
        }
        //头插法插入节点 比如: dummy -> 1 -> 3 -> 2 -> 4 -> 5
        for(int i=0; i<right-left; i++){
            ListNode temp = end.next; //接收end的后一个节点
            end.next = end.next.next; //删除end的后一个节点
            temp.next = begin.next; //将刚删除的节点指向begin的next节点
            begin.next = temp; //begin再指向 刚删除的节点
        }

        return dummy.next;
    }
}

在这里插入图片描述

25. K 个一组翻转链表 - 2.13(迭代和递归)

25. K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

进阶:

你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

在这里插入图片描述


解析:

1、先反转以 head 开头的 k 个元素。
在这里插入图片描述
2、将第 k + 1 个元素作为 head 递归调用 reverseKGroup 函数。

在这里插入图片描述

3、将上述两个过程的结果连接起来。
在这里插入图片描述

/**
 * 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 reverseKGroup(ListNode head, int k) {
        if(head == null) return head;
        ListNode begin = head; //一组的开始位置
        ListNode end = head; //一组的结束位置

        //先将end指针移动k个单位,即为一组
        for(int i=0; i<k; i++){
            if(end == null) return head; //不足 k 个,不需要反转,base case
            end = end.next; //移动
        }

        ListNode newHead = reverse(begin,end); //反转一组节点
        begin.next = reverseKGroup(end, k); //递归反转后续链表 并 连接起来
        return newHead;
    }

    //反转链表[a,b)节点
    private ListNode reverse(ListNode a, ListNode b){
        ListNode pre = null;
        ListNode cur = a;
        ListNode temp = a;
        while(cur != b){
            temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

在这里插入图片描述

234. 回文链表 - 2.13(递归)

234. 回文链表
在这里插入图片描述


解析:先用快慢指针找到中点,然后反转后半部分链表,比较前后链表的值是否相等,若不相等则不是回文链表,反之则是。

/**
 * 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 boolean isPalindrome(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        //快慢指针 找 中点
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        //如果fast不为空,说明为奇数,slow还要继续走一步
        if(fast != null){
            slow = slow.next;
        }

        //比较前后半部分是否对称
        ListNode left = head;
        ListNode right = reverse(slow);
        while(right != null){
            if(left.val != right.val){
                return false;
            }
            left = left.next;
            right = right.next;
        }
        return true;


    }

    //反转后半部分的链表
    private ListNode reverse(ListNode head){
        ListNode pre = null;
        ListNode cur = head;
        ListNode temp = head;
        while(cur != null){
            temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;

    }
}

在这里插入图片描述

24. 两两交换链表中的节点 - 中等 - 10/19(递归)

24. 两两交换链表中的节点

参考:三道题套路解决递归问题

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

在这里插入图片描述在这里插入图片描述


解析:使用递归

直接上三部曲模版:

  • 找终止条件。 什么情况下递归终止?没得交换的时候,递归就终止了呗。因此当链表只剩一个节点或者没有节点的时候,自然递归就终止了。
  • 找返回值。 我们希望向上一级递归返回什么信息?由于我们的目的是两两交换链表中相邻的节点,因此自然希望交换给上一级递归的是已经完成交换处理,即已经处理好的链表。
  • 本级递归应该做什么。 结合第二步,看下图!由于只考虑本级递归,所以这个链表在我们眼里其实也就三个节点:head、head.next、已处理完的链表部分。而本级递归的任务也就是交换这3个节点中的前两个节点,就很easy了。

在这里插入图片描述

/**
 * 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 swapPairs(ListNode head) {
        //终止条件:链表只剩一个节点或者没节点了,没得交换了。返回的是已经处理好的链表
        if(head == null || head.next == null) return head;
        //声明当前head节点的下一个节点为 next
        ListNode next = head.next;
        //交换位置
        head.next = swapPairs(next.next);
        next.next = head;
        //返回给上一级的是当前已经完成交换后,即处理好了的链表部分
        return next;


    }
}

在这里插入图片描述

19. 删除链表的倒数第 N 个结点 - 中等 - 10/19 (快慢指针)

9. 删除链表的倒数第 N 个结点 - 中等

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

进阶:你能尝试使用一趟扫描实现吗?

在这里插入图片描述在这里插入图片描述


解析:快慢双指针,快指针先移动,然后一起移动。

/**
 * 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 removeNthFromEnd(ListNode head, int n) {
        //加一个空的结点
        ListNode dummy = new ListNode(0);
        //让它指向头指针
        dummy.next = head;
        
        ListNode first = dummy;
        ListNode second = dummy;
        //第一个指针先移动 n 步
        for (int i = 1; i <= n + 1; i++) {
            first = first.next;
        } 
        //第一个指针到达终点停止遍历
        while (first != null) {
            first = first.next;
            second = second.next;
        }
        //改变指针方向,即为删除结点
        second.next = second.next.next;
        return dummy.next;

    }
}

面试题 02.07. 链表相交 - 简单 - 10/19(双指针 | 快慢指针)

面试题 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:

在这里插入图片描述
在这里插入图片描述


解法一:

我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:
在这里插入图片描述
此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到焦点。

否则循环退出返回空指针。


/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null) return null;
        ListNode currA = headA;
        ListNode currB = headB;
        int lenA = 0, lenB = 0;
        //求A链表的长度
        while(currA != null){
            ++lenA;
            currA = currA.next;
        }
        //求B链表的长度
        while(currB != null){
            ++lenB;
            currB = currB.next;
        } 
        //重新赋值
        currA = headA;
        currB = headB;

        //如果A比B长
        if(lenA > lenB){
            //让A先移动
            for(int i=0; i<lenA-lenB;i++){
                currA = currA.next;
            }
            //再一起移动,如果发现结点相等,就返回
            for(int i=0; i<lenB;i++){
                if(currA == currB){
                    return currA;
                }else{
                    currA = currA.next;
                    currB = currB.next;
                }
            }   
                    
        }else{ //如果B比A长
            //让B先移动
            for(int i=0; i<lenB-lenA;i++){
                currB = currB.next;
            }
            //再一起移动,如果发现结点相等,就返回
            for(int i=0; i<lenA;i++){
                if(currA == currB){
                    return currA;
                }else{
                    currA = currA.next;
                    currB = currB.next;
                }
            } 
        }
        return null;
    }
}

在这里插入图片描述

解法二:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

//让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA;
        ListNode p2 = headB;
        while(p1 != p2){
            if(p1 == null) p1 = headB; //p1 遍历完链表 A 之后开始遍历链表 B
            else p1 = p1.next;

            if(p2 == null) p2 = headA; //p2 遍历完链表 B 之后开始遍历链表 A
            else p2 = p2.next;
        }
        return p1;
    }
}

在这里插入图片描述

剑指 Offer 06. 从尾到头打印链表(头插法 | 栈)

剑指 Offer 06. 从尾到头打印链表

在这里插入图片描述


方法一:头插法实现

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        ListNode temp = listNode;
        while(temp!=null){
            list.add(0,temp.val);
            temp = temp.next;
        }
        return list;
    }
}

方法二:利用栈,遍历链表,压栈后出栈。

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        //用来存储链表中节点的值。
        Stack<Integer> stack = new Stack<>();
        while(listNode!=null){
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        //存储数组
        ArrayList<Integer> list = new ArrayList<>();
        while(!stack.isEmpty()){
            list.add(stack.pop()); //将值从栈中弹出,并添加到ArrayList中
        }
        return list;
    }
}

方法三:先反转链表,在存储

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList();
        ListNode pre = null;
        ListNode cur = listNode;
        ListNode temp = cur;
        //反转链表
        while(cur != null){
            temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        //依次添加进list
        while(pre != null){
            list.add(pre.val);
            pre = pre.next;
        }
        return list;
    }
}

剑指 Offer 24. 反转链表(双指针)

剑指 Offer 24. 反转链表

描述
给定一个单链表的头结点pHead,长度为n,反转该链表后,返回新链表的表头。

数据范围: n\leq1000n≤1000
要求:空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n) 。

如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:

在这里插入图片描述
在这里插入图片描述


反转链表:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        ListNode pred = null; //前一个结点
        ListNode curr = head; //当前结点
        ListNode temp = null; //临时保存结点
        while(curr != null){
            temp = curr.next; //保存下一个结点
            curr.next = pred; //反转
            pred = curr; //移动
            curr = temp; //移动
        }
        return pred;
    }
}

剑指 Offer 25. 合并两个排序的链表(新建链表)

剑指 Offer 25. 合并两个排序的链表
在这里插入图片描述

在这里插入图片描述


解析:新建一个链表来存储,比较两个链表的值,小的就添加到新链表中,然后移动到下一个结点再比较。

/**
 * 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 mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummp = new ListNode(-1); //合并后的链表
        ListNode h = dummp;   //操作链表
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){ //如果l1结点的值 小于 l2结点的值
                h.next = l1; //链表指向 较小的l1;
                l1 = l1.next;  //l1也移动到下一个结点
            }else{
                h.next = l2;
                l2 = l2.next;
            }
            h = h.next;  //移动到下一个结点
        }
        if(l1 == null){ //如果l1为空了,就把 h指向l2
            h.next = l2;
        }
        if(l2 == null){ //如果l2为空了,就把 h指向l1
            h.next = l1;
        }
        return dummp.next;
    }
}

23. 合并K个升序链表 - 2.11(根据值反向构建新链表 | 最小堆)

23. 合并K个升序链表

在这里插入图片描述


解法一:读取所有链表的节点,存入list数组中,对数组排序,然后根据数组的值反向构建单链表返回。

/**
 * 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) {
        //新建一个数组,用来存储所有值
        List<Integer> list = new ArrayList<>();
        //遍历所有结点,存入list
        for(ListNode node : lists){
            while(node != null){
                list.add(node.val); //添加到list中
                node = node.next;   //下一个结点
            }
        }
        //排序
        Collections.sort(list);
        //将集合转为链表
        ListNode head = new ListNode(0);
        ListNode h = head;
        for(int i : list){
            ListNode newNode = new ListNode(i); //新结点
            h.next = newNode; //拼接新结点
            h = h.next; //指向下一个结点
        }
        h.next = null; //最后一个结点为空
        return head.next; //返回head
    }
}

在这里插入图片描述


解法二:二叉堆。将k个链表都加入最小堆,然后取出堆元素加入链表中,再添加链表的下一个节点,然后移动p指针。

/**
 * 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 dummp = new ListNode(-1); //虚拟头节点
        ListNode p = dummp;
        //优先级队列,最小堆
        PriorityQueue<ListNode> pq = new PriorityQueue<>(
            lists.length, (o1,o2) -> (o1.val - o2.val)
        );
        //将k个链表都加入最小堆
        for(ListNode list : lists){
            if(list != null){
                pq.add(list);
            }
            
        }

        //遍历最小堆
        while(!pq.isEmpty()){
            ListNode node = pq.poll(); //取出的节点就是最小节点
            p.next = node; //添加到链表
            if(node.next != null) pq.add(node.next); //添加下一个节点
            p = p.next; //p不断移动
        }

        return dummp.next;
    }
}

在这里插入图片描述

剑指 Offer 52. 两个链表的第一个公共结点(双指针 | 拼接链表)

两个链表的第一个公共结点
在这里插入图片描述

在这里插入图片描述


解法一:使用双指针,先计算出两个链表的长度,然后让长的链表先移动“差值”,然后再一起移动,一起移动时碰到两个节点相同就返回,否则就一起移动到下一个节点。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if(pHead1 == null && pHead2 == null) return null;
        ListNode curr1 = pHead1;
        ListNode curr2 = pHead2;
         //算出两个链表的长度
        int len1=0, len2=0;
        while(curr1 != null){
            ++len1;
            curr1 = curr1.next;
        }
        while(curr2 != null){
            ++len2;
            curr2 = curr2.next;
        }
        //重置头指针
        curr1 = pHead1;
        curr2 = pHead2;
        
        //如果链表1比链表2长
        if(len1 > len2){
            //链表1先移动
            for(int i=0; i<(len1-len2); i++){
                curr1 = curr1.next;
            }
            //再同时移动,如果发现结点相等,就返回
            for(int j=0; j<len2; j++){
                if(curr1 == curr2){
                    return curr1;
                }else{
                    curr1 = curr1.next;
                    curr2 = curr2.next;
                }
            }
        }else{
            //链表2先移动
            for(int i=0; i<(len2-len1); i++){
                curr2 = curr2.next;
            }
            //再同时移动,如果发现结点相等,就返回
            for(int j=0; j<len2; j++){
                if(curr2 == curr1){
                    return curr2;
                }else{
                    curr1 = curr1.next;
                    curr2 = curr2.next;
                }
            }            
        }
        return null;
    }
}

解法二:让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

//让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA;
        ListNode p2 = headB;
        while(p1 != p2){
            if(p1 == null) p1 = headB; //p1 遍历完链表 A 之后开始遍历链表 B
            else p1 = p1.next;

            if(p2 == null) p2 = headA; //p2 遍历完链表 B 之后开始遍历链表 A
            else p2 = p2.next;
        }
        return p1;
    }
}

JZ23 链表中环的入口结点(哈希表 | 快慢指针)

JZ23 链表中环的入口结点
在这里插入图片描述


解法一:使用哈希表存储和查找,如找到有,说明存在环,直接返回就是环入口;否则,就是无环单链表,返回null。

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.HashSet;

public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        //建立哈希表:存储、查找node
        HashSet<ListNode> set = new HashSet<ListNode>();
        while(pHead != null){
            if(set.contains(pHead)){ //如果哈希表里包含有该节点,说明有环,返回就是环入口
                return pHead;
            }else{
                set.add(pHead); //添加节点
                pHead = pHead.next; //移动到下一个节点
            }
        }
        //无环单链表
        return null;
    }
}

解法二:快慢指针,相遇后将慢指针指向head,然后快慢指针同时移动,相遇即为入环节点

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

//快慢指针,相遇后将慢指针指向head,然后快慢指针同时移动,相遇即为入环节点
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head; //慢指针
        ListNode fast = head; //快指针
        while(fast != null && fast.next != null){
            slow = slow.next; //慢指针走一步
            fast = fast.next.next; //快指针走两步
            if(slow == fast) break; //如果相等,先跳出循环
        }
        //如果fast为空,则没有环
        if(fast == null || fast.next == null) {
            return null;
        }

        slow = head; //慢指针指向head
        while(slow != fast){ //同时移动,快指针在环内走一圈,慢指针从头开始走,相遇时即跳出循环
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

在这里插入图片描述

剑指 Offer 22. 链表中倒数第k个节结点(双指针)

22. 链表中倒数第k个节结点

在这里插入图片描述
在这里插入图片描述


解法一:创建一个链表指向原来的链表,先遍历计算出链表长度,然后复位,移动到差值的部分即可返回。

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode FindKthToTail (ListNode pHead, int k) {
        int len=0;
        ListNode curr = pHead;
        //计算链表长度
        while(curr != null){
            ++len;
            curr = curr.next;
        }
        //回到头节点
        curr = pHead;
        //如果链表长度小于k,直接返回空
        if(len < k){
            return null;
        }else{
            //遍历到要返回的节点
            for(int i=0; i<(len-k); i++){
                curr = curr.next;
            }
            return curr;
        }
        
        
    }
}

解法二:使用双指针,让后指针先移动k个位置,然后再一起移动,当后指针移动到null时,返回前指针即可。

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode FindKthToTail (ListNode pHead, int k) {
        ListNode pred = pHead;
        ListNode end = pHead;
        for(int i=0; i<k; i++){
            if(end == null) return null;
            end = end.next;
        }
        while(end != null){
            pred = pred.next;
            end = end.next;
        }
        return pred;
        
        
    }
}

剑指 Offer 35. 复杂链表的复制(map存储)

35. 复杂链表的复制

在这里插入图片描述
在这里插入图片描述


解析:使用map存储新旧节点,建立 “原节点 -> 新节点” 的 Map 映射,然后通过获取键值对 构建新链表的 next 和 random 指向,最后返回新链表的头节点。

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.Map;
import java.util.HashMap;

public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead == null) return null;
        RandomListNode curr = pHead;
        Map<RandomListNode,RandomListNode> map = new HashMap<>();
        //复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        while(curr != null){
            map.put(curr,new RandomListNode(curr.label));
            curr = curr.next;
        }
        //重置
        curr = pHead;
        //构建新链表的 next 和 random 指向
        while(curr != null){
            map.get(curr).next = map.get(curr.next);
            map.get(curr).random = map.get(curr.random);
            curr = curr.next;
        }
        //返回新链表的头节点
        return map.get(pHead);
    }
    
}

JZ76 删除链表中重复的结点(遍历)

JZ76 删除链表中重复的结点

在这里插入图片描述
在这里插入图片描述


解析:比较相邻的两个节点,如果相同,就一直移动到不相同值的节点,然后返回。

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.HashSet;

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        //空处理
        if(pHead == null) return null;
        //创建虚拟头节点,方便操作
        ListNode dummy = new ListNode(0);
        dummy.next = pHead;
        ListNode cur = dummy;
        while(cur.next != null && cur.next.next != null){
            //如果两个值相等,就继续查找并移动到相等的值的节点
            if(cur.next.val == cur.next.next.val){
                int x = cur.next.next.val;
                while(cur.next != null && cur.next.val == x){
                    //跳过重复的节点
                    cur.next = cur.next.next;
                }
            }else{
                //没有重复的节点,就移动到下一个节点
                cur = cur.next;
            }
        }
        //返回链表
        return dummy.next;
    }
}

JZ18 删除链表的节点(遍历)

删除链表的节点

在这里插入图片描述


解析:创建虚拟头节点,遍历链表,找到与给出的值相等的节点,就跳过;否则就移动到下一个节点。

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param head ListNode类 
     * @param val int整型 
     * @return ListNode类
     */
    public ListNode deleteNode (ListNode head, int val) {
    	if(head == null) return null;
        //定义虚拟节点
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode cur = dummy;
        while(cur.next != null){
            //如果值相等,就跳过
            if(cur.next.val == val){
                cur.next = cur.next.next;
            }else{
                //否则就移动到下一个节点
                cur = cur.next;
            }
        }
        //返回头节点
        return dummy.next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值