算法——链表(1)

26 篇文章 0 订阅

在这里插入图片描述

T04BF

👋专栏: 算法|JAVA|MySQL|C语言

🫵 小比特 大梦想

此篇文章与大家分享链表专题的第一部分
如果有不足的或者错误的请您指出!

1.链表常用技巧总结

1.1引入虚拟头结点

在力扣上,基本提供的链表题目都是"无头的",但是针对无头链表,我们最怕的就是参数链表是一个空链表,就容易出现对null的访问.当我们引入虚拟头结点后,我们并不关心这个"头结点"里面放的是什么值,而这个节点只是起到一个哨兵的左右
举一个例子,如果我们要合并两个链表,引入第三链表将两个链表合并
在没有引入虚拟头结点的情况下:
在这里插入图片描述
那么我们的第三个链表就要先判断是否已经有节点了??如果有,直接插在后面即可;如果没有就要将插入的节点作为头结点
但是如果我们引入虚拟头结点
在这里插入图片描述
此时我们就不需要判断了,只需要将两个链表的节点依次插入到newHead后面即可
引入虚拟头结点的优势还有很多,我们在后面的题目中更能感受到

1.2不要吝啬空间

在链表专题,如果我们吝啬空间,有时候会给我们带来很大麻烦
举个例子:
我们通常会做到这样一种题目:在prve和prev.next节点之间插入cur节点
在这里插入图片描述
我们通常会这样做:
①cur.next = prev.next
②prev.next.prev = cur
③prev.next = cur
④cur.prev = prev
这四步操作中,前两步是一定要在后两个之前的
但是如果我们直接将prev.next用用一个临时节点存储起来:
在这里插入图片描述
这是就没必要去担心什么顺序问题了,直接针对三个节点进行操作即可

1.3快慢双指针的利用

快慢双指针在链表操作中经常遇到,而最常用的是我们下面这三种操作,最重要的是这三种操作往往只是作为一道题目里面的一小部分存在

1.3.1判断链表是否有环

题目:判断链表是否有环
在这道题中,我们定义一个fast指针和slow指针,fast指针一次走两步,slow指针一次走两步,那么如果链表没环,那么fast指针一定会走到null,如果链表没环,那么fast指针和slow指针一定会在环的某一点相遇
代码呈现:

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

1.3.2找到链表环的入口

题目:找到环的入口
在上一题的基础上,如果链表有环,我们需要找到环的入口
实际上这道题有一个数学推导出来的结论:在fast指针和slow指针相遇点,此时这个相遇点距离环的入口,与起跑点距离环的入口,距离是一样的
在这里插入图片描述
即蓝色路程和红色路程是一样的
那么我们再定义一个节点node在起跑点位置,slow和node同时移动,二者相遇点就是环的入口
代码呈现:

public class Solution {
    private ListNode isCircle(ListNode head){
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return slow;
            }
        }
        return null;
    }
    public ListNode detectCycle(ListNode head) {
        ListNode node = isCircle(head);
        if(node == null || node.next == null){
            return null;
        }
        ListNode cur = head;
        while(cur != node){
            cur = cur.next;
            node = node.next;
        }
        return cur;
    }
}

1.3.3删除链表的倒数第k个节点

同样利用双指针,我们让fast指针先走k步,此时fast和slow之间的距离就是k;接着让fast和slow指针同时走,直到fast为null,此时slow和空节点的距离就是k,即为倒数第k个节点
代码呈现:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head == null || head.next == null){
            return null;
        }
        ListNode node = new ListNode(-1,head);//解决头结点被删的情况
        ListNode fast = node;
        ListNode slow = node;
        while(n > 0){
            n--;
            fast = fast.next;
        }
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return node.next;
    }
}

1.3.4寻找中间节点

题目:链表的中间节点
固定套路:定义fast指针和slow指针,fast一次走两步,slow一次走一步,当fast为空或者fast.next 为空时,slow所在位置就是中间节点的位置
代码:

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

1.4反转链表

题目:反转链表
这道题如果引入"虚拟头结点",那么在时间复杂度为O(n)的情况下就能完成
即直接往虚拟头结点后面进行头插即可

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode newHead = new ListNode(0);
        ListNode cur = head;
        while(cur != null){
            ListNode tmp = cur.next;
            cur.next = newHead.next;
            newHead.next = cur;
            cur = tmp;
        }
        return newHead.next;
    }
}

2.两数相加

题目:两数相加

2.1解析

思路比较简单,用两个指针遍历两个链表,每次将两个节点里面的值相加,将得到的数的个位数作为新节点的值,把新节点插入到引入的虚拟头结点,同时记录下此时两数相加的进位,在下次计算的时候要加上进位

2.2题解

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode cur1 = l1;
        ListNode cur2 = l2;
        int tmp = 0;
        ListNode newHead = new ListNode(0);
        ListNode cur = newHead;//记录新链表的最后一个节点
        while(cur1 != null || cur2 != null || tmp != 0){//tmp != 0 是为了解决最后计算完还有进位的情况
          if(cur1 != null){
              tmp += cur1.val;
              cur1 = cur1.next;
          }
          if(cur2 != null){
              tmp += cur2.val;
              cur2 = cur2.next;
          }
          ListNode node = new ListNode(tmp % 10);
          tmp /= 10;
          cur.next = node;
          cur = cur.next;
        }
        return newHead.next;
    }
}

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

题目:两两交换链表中的节点

3.1解析

这道题就能体现出我们之前说过的"不要吝啬变量"的好处
为了方便,我们还是引入"虚拟头节点"
在这里插入图片描述
对此链表进行两两翻转操作
要翻转两个节点,不仅需要记录这两个节点,还要记录前一个节点和后一个节点
因此我们直接引入这些变量:
在这里插入图片描述
就是对cur和next进行翻转操作,就很简单明了了,直接让prev.next = next; next.next = cur; cur.next = nnext;
在这里插入图片描述
此时前两个就翻转完了
接下来就要让prev指向当前cur的位置,继续进行刚才的操作
在这里插入图片描述
那么循环什么时候结束呢??会有两种情况
(1)就像我们上面这一组一样,当我们再次把prev指向cur的时候
在这里插入图片描述
此时,没翻转的节点就一个,那么就不用翻转了,即当next == null的时候是循环结束
(2)假设我们没有最后一个节点
在这里插入图片描述
那么此时已经没有节点了,自然就不需要翻转.因此cur == null也是循环结束的标志

3.2题解

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode newHead = new ListNode(0,head);
        ListNode prev = newHead;
        ListNode cur = prev.next;
        ListNode next = cur.next;
        ListNode nnext = next.next;
        while(cur != null &&  next != null){
            prev.next = next;
            next.next = cur;
            cur.next = nnext;
            prev = cur;
            cur = prev.next;
            if(cur != null){
                next = cur.next;
                if(next != null){
                    nnext = next.next;
                }
            }
        }
        return newHead.next;
    }
}

4.重排链表

题目:重排链表

4.1解析

此题最简单的思路就是将数组划分为两部分,并将右边的部分逆置,随后依次插入新的链表中
在这里插入图片描述
而其中的操作就是寻找中间节点、链表逆置、合并链表,我们在一开始都是讲过的,因此我们直接代码呈现\

4.2

    private ListNode findCenter(ListNode head){
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
    private ListNode reverse(ListNode head){
        ListNode newHead = new ListNode(1);
        ListNode cur = head;
        while(cur != null){
            ListNode tmp = cur.next;
            cur.next = newHead.next;
            newHead.next = cur;
            cur = tmp;
        }
        return newHead.next;
    }
    public void reorderList(ListNode head) {
        ListNode center = findCenter(head);
        ListNode node = reverse(center.next);
        //让中心节点的后面部分逆置
        center.next = null;//断开两部分
        ListNode cur1 = head;//遍历前一部分
        ListNode newHead = new ListNode(0);
        ListNode cur = newHead;//遍历新链表

        //合并
        while(cur1 != null){//前面的部分一定比后面的部分长
            cur.next = cur1;
            cur1 = cur1.next;
            cur = cur.next;
            if(node != null){
                cur.next = node;
                node = node.next;
                cur = cur.next;
            }
        }
        head = newHead.next;
    }

感谢您的访问!!期待您的关注!!!

在这里插入图片描述

T04BF

🫵 小比特 大梦想
  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值