剑指offer刷题记录--链表

链表

无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。
面试的时候经常碰见诸如寻找公共尾部入口、获取倒数第k个元素,获取中间位置的元素,判断链表是否存在环,判断环的长度等和长度与位置有关的问题。
这些问题都可以通过灵活运用**双指针**来解决

刷题路线

题目解法题解
从尾到头打印链表辅助栈;递归两种
链表中倒数第k个结点快慢指针;辅助list双指针
反转链表递归;迭代两种
合并两个或k个有序链表迭代;递归两种
复杂链表的复制原地修改(扩充&分离);辅助HashMap两种
两个链表的第一个公共结点双指针(你的名字);辅助HashMap走对方的路相遇
链表中环的入口结点双指针;HashMap快慢指针环形相遇
删除排序链表中的重复元素HashMap;双指针;递归前两种双指针+尾插法

从尾到头打印链表

解法时间复杂度空间复杂度
辅助栈O(n)O(n)
递归O(n)O(n)
//辅助栈
class Solution {
    public int[] reversePrint(ListNode head) {
        LinkedList<Integer> stack=new LinkedList<>();
        while(head!=null){
            stack.offerLast(head.val);
            head=head.next;
        }

        int[] res=new int[stack.size()];
        for(int i=0;i<res.length;i++){
            res[i]=stack.pollLast();
        }
        return res;
    }
}

链表中倒数第k个结点

解法时间复杂度空间复杂度
快慢指针O(n)O(n)

实际返回以该节点为开始的链表

//快慢指针,先让快指针走k步,然后两个指针同步走,当快指针走到头时,慢指针就是链表倒数第k个节点。
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        if(head==null||k==0) return null;
        //快慢指针
        ListNode fast=head,slow=head;
        for(int i=0;i<k;i++){
            //边界判断
            if(fast==null) return null;
            fast=fast.next;
        }
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;

    }
}

类似题目
返回倒数第K个节点
返回值不同 只返回值即可 题解

class Solution {
    public int kthToLast(ListNode head, int k) {
        ArrayList<Integer> list=new ArrayList<>();
        while(head!=null){
            list.add(head.val);
            head=head.next;
        }
        
        return list.get(list.size()-k);

    }
}

反转链表

解法时间复杂度空间复杂度
递归O(n)O(n)
迭代O(n)O(1)
//递归
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;
        return last;
    }
}
//迭代
public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
}

合并两个或k个有序链表

解法时间复杂度空间复杂度
递归O(n+m)O(n+m)
迭代O(n+m)O(1)
//迭代
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null) return l2;
        else if(l2==null) return l1;
        ListNode head=new ListNode(-1);
        ListNode cur=head;
        while(l1!=null&&l2!=null){
            if(l1.val<=l2.val){
                cur.next=l1;
                l1=l1.next;
            }else{
                cur.next=l2;
                l2=l2.next;
            }
            cur=cur.next;
        }
        cur.next=l1==null?l2:l1;
        return head.next;
            
        
    }
}
//递归
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;
        }

    }
}

复杂链表的复制

解法时间复杂度空间复杂度
HashMap辅助O(n)O(n)
原地修改(扩充&分离)O(n)O(1)
/*
HashMap辅助,空间和时间都是O(n)
*/
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return head;
        }
        //map中存的是(原节点,拷贝节点)的一个映射
        Map<Node, Node> map = new HashMap<>();
        for (Node cur = head; cur != null; cur = cur.next) {
            map.put(cur, new Node(cur.val));
        }
        //将拷贝的新的节点组织成一个链表
        for (Node cur = head; cur != null; cur = cur.next) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
        }

        return map.get(head);
    }
}
/*
原地修改,空间复杂度为O(1)
*/
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return head;
        }
        //将拷贝节点放到原节点后面,例如1->2->3这样的链表就变成了这样1->1'->2->2'->3->3'
        for (Node node = head, copy = null; node != null; node = node.next.next) {
            copy = new Node(node.val);
            copy.next = node.next;
            node.next = copy;
        }
        //把拷贝节点的random指针安排上
        for (Node node = head; node != null; node = node.next.next) {
            if (node.random != null) {
                node.next.random = node.random.next;
            }
        }
        //分离拷贝节点和原节点,变成1->2->3和1'->2'->3'两个链表,后者就是答案
        Node newHead = head.next;
        for (Node node = head, temp = null; node != null && node.next != null;) {
            temp = node.next;
            node.next = temp.next;
            node = temp;
        }

        return newHead;
    }
}

两个链表的第一个公共结点||相交链表

解法时间复杂度空间复杂度
双指针O(n+m)O(1)
哈希表法O(m+n)O(m)/O(n)
//双指针
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null||headB==null) return null;

        //A长度 LenA+Len公共
        //B长度 LenB+Len公共
        //总长度确定
        //双指针 找到相遇

        ListNode a=headA,b=headB;
        //走自己的路,走到尽头,走对方的路
        while(a!=b){
            a=(a==null)?headB:a.next;
            b=(b==null)?headA:b.next;
        }
        return a;
    }
}

链表中环的入口结点||环形链表

解法时间复杂度空间复杂度
双指针O(n)O(1)
哈希表法O(n)O(n)
//第一份反应的做法
//利用ArrayList 的contains API
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead==null) return null;
        List<ListNode> list=new ArrayList<>();
        while(pHead!=null){
            if(list.contains(pHead)) return pHead;
            list.add(pHead);
            pHead=pHead.next;
        }
        return pHead;
    }
}
//同思路,采用Set
public boolean hasCycle(ListNode head) {
    Set<ListNode> nodesSeen = new HashSet<>();
    while (head != null) {
        if (nodesSeen.contains(head)) {
            return true;
        } else {
            nodesSeen.add(head);
        }
        head = head.next;
    }
    return false;
}
//leetcode141 返回值boolean
//快慢指针 相遇就可以判断为有
public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) {
        return false;
    }
    ListNode slow = head;
    ListNode fast = head.next;
    while (slow != fast) {
        if (fast == null || fast.next == null) {
            return false;
        }
        slow = slow.next;
        fast = fast.next.next;
    }
    return true;
}
//leetcode142 返回环形入口
//第一次相遇:仅仅能够判断存在环,不一定在入口处
//第二次相遇:令他们在入口相遇
/*
	链表共有 a+ba+ba+b 个节点,b为环形含入口

    1.第一次相遇,slow = nb
    2.a+nb = 入口点
    3.slow再走a = 入口 = head走到入口 = a
    4.由3得出,起始距离入口 = 第一次相遇位置 + a

*/

```java
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null||head.next==null) return null;
        ListNode slow=head,fast=head;
        while(true){
            if(fast==null||fast.next==null) return null;
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow) break;
        }
        fast=head;
        while(fast!=slow){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
}

删除排序链表中的重复元素

保留重复的数字=删除链表中重复的结点(牛客网)

//保留第一个重复节点
//迭代法
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        Set<ListNode> set=new HashSet<ListNode>();
        if(head==null||head.next==null) return head;
        ListNode pre=head,cur=head.next;
        while(cur!=null){
            if(cur.val==pre.val){
                pre.next=cur.next;
            }else{
                pre=pre.next;
            }
            cur=cur.next;
        }
        return head;
    }
}
//保留第一个重复节点
//递归法
/*
递归套路解决链表问题:

    找终止条件:当head指向链表只剩一个元素的时候,自然是不可能重复的,因此return
    想想应该返回什么值:应该返回的自然是已经去重的链表的头节点
    每一步要做什么:宏观上考虑,此时head.next已经指向一个去重的链表了,而根据第二步,我应该返回一个去重的链表的头节点。因此这一步应该做的是判断当前的head和head.next是否相等,如果相等则说明重了,返回head.next,否则返回head

*/
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        head.next = deleteDuplicates(head.next);
        if(head.val == head.next.val) head = head.next;
        return head;
    }
}

不保留重复的数字

解法时间复杂度空间复杂度
双指针O(n)O(1)
双指针+尾插法
哈希表法O(n)O(n)
递归O(n)O(n)

1: 哈希表法
hash_map 统计出现次数,如果出现次数等于1,那么就放在新链表后面。

:2:双指针:
定义一个 dummy 头结点,链接上原链表,cur 指向原链表头部
① 当前结点value != 当前结点的下一结点value。那么让pre指针来到当前结点,这样就链接了前一结点和当前结点。然后当前结点移至下一结点
② 当前结点value == 当前结点的下一结点value。那么就让 cur 一直往下走直到 当前结点value != 当前结点的下一结点value,然后此时是不能动 pre 指针的,要避免后面还有重复的,所以让 pre->next = cur->next。然后当前结点移至下一结点。
循环结束的条件结合例子即可,如[1,1]

3.递归法

//递归
/*
head 后面有值而且和 head 的值相等,那么就找到不相等为止,然后对后面一个结点去递归,这样就把前面重复的给删除了。
head 后面有值但和 head 的值不等,那么就递归后面一个结点,接在 head 的后面
最后返回 head
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if(pHead==null||pHead.next==null) return pHead;
        ListNode newNode=deleteDuplication(pHead.next);
        if(pHead.next!=null&&pHead.next.val==pHead.val){
            while(pHead.next!=null&&pHead.next.val==pHead.val)
            {
                pHead=pHead.next;
            }
            return deleteDuplication(pHead.next);
        }
        else{
            pHead.next=deleteDuplication(pHead.next);
        }               
        return pHead;
        

    }
}
//双指针
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null||head.next==null){
            return head;
        }
        
        //避免头几个重复 建立空头节点
        ListNode first=new ListNode(-1);
        first.next=head;
        //两个指针,pre不能轻易动,保持在重复元素前一个
        ListNode pre=first,cur=head;
        //由cur去比较curcur.next
        while(cur!=null&&cur.next!=null){
            if(cur.val!=cur.next.val){
                pre=cur;
                cur=cur.next;
            }else{//有重复了  找到连续几个重复 
                while(cur.next!=null&&cur.val==cur.next.val){
                    cur=cur.next;
                }
                //出循环 cur.next与cur不等了
                //这时 pre保持在循环开始前cur的前一个
                pre.next=cur.next;//删除所有重复的
                cur=cur.next;


            }
        }
        return first.next;

    }
}
//双指针 尾插
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) return head;  // 若head为空则直接返回null
        ListNode dummy = new ListNode(-1);  // 建立一个虚拟头结点
        ListNode tail = dummy;  // 定义一个尾巴,用于尾插法。
        for (ListNode l = head, r = head; l != null; l = r) {
            while (r != null && r.val == l.val) r = r.next;  // 只要r不为空并且与l的值相等则一直向后移动
            if (l.next == r) {  // 若长度为1,则通过尾插法加入。
                tail.next = l;  // 基本的尾插法                                         
                tail = l;                                               
                tail.next = null;  // 这里记得将尾部的后面置为null,不然可能后面会带着一些其他的节点。
            }
        }
        return dummy.next;
    }
}

作者:optimjie
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/solution/javashuang-zhi-zhen-dai-ma-jiao-duan-rong-yi-li-ji/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值