0
点赞
收藏
分享

微信扫一扫

【数据结构与算法】一套链表 OJ 带你轻松玩转链表

洒在心头的阳光 2023-01-03 阅读 115

链 表 OJ


 

🏳️一. 移除链表元素

简介:

示例1:请添加图片描述

示例2:

示例3:

提示:

要求:

首先我们需要知道的是:

所以我们在这里定两个指针cur和prev,其中cur代表的是要删除的节点,prev代表的是cur的前驱

在这里插入图片描述

  1. 先考虑特殊情况:头节点不为空
if(head == null){
    return null;
}
  1. 定义cur和prev指针位置
ListNode cur = head.next;
ListNode prev = head;
  1. 在删除节点之前,我们得知道cur走动的前提是不为空的
while(cur != null){
	......
}
  1. 在cur 不为空的前提下,我们该如何删除元素呢?
if(cur.val == val){
    prev.next = cur.next;
    cur = cur.next;
}else{
    prev = cur;
    cur = cur.next;
}
  1. 最终代码就完成了,但是还有一个特殊情况,那就是cur是从第二个元素开始遍历,并没有考虑到头节点,所以我们在这里单独判断一下头节点
if(head.val == val){
    head = head.next;
}

附上总的代码:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null){
            return null;
        }

        ListNode cur = head.next;
        ListNode prev = head;

        while(cur != null){
            if(cur.val == val){
                prev.next = cur.next;
                cur = cur.next;
            }else{
                prev = cur;
                cur = cur.next;
            }
        }

        //单独处理了头节点
        if(head.val == val){
            head = head.next;
        }
        
        return head;
    }
}

 

🏴二.反转链表

简介:

示例 1:
请添加图片描述

示例 2:
请添加图片描述

示例 3:

提示:

要求:

注意:

反转前的链表:
在这里插入图片描述
反转后的链表:
在这里插入图片描述
由上图可知,我们需要把头节点挪到最后面,让每个节点的指向发生反向改变,这时候我们就需要一个前驱信息来支撑我们的节点指向发生改变

  1. 先考虑特殊情况

①:当头节点为空的情况下直接返回空

if(head == null){
    return null;
}

②:只有一个节点

if(head.next == null){
    return head;
}
  1. 根据图表直接把头节点的next设为null
head.next = null;
  1. 定义两个指针,一个遍历转变节点指向的指针cur,一个成为前驱信息支撑第一个指针遍历的指针curNext,cur在头节点head后一位

在这里插入图片描述

ListNode cur = head.next;

反转循环:

while(cur != null){
    ListNode curNext = cur.next;
    cur.next = head;
    head = cur;
    cur = curNext;
}

附上总的代码:

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null){
            return null;
        }
        //只有一个头节点
        if(head.next == null){
            return head;
        }

        ListNode cur = head.next;
        head.next = null;
       
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = head;
            head = cur;
            cur = curNext;
        }
        return head;
    }
}

 

🏁三.链表的中间结点

简介:

示例 1:

示例 2:

提示:

要求:

思路:

定义快慢指针,判断终止条件及快慢指针的走动

public ListNode middleNode(){
	ListNode fast = head;
	ListNode slow = head;
	while(fast != null && fast.next != null){
   		fast = fast.next.next;
    	slow = slow.next;
	}
	return slow;
}

 

🚩四.链表中倒数第k个结点

描述:

示例1:

思路:

  1. 先考虑特殊情况(k的合法性和头节点)
if(k <= 0 ){
    return null;
}
if(head == null){
    return null;
}
  1. 定义快慢指针,并且执行快指针比慢指针多的步数
ListNode fast = head;
ListNode slow = head;
while(k - 1 > 0){
    fast = fast.next;
    if(fast == null){
        return null;
    }
    k--;
}
  1. 当快指针比慢指针快了 k - 1 步的时候,两个指针一起走,直至快指针达到临界条件
while (fast.next != null){
    fast = fast.next;
    slow = slow.next;
}

附上总的代码:

public ListNode findKthTOTail(int k){
    if(k <= 0 ){
        return null;
    }
    if(head == null){
        return null;
    }

    ListNode fast = head;
    ListNode slow = head;

    while(k - 1 > 0){
        fast = fast.next;
        if(fast == null){
            return null;
        }
        k--;
    }
    //fast已经走了k-1步了
    while (fast.next != null){
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

 

🏳️‍🌈五.合并两个有序链表

要求:

示例 1:
请添加图片描述

示例 2:

示例 3:

提示:

思路:

  1. 先考虑特殊情况,任何一链表为空
if (head1 == null) return head2;
if (head2 == null) return head1;
  1. 定义一个新的链表来接收俩链表的合成,并且定义一个新的指针完成比较大小的走动来遍历打印新的链表
ListNode newHead = new ListNode(-1);
ListNode tmp = newHead;
  1. tmp作为新的指针,在比较俩链表遍历大小时,每当俩节点谁最小,tmp便指向它,然后一直这样走,就可以把俩链表整体大小归纳到tmp当中,传输给newHead链表
while(head1 != null && head2 != null){
    if(head1.val < head2.val){
        tmp.next = head1;
        tmp = tmp.next;
        head1 = head1.next;
    }else {
        tmp.next = head2;
        tmp = tmp.next;
        head2 = head2.next;
    }
}
  1. 在这里我们还有个特殊情况,当任意一个链表走完之后,我们另外的一个链表就不用比较了,直接把剩下的节点拼接上去就可以
if(head1 != null){
    tmp.next = head1;
}
if(head2 != null){
    tmp.next = head2;
}

附上总的代码:

public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
    if (head1 == null) return head2;
    if (head2 == null) return head1;

    ListNode newHead = new ListNode(-1);
    ListNode tmp = newHead;

    while(head1 != null && head2 != null){
        if(head1.val < head2.val){
            tmp.next = head1;
            tmp = tmp.next;
            head1 = head1.next;
        }else {
            tmp.next = head2;
            tmp = tmp.next;
            head2 = head2.next;
        }
    }
    if(head1 != null){
        tmp.next = head1;
    }
    if(head2 != null){
        tmp.next = head2;
    }
    return newHead.next;
}

 

🏳️‍⚧️六.链表的回文结构

描述:

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

测试样例:

思路:

  1. 考虑特殊情况,链表为空或者只有一个头节点
if(this.head == null) return false;
if (this.head.next == null) return true;//只有一个节点
  1. 找中间节点
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
    fast = fast.next.next;
    slow = slow.next;
}
  1. 反转后半部分链表
ListNode cur = slow.next;
while(cur != null){
    ListNode curNext = cur.next;
    cur.next = slow;
    slow = cur;
    cur = curNext;
}
  1. slow走到最后一个节点的时候,往中间遍历,此时头节点往中间遍历,看两边分别遍历是否相同,判断是否为回文串
while (head != slow){
    if(head.val != slow.val){
        return false;
    }
    //偶数的情况
    if (head.next == slow){
        return true;
    }
    head = head.next;
    slow = slow.next;
}

附上总的代码:

public boolean chkPalindrome() {
    if(this.head == null) return false;
    if (this.head.next == null) return true;//只有一个节点

    //1.找中间节点
    ListNode fast = head;
    ListNode slow = head;
    while(fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
    }
    //2.slow一定是中间节点,再翻转
    ListNode cur = slow.next;
    while(cur != null){
        ListNode curNext = cur.next;
        cur.next = slow;
        slow = cur;
        cur = curNext;
    }
    //3.slow走到了最后一个节点的地方
    while (head != slow){
        if(head.val != slow.val){
            return false;
        }
        //偶数的情况
        if (head.next == slow){
            return true;
        }
        head = head.next;
        slow = slow.next;
    }
    return true;
}

 

🏴‍☠️七.链表分割

描述:

注意:

思路:

  1. 考虑特殊情况
if(head == null) return null;
  1. 定义两条链表的首尾节点
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
  1. 遍历链表,然后进行分类,判断是分类到哪一条新链表中(尾插)
ListNode cur = head;
while(cur != null){
    if(cur.val < x){
        //判断是不是第一次插入
        if(bs == null){
            bs = cur;
            be = cur;
        }else {
            be.next = cur;
            be = be.next;
        }
    }else {
        if(as == null){
            as = cur;
            ae = cur;
        }else {
            ae.next = cur;
            ae = ae.next;
        }
    }
    cur = cur.next;
}
  1. 拼接两个新链表
if(bs == null){
    //说明第一个区间没有数据
    return as;
}
be.next = as;
if(as != null){
    ae.next = null;
}
return bs;

附上总的代码:

public ListNode partition(int x) {
    if(head == null) return null;
    ListNode bs = null;
    ListNode be = null;
    ListNode as = null;
    ListNode ae = null;

    ListNode cur = head;
    while(cur != null){
        if(cur.val < x){
            //判断是不是第一次插入
            if(bs == null){
                bs = cur;
                be = cur;
            }else {
                be.next = cur;
                be = be.next;
            }
        }else {
            if(as == null){
                as = cur;
                ae = cur;
            }else {
                ae.next = cur;
                ae = ae.next;
            }
        }
        cur = cur.next;
    }
    if(bs == null){
        //说明第一个区间没有数据
        return as;
    }
    be.next = as;
    if(as != null){
        ae.next = null;
    }
    return bs;
}

 

🏴󠁧󠁢󠁷󠁬󠁳󠁿八.相交链表

简介:

图示两个链表在节点 c1 开始相交:请添加图片描述

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):

评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。

示例 1:
请添加图片描述

示例 2:请添加图片描述

示例 3:
请添加图片描述

提示:

要求:

思路:

  1. 特殊情况,有链表为空
if(headA == null || headB == null) return null;
  1. 遍历两个链表的长度
int lenA = 0;
int lenB = 0;
ListNode pl = headA;//pl:永远指向长的链表
ListNode ps = headB;//ps:永远指向短的链表

while(pl != null){
    lenA++;
    pl = pl.next;
}

while (ps != null){
    lenB++;
    ps = ps.next;
}
  1. 我们还需要用到pl,ps,所以这里还需要换回来
pl = headA;
ps = headB;
  1. 差值步的计算和判断
//差值步 计算
int len = lenA - lenB;
if(len < 0){
    pl = headB;
    ps = headA;
    len = lenB - lenA;//更新差值步
}
//走到这里,pl一定指向的是那个最长的链表,ps一定指向的是最短的链表,且len一定是一个正数

while (len != 0){
    pl = pl.next;
    len--;
}
//说明pl走了差值步了,接下来一起走直到他们两个相遇
  1. 差值步走完,俩链表一起走,直到相遇
while(pl != ps){
    pl = pl.next;
    ps = ps.next;
}
  1. 还要考虑特殊情况,没有交点的时候
if(headA == headB && headA == null){
    return null;
}

附上总的代码:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if(headA == null || headB == null) return null;

    //1,求俩个链表的长度
    int lenA = 0;
    int lenB = 0;
    ListNode pl = headA;//pl:永远指向长的链表
    ListNode ps = headB;//ps:永远指向短的链表

    while(pl != null){
        lenA++;
        pl = pl.next;
    }

    while (ps != null){
        lenB++;
        ps = ps.next;
    }

    pl = headA;
    ps = headB;
    //差值步 计算
    int len = lenA - lenB;
    if(len < 0){
        pl = headB;
        ps = headA;
        len = lenB - lenA;//更新差值步
    }
    //走到这里,pl一定指向的是那个最长的链表,ps一定指向的是最短的链表,且len一定是一个正数

    while (len != 0){
        pl = pl.next;
        len--;
    }
    //说明pl走了差值步了,接下来一起走直到他们两个相遇
    while(pl != ps){
        pl = pl.next;
        ps = ps.next;
    }

    //说明没有交点
    if(headA == headB && headA == null){
        return null;
    }
    return pl;
}

 

🏳️‍🌈九.环形链表

简介:

示例 1:
请添加图片描述

示例 2:
请添加图片描述

示例 3:

请添加图片描述

提示:

思路:

扩展问题:

  1. 为什么快指针每次走两步,慢指针走一步可以?
  1. 快指针一次走3步,走4步,…n步行吗?

附上总的代码:

public boolean hasCycle(ListNode head) {
    if(head == null) return false;
    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;
}

 

🍹十.环形链表 II

简介:

示例 1:

请添加图片描述

示例 2:

请添加图片描述

示例 3:

请添加图片描述

提示:

导图:

在这里插入图片描述

思路:

fast只走了一圈的情况下:

fast走了n圈:

总结:此处说明fast此时如果和slow速度一样,让slow从起点开始走,再走X步他们就会在相遇点相遇

  1. 考虑特殊情况
if(head == null) return null;
  1. 定义快慢指针,并且由总结可知,相遇的时候需要另外挪动指针
ListNode fast = head;
ListNode slow = head;

while(fast != null && fast.next != null){
    fast = fast.next.next;
    slow = slow.next;
    if(fast == slow){
        break;
    }
}
  1. break之后有两种情况,一是不满足循环,二是遇到break;说明是环且相遇
if(fast == null || fast.next == null){
    return null;
}
slow = head;
while(slow != fast){
    fast = fast.next;
    slow = slow.next;
}

附上总的代码:

public ListNode detectCycle(ListNode head) {
    if(head == 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;
        }
    }
    //走到这里说明遇到俩种情况
    //1.不满足循环,有一个为空
    //2.遇到break;说明是环且相遇
    if(fast == null || fast.next == null){
        return null;
    }
    slow = head;
    while(slow != fast){
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}
举报

相关推荐

0 条评论