链表的实际应用

简介: 链表的实际应用

1. 删除链表中等于给定值 val 的所有节点。


链接:203. 移除链表元素 - 力扣(Leetcode)


假设:我们有这么个链表:


d7b4c99306e24ea39ed169af8a68d506.png

我们需要删除所有date域为key的结点。

假设key为4:

思路(快慢指针):

1. 我们设置两个指针,一个叫做fast,用于与date域进行判断;另一个叫做slow,用于将所有的链表串联。fast指向head.next;slow指向slow。


8a51c4d1f41f429588c504ab4a8ab26d.png


2. fast先走,判断date域中的的值和key的对比结果。如果结果为不相等,那么slow和fast一起向后走一步;



7e0ec253bf2d43eb86a339502e70e3fa.png



4aca650a8f9d4797b5318898a4b451db.png


如果结果相等:

将fast.next 赋给slow.next ,用于跳过这个指针。



9d87f7831af4448ab330b8e8e60d5d35.png


179df62af99b43ff8b376d18181a46a7.png


继续向后走:


ed0b121d144d400fbdbf134215af99af.png



我们可以知道直到fast为null时,我们就结束了。

3. 考虑特殊情况;1. 我们的fast是指向第二个结点的,可能只有单个结点;2. 我们的思路没有考虑头结点的date域为key的情况。

class Solution {
    public ListNode removeElements(ListNode head, int key) {
        if (head == null) {
            return null;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast != null) {
            if(fast.date== key) {
                slow.next = fast.next;
            } else {
                slow = fast;
            }
            fast = fast.next;
        }
        return head;
    }
}


我们还缺少一段代码:

 if(head.date== key) {
            head = head.next;
        }


对于这段代码放在哪里也需要考虑,如果放在循环之前,那么会有漏掉一个结点的情况,我们来测试一下。

假设key为2:



e53d813633f24516ba56814c02315c4f.png

判断head.date 是否与key相等,结果为真,那么



c59dadffcb67440c95f5a86c415fbd18.png


我们新的head结点的date域还是2,这种情况下是不能解决问题的。

我们可以试着先不管head结点,把其他的结点全部排序好,最后再来处理head结点,所以我们可以把该段代码放在循环之后:

class Solution {
    public ListNode removeElements(ListNode head, int key) {
        if (head == null) {
            return null;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast != null) {
            if(fast.date== key) {
                slow.next = fast.next;
            } else {
                slow = fast;
            }
            fast = fast.next;
        }
        if(head.date== key) {
            head = head.next;
        }
        return head;
    }
}


我们来看看结果如何:


ff3cd5524e73438ba87ff24d61415b3e.png



2. 反转一个单链表。


链接:206. 反转链表 - 力扣(Leetcode)

假设链表为:



24647e235eed4bbc8aa4bc694ac6f6fc.png


我们的最终目的是变成:


9c25c3a3410b4f1b9752b7ddde9c57b0.png


我们的思路有很多,但是我们要求只遍历一遍就将链表翻转。

我们有一个最简单的思路,头变尾,尾变头:



9d6aaf4651804875b3bf650a2d3f7c5c.png



将head结点的next变为null;


5e6f84d043fb4a0898866a5ebc8b1933.png



将第二个结点变为新的head结点原来的head结点接到新的head结点,后面的所有结点也如此。

具体思路:

1. 先创建一个 cur 结点保存 head.next,再将head结点的next域置为null;


846532772d8742bba50c16d4b4abafa7.png



 2. 创建一个curNext结点,用于保存cur.next ;将head的地址保存到cur.next 中,


6593b048b80147dda4720abd6ebd3775.png


 3.  将cur赋给head,将curNext赋给cur:


ed506293840e4fe6baf9857738807dcc.png

4. 重复2 3 两步操作:


bfcb8fcc02624a2381a14d68cc2c4926.png

直到cur 为 null。


14188a0324a04178b4354d1b46c1701c.png


当然我们还需要考虑特殊情况如果head为null,那么直接返回null即可,如果只有head一个结点,那么我们返回head。

代码如下:

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


结果如下:


6a508ffb8a8f43e3b5522d5252cfebc4.png



3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。


链接:876. 链表的中间结点 - 力扣(Leetcode)


这题很简单,分析如下:


设置两个指针,一个fast,一个slow,我们要找到中间结点,如果是单数比如5,我们的中间结点元素的下表应该为2,恰好为正中间。如果是双数,比如4,中间结点的下标为:4 / 2 = 2。


那么思路就有了:


快指针fast每次走2步,慢指针slow每次走1步,当fast走到末尾时slow正好走到中间。


代码:

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode prve = head;
        ListNode cur = head;
        while(prve != null && prve.next != null) {
            prve = prve.next.next;
            cur = cur.next;
        }
        return cur;
    }
}


结果:


4cc17a7275e342fe9bc2b79ad7658c34.png



我们这一题的目的是为了引出下一题:

输入一个链表,输出该链表中倒数第k个结点


4. 输入一个链表,输出该链表中倒数第k个结点


链接:链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)

在上一题的基础上,我们去找倒数第k个结点:


adf6eba55fab4b0396e0009628a04f0a.png



思路(快慢指针):


91dc17d0af4741dfa9ed26137892abe0.png

我们先让fast跑到k-1步;为什么是走k-1步呢?我们找中间结点堆成,无论是单数结点,还是双数结点;比如4,找倒数第二个结点,fast先走1步,然后再fast和slow一起一步步走,fast走到最后一个,slow恰好走到目标结点。


因为与目标结点相对称的结点,恰好距离fast k - 1 步。


测试:假设上图中目标结点为倒数第二个结点:


fast先走2-1 步

966dabfff2ab4c35ab5205c0c6f58be2.png


接下来fast和slow一起走:


e72227552bab4d41a1d193c4c3fc6ac0.png

de2ae45e101143dcb4a00f3e3f62b4e3.png


当fast走到末尾时,slow恰好走到目标结点:


当然不能忘记特殊情况,如果k不合理或者,head为空时需要单独处理:


代码如下:

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if (k <= 0 || head == null ) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (k - 1 != 0) {
            fast = fast.next;
            if(fast == null) {
                return null;
            }
            k--;
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

结果如下:


e3aec8a295074cd2a459ca847e2800c9.png


相关文章
|
4天前
|
存储 算法
单链表的应用
单链表的应用
25 6
|
6天前
链表\链表基础应用
链表\链表基础应用
8 3
|
1月前
|
存储 调度 C语言
链表,栈,队列的区别及其应用
链表,栈,队列的区别及其应用
|
1月前
|
存储 算法 C语言
链表带头和不带头的区别及其应用
链表带头和不带头的区别及其应用
|
1月前
|
存储 C语言
链表—初始化指针变和创建新的节点------区别应用分析
链表—初始化指针变和创建新的节点------区别应用分析
|
1月前
单链表的应用
上篇博客中,我们学习了单链表,为了更加熟练掌握这一知识点,就让我们将单链表的应用操练起来吧!
21 0
|
1月前
|
存储 编译器 C语言
【数据结构】深入浅出理解链表中二级指针的应用
【数据结构】深入浅出理解链表中二级指针的应用
42 0
|
1月前
|
存储 缓存 算法
C++链表解析:从基础原理到高级应用,全面掌握链表的使用
C++链表解析:从基础原理到高级应用,全面掌握链表的使用
97 0
|
1月前
|
算法
链表中快慢指针的应用
链表中快慢指针的应用
|
6月前
数据结构循环链表之介绍和应用 | 第一套
数据结构循环链表之介绍和应用 | 第一套
33 0