单链表问题总结

参考资料:
1. Leetcode
2. 【Hackbuteer1的专栏】http://blog.csdn.net/hackbuteer1/article/details/7583102
最近做了些单链表的题目,觉得要解决单链表问题,可以从以下几个方面入手:
先定义链表的结构

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
 }

1. 要学会画草图进行模拟
2. 要会链表的逆置
链表的逆置是许多其他链表题目的基础,给定一个链表,我们可以采用头插法的方法对这个链表进行逆置,代码如下:

    /**
     * 逆转链表
     * */
    public static ListNode reverseListNode(ListNode node){
        ListNode reverseHead=null;//逆转后链表的头结点
        ListNode moveNode=null;
        while(node!=null){
            moveNode=node.next; //保存待逆置的链表的下一个节点
            if(reverseHead==null){ //第一个节点即已逆置的链表的尾节点
                reverseHead=node;
                reverseHead.next=null;
            }else{ //采用头插法建立链表
                node.next=reverseHead;
                reverseHead=node;
            }
            node=moveNode;
        }
        return reverseHead;
    }

以链表 1->2->3 为例子,模拟如下:
这里写图片描述

学会了逆置后,我们可以做做这个题https://leetcode.com/problems/palindrome-linked-list/,就是判断一个链表里的内容是不是回文字符串,要求O(n)的时间复杂度和O(1)的空间复杂度。我们的做法可以是:先遍历一遍链表,求出链表的长度L,然后从L/2即中间节点的位置将后半段链表逆置,再依次遍历逆置的链表以及前半段链表,进行比较就可以判断是不是回文字符串了。

3. 容器比如Set或Stack很好用,如果在对空间复杂度没有要求的话,可以考虑使用容器
有时候没有要求空间复杂度,我们可以借助一些集合来处理,因为Set具有元素唯一的特性即其不能存储重复的元素,而栈具备先进后出的特性。这里针对之前的判断回文字符串的问题,我们可以使用一个栈:先遍历一遍链表,将所有节点入栈,之后再次遍历链表并且出栈,进行比较判断,也能求得链表是不是回文字符串,这里时间复杂度也是O(n),但其空间复杂度也是O(n)。
使用Set可以很好的解决链表存不存在环的问题,比如给定一个单链表,判断链表是否存在环,如果存在环求出环的入口节点。针对这种题目,我们使用一个Set,然后从链表头节点开始add,由于Set的add方法在添加重复的元素时返回false,因此针对链表有两种情况:
1. 链表不含有环:那么可以一直add,不会返回false,直到链表结尾
2. 链表含有环:那么在不停的add过程中会进入到环里,肯定会add之前已经add的节点,此时会返回false,而且第一次返回false时对应的节点就是环的入口节点,可以画个草图模拟下,代码如下:

    public boolean hasCycle(ListNode head) {
        Set<ListNode> nodeSet=new HashSet<>();
        while(head!=null){
            if(!nodeSet.add(head)) //如果添加不成功,说明之前已经存在该节点,即有环,此时这个head节点即为环的入口节点
                return true;
            head=head.next;
        }
        return false;
    }

如果针对求环的问题要求空间复杂度为O(1)呢?我们就不能使用Set了,可以使用下面介绍的两个指针的方法

4. 要学会使用两个指针
有时间针对一个链表我们需要使用两个指针来实现所谓的“追赶”问题,针对两个链表则更需要两个指针了,我们先使用两个指针来解决判断两个链表是否相交的链表,如下图所示:
这里写图片描述
方法1:先计算L1,L2的长度为S1,S2,假设S1大于S2(反之亦然),设立两个指针指向L1,L2,L1先走(S1-S2)步,此时L1,L2是处于同一个位置的,然后同时移动,如果链表相交必然会存在L1==L2,否则链表相交,这里的时间复杂度是O(max(S1,S2)),空间复杂度为O(1)
方法2:将两个链表分别逆置,判断逆置后两个链表的头节点是否相等,相等则两个链表相交,否则不相交。
方法3:可以将任一链表的尾节点的后继指向另一个链表的头节点,此时就变成了判断这个链表是否存在环的问题
将两个指针最恰当的运行就是在O(1)空间复杂度判断一个链表是否存在环,如果存在环找出环的入口节点,这里说下简要的步骤,具体细节可参考开头引用的博客。
判断链表是否存在环:设立两个指针slow,fast,开始均指向链表的头结点,接着slow每次走一步,fast每次走两步,如果不存在环fast肯定先为NULL,如果存在环,fast绕环n圈后肯定会追上slow,即fast==slow
找出环的入口节点:根据前面得到了fast与slow第一次相遇的节点p,重新设立指针p1,p2。p1指向链表头结点,p2指向p。p1,p2同时移动相同的步数,那么p1,p2第一相遇的节点即为环的入口节点,至于为什么,相关的证明可以上面引用的博客。

总结:我觉得链表题不算特别难,主要是自己得细心,考虑一些极端情况(比如指针为NULL之类的);另外就是在处理链表的时候,注意各个引用的关系(最好画草图),别把节点的指向搞混了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zlp1992

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值