链表指针转动,很容易转晕,介绍其中有几个常见的技巧。

一.指针/引用含义

指针/引用,实际上都是存储所指对象内存。

将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。

二.指针丢失和内存泄漏

插入节点时,注意操作顺序。

删除节点时,注意释放空间。

三.哨兵

哨兵结点的链表叫做带头链表,这样节省判断head == null

四.边界条件

  • 如果链表为空时,代码是否能正常工作?
  • 如果链表只包含一个结点时,代码是否能正常工作?
  • 如果链表只包含两个结点时,代码是否能正常工作?
  • 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
  • 链表结点位置,在头部,中部,尾部的相关操作,是否能正常工作?

五.常见问题

5.1 链表反转

注意:指针的反转

public void reverseList(){
    Link resLink = null;
    Link prevLink = null;
    Link curLink = first;
    while (curLink != null){
       //记录下一个结点
        Link nextLink = curLink.getNext();
        if(nextLink == null){
            //所有结点遍历完毕
            resLink = curLink;
        }
        //指针反转,指针向前
        curLink.setNext(prevLink);
        prevLink = curLink;
        curLink = nextLink;
    }
    Assert.assertNotNull(resLink);
}

5.2 链表中环的检测

注意:快慢指针方式,快指针是慢指针的两倍。

public boolean checkCire(){
    boolean result = false;
    if(first == null){
        return  result;
    }
    Link fast = first.getNext();
    Link slow= first;
    //快指针与慢指针相差两步,如果有环,会一直循环,总会相碰。
    while (fast!= null && fast.getNext() != null){
        fast = fast.getNext().getNext();
        slow = slow.getNext();
        if(fast == slow){
            return true;
        }
    }
    return result;
}

5.3 两个有序的链表合并

注意:确定结点头部,中部,尾部的位置判定。

public Link mergeNode(Link pNode, Link qNode) {
    if (pNode == null)
        return qNode;
    if (qNode == null)
        return pNode;
    Link p = pNode;
    Link q = qNode;
    Link resNode = null;
    //第一个结点判断
    if (p.getData() < q.getData()) {
        resNode = p;
        p = p.getNext();
    } else {
        resNode = q;
        q = q.getNext();
    }
    Link node = resNode;
    while (p != null && q != null) {
        if (p.getData() < q.getData()) {
            node.setNext(p);
            p = p.getNext();
        } else {
            node.setNext(q);
            q = q.getNext();
        }
        node = node.getNext();
    }
    //末尾节点
    if (p != null) {
        node.setNext(p);
    } else {
        node.setNext(q);
    }
    return resNode;
}

5.4 删除链表倒数第n结点

注意:快慢指针方式实现,快指针的步数是变动的。

public void deleteLastKByNode(int k){
    Link fast = first;
    //计数,排除k等0情况。
    int i = 1;
    while (fast != null && i < k){
        fast = fast.getNext();
    }
    if(fast == null){
        return;
    }
    Link slow = first;
    Link preNode = null;
    while (fast.getNext() != null){
        fast = fast.getNext();
        preNode = slow;
        slow = slow.getNext();
    }
    //头结点删除
    if(preNode == null){
        first = first.getNext();
    }else{
        //删除
        preNode.setNext(preNode.getNext().getNext());
    }
    Assert.assertNotNull(first);
}

5.5 求链表的中间结点

注意:快慢指针方式,快指针是慢指针的二倍。

public void findMiddleByNode(){
    if(first == null) return;
    Link fast = first.getNext();
    Link slow = first;
    //快指针走完一圈,慢指针半圈
    while (fast != null && fast.getNext() != null){
        fast = fast.getNext().getNext();
        slow = slow.getNext();
    }
    Assert.assertNotNull(slow);
}

5.6 代码

链表

参考

《数据结构与算法之美》