2022秋招前,链表最后一练

链表的重要性,那是不言自明,那么一起刷题吧,如果觉得还行记得随手给个赞👍,嘿嘿。
后续还会有其他的专练,嘚嘚。都是一些比较经典的链表题目。
基本会控制在对应专练的每一篇博客在十个题目左右!

删除链表的倒数第n个结点

在这里插入图片描述
由于头节点可能是被删除的,所以技巧就是建立一个虚拟的头结点,让虚拟的头结点指向真正的结点。
由于要删除一个结点,所以需要找到要删除结点的前一个结点。也就是找到倒数第n+1个结点。
由于只可以遍历一次,所以使用快慢指针的思路

  • 快指针前走n 步 之后 快慢指针一起走,当快指针走到最后一个点的时候,终止,此时的慢指针就是倒数第n+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 removeNthFromEnd(ListNode head, int n) {
        //首先对n进行合法性的校验-- 题目已说是合法的
        
        //要得到倒数第n个结点使用快慢指针来做
        ListNode prev = new ListNode(-1);
        prev.next = head;
        ListNode slow = prev;
        ListNode fast = prev;
        while(n>0){
            fast = fast.next;
            n--;
        }
        while(fast.next != null){
            slow = slow.next;
            fast = fast.next;
        }
        // slow 指向的是要删除的结点的前一个 所以不会出现空指针异常的
        slow.next = slow.next.next;
        //要返回虚拟头节点的下一个结点,头节点可能被删掉
        return prev.next;
       
    }
}

删除链表中的结点

这个题的确写的是简单,但是别小瞧这个题,有点意思哈!
在这里插入图片描述

传入的参数很有意思,不是链表的头结点,也不知道链表的头结点。
但是如果我们要删除一个结点,一般的做法就是要知道要删除的结点的前一个,但是这里不可以获取,所以只可以使用一种假装删除的做法。
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;//复制下一个结点的值
        node.next = node.next.next; // 由于给定的点一定不是最后一个点,所以不会出现空指针异常。
    }
}

删除链表中的重复元素

在这里插入图片描述
使用快慢指针的做法

如果当前的值和下一个值不相等那么慢指针和快指针各向后一步走
如果当前的值和下一个值相等,那么就让快指针一直往后走,直到走到快慢指针的值不相等为止,然后是slow 的next 指向fast 然后fast 继续往后走。

这个题做过很多次,但是每次都没有办法一遍做对!

  • 要走完整个链表而不是走到最后一个结点 所以条件是 cur != null
  • 最后无论如何,都要把slow的next 置空处理,避免 如果整个链表全都是重复的 1-1-1-1 链表走完 都没有找到那个不重复的点,所以必须要置空!
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null) return null; //特例判定
        ListNode slow = head;
        ListNode fast = head.next; //一定有重复元素 所以fast 一定不为空

        while(fast != null ){
            if(slow.val == fast.val){
                fast = fast.next;
            }else{
                slow.next = fast;
                slow = fast;
                fast = fast.next;
            }
        }
        slow.next = null;//最后一定要置空处理
        return  head;
    }

删除链表中的重复元素II(※)

在这里插入图片描述

思路一:

使用快慢指针的做法

  • 判断两个指向的值是否一样,一样既让fast 一直往后,直到找到不一样的为止。然后slow 指向fast fast 指向fast的next 继续判断。
  • 如果值不一样,那么当前的slow 指向的结点就可以加入到无重复的链表中去。
  • 头节点可能重复,所以new 虚拟结点 同时虚拟结点作为无重复链表的前驱结点。
  • 要考虑 fast 为空的时候 slow 的状态是怎么来的。
    在这里插入图片描述
    每一次做,代码写的都不一样…
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode fake = new ListNode(-1);
        fake.next = head;
        ListNode prev = fake;
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast != null){
            if(slow.val != fast.val){
                prev.next = slow;
                prev = slow;
                slow = fast;
                fast = fast.next;
            }else{
                while(fast != null && slow.val == fast.val){
                    fast = fast.next;
                }
                if(fast == null){ //走完链表都是重复的 也就是最后一个元素是重复的
                    prev.next = null;
                    return fake.next;
                }else{
                    slow = fast;
                    fast = fast.next;
                }
            }
        }
        //这里跳出循环是因为fast 为空  并且最后一个元素没有重复  一定要把最后一个元素给串起来!
        prev.next = slow;
        return fake.next;
    }
}

旋转链表

在这里插入图片描述

需要找到的点 是 移动的头节点和尾结点,可以使用双指针来确定。由于需要头结点的前一个结点,所以使用双指针。fast先走k步
然后更改 fast的next 是head 。head 指向 slow 的next,然后将slow的next 置空。
注意此题的k取值可能会很大,所以需要先求出链表的长度,然后将k %= size

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        ListNode fake = new ListNode(-1);
        fake.next  = head;
        //首先对k 和 head 进行校验
        if(head == null) return null;
        //求链表的长度 然后取模 缩小k的范围
        ListNode cur = head;
        int count = 0;
        while(cur != null){
            count++;
            cur = cur.next;
        }
        k = k %count;
        if(k == 0) return head;

        //也就是找到倒数的第k个的前一个
        ListNode fast = fake;
        ListNode slow = fake;
        while( k > 0){
            fast = fast.next;
            k--;
        }
        while(fast.next != null){
            slow = slow.next;
            fast = fast.next;
        }
        fast.next = fake.next;
        fake.next = slow.next;
        slow.next = null;
        return fake.next;
    }
}

两两交换链表中的节点

在这里插入图片描述

头结点会发生变化,所以新建虚拟结点。同时以每一对作为一个单位 ,枚举的是每一对的头一个结点。
在这里插入图片描述

class Solution {
    public ListNode swapPairs(ListNode head) {
        //不存在头结点 或者 是头结点的下一个结点不存在那么就不需要交换 
        if(head == null || head.next == null) return head;
        //说明至少有两个结点
        ListNode prev = new ListNode(-1);
        ListNode fake = prev;
        prev.next = head;
        ListNode cur = head;

        while(cur != null && cur.next != null){
            prev.next =  cur.next;
            cur.next = cur.next.next;
            prev.next.next = cur;
            prev = cur;
            cur = cur.next;
        }
        return fake.next;
    }
}

反转链表(※)

在这里插入图片描述
cur表示需要反转的结点以及 记录它的前驱和后继。(本质是两两操作记录两个即可,但是由于更改会影响下一个 所以需要记录第三个结点)
同时记得更改头节点的指向置空
在这里插入图片描述

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode prev =  head;
        ListNode cur = head.next;//代表当前要翻转的结点
        head.next = null;//这里一定要置空否则会循环
        ListNode curNext = null;
        while(cur != null ){ //要走完整个链表
            curNext = cur.next;
            cur.next = prev;
            prev = cur;
            cur = curNext;
        }
        return prev;
    }
}

反转链表II

在这里插入图片描述
没啥难点,除了一些特殊的判断唯一需要多说一句的就是坑点。
因为要根据left 和 right 的差值确定有多少结点需要翻转,而这个值需要事先求出来,(中间的left 和 right 因为要确定翻转起始点和结束点所以会变化。)

public ListNode reverseBetween(ListNode head, int left, int right) {
        if(head == null || left == right)  return head;
        //首先需要找到 left 的前驱 可能为空
        ListNode fake = new ListNode(-1);
        fake.next = head;
        ListNode prev =fake;
        ListNode cur = head;
        //这个必须放在前面  left 会变动
        int sub= right - left;
        int temp = sub;//
        while(left-1 > 0){
            prev = cur; 
            cur = cur.next;
            left --;
        } //cur 指向了left 

        ListNode end = cur;
        while(sub >0){
            end = end.next;
            sub--;
        }
        //end 指向 right 
    
        prev.next = end;
        ListNode curNext = cur.next;
        cur.next = end.next;
        sub = temp;
        while(sub > 0){
            prev  = cur;
            cur = curNext;
            curNext = cur.next;
            cur.next = prev;
            sub--;
        }
        return fake.next;
    }

相交链表(※)

在这里插入图片描述
思路一:差值法
整个思路其实一点都不难想,但是就是想不到,对自己无语,这可是一个easy的题啊!而且还是做了很多遍的。
其实长短不一的链表,分别求出两个链表的和然后让长的那一个先走和短的的差值,之后两个一起走,如果相等就相交了,不相等如果一直走到null 就说明确实是不相交的链表

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode cur1 = headA;
        int countA = 0;
        int countB = 0;
        ListNode cur2 = headB;
        while(cur1 != null){
            countA++;
            cur1 = cur1.next;
        }
        cur1 = headA;
        while(cur2 != null){
            countB++;
            cur2= cur2.next;
        }
        cur2 = headB;
        int sub = countA-countB;
        if(sub >0){ // 判断哪一个链表长 让长的链表先走差值 之后再一起走。
            while(sub >0){
                cur1 = cur1.next;
                sub--;
            }
            while(cur1 != null){
                if(cur1 == cur2){
                    return cur1;
                }
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
        }else{
             while(sub <0){
                cur2 = cur2.next;
                sub++;
            }
            while(cur2 != null){
                if(cur1 == cur2){
                    return cur2;
                }
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
        }
        return null;
    }
}

但是这样的做法链表需要先遍历一遍,并且可以看出来,代码的冗余度很大。

思路二:双指针(这个要多刷!)
在这里插入图片描述
可以让每一个结点都多走一个空结点,空结点也算一步,之后再指向下一个的头节点。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        while(cur1 != cur2){
           if(cur1 == null){
               cur1 = headB;
           }else{
               cur1 = cur1.next;
           }

           if(cur2 == null){
               cur2 = headA;
           }else{
               cur2 = cur2.next;
           }
        }
        return cur1;
    }
}

思路三:哈希法

遍历一遍链表1,放大哈希表里面,然后遍历链表2的每一个结点,看当前遍历到的结点是否再哈希表中存在。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<>();
        ListNode cur1 = headA;
        while(cur1 != null){
            set.add(cur1);
            cur1 = cur1.next;
        }
        ListNode cur2 = headB;
        while(cur2 != null){
           if(set.contains(cur2)){
               return cur2;
           }
           cur2 = cur2.next;
        }
        return null;
    }
}

环形链表

在这里插入图片描述
如果链表有环,快慢指针一定相遇,两个相等,如果没有相遇,那么返回结果。

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

环形链表II(※)

在这里插入图片描述

快慢指针的经典题目
思路概述:
好像是操场跑步一样,一个指针走一步,一个指针走两步,这两个指针一定会相遇的。但是相遇的地方不一定是入环的结点,因此当相遇的时候,让快指针回到起点,然后在和慢指针一人一步走,最后再次相遇的结点就是入环的结点位置。
证明原理:

为什么是一个一步,一个两步? 不可以是一个一步,一个三步走、四步走吗?
其实是可以的,当环的长度不确定的时候 有公式表明一个一步 一个两步相遇消耗的时间平均最少。(也就是可以最快的相遇)
两个指针一定会相遇 这个不用过多解释了,当慢指针入环的时候,快指针每走一次两个指针的距离缩小1

如何做到第一次相遇后 一个回到起点 和另一个分别一步走直到再次相遇的时候就是入环的点呢?

在这里插入图片描述
老说人家链表的题简单,其实是非常考验逻辑思维的,谁再说链表简单😕?

  • 没有结点和一个结点不能成环。
  • while (条件) 不仅要作为循环的判断逻辑还要作为进入的判断逻辑(所以 fast == slow 这个判断不可以加while上 必须是在里面的 否则都无法进入while)
  • 结束循环的时候 是 由于什么情况结束的
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null) return  null;

        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                break;
            }
        }

        //说明就是第一次相遇了 说明有环了
        if(fast == slow ){
            fast = head;
        }else{
            return null;//说明没有环
        }

        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}

第一部分就暂时到这里啦!
接下来第二部分的链表题都会很变态,至少我个人这么觉得。争取或许可以在这周日前把他们搞出来?

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值