链表

233 阅读9分钟

常见错误:空指针、死循环、边界条件

基本结构

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

基本操作

遍历链表

案例:给一个链表头,求这个链表的长度

迭代法:
int listLength(ListNode head){
    int len=0;
    while(head!null){
        head=head.next;
        len++;
    }
    return len;
}

int listLength(ListNode head){
    int len=0;
    for(ListNode p=head;p!=null;p=p.next){
        len++;
    }
    return len;
}

插入元素

  • 表头前(链表开始处):头插法
  • 表尾后(链表结尾处)
  • 表中间(随机位置)

删除元素

  • 表头前(链表开始处)
  • 表尾后(链表结尾处)
  • 表中间(随机位置)

左神笔记

1.链表的分类

  • 按照连接方向分类:单链表、双链表
  • 按照有环无环分类:普通链表、循环链表

2.链表问题代码实现的关键点

  • 链表调整函数的返回值类型,根据要求往往是节点类型,因为链表在调整过程中往往会有改变头部的情况发生,如果头结点换了,自然要返回新头部
  • 处理链表过程中,要注意思考哪些指针变化了,先采用画图的方式理清逻辑,看一下修改的哪些指针,同时要注意调整时前后节点的变化,环境需要预先保持下来,不然可能发生后面断线找不到后续节点的情况
  • 链表问题边界问题讨论要去严格,比如头结点、尾结点、空节点这些都是特殊值,不能总假设指针有意义,处理过程要时刻判断节点是否为空

3.关于链表插入和删除的注意事项

  • 特殊处理链表为空,或者链表长度为1的情况
  • 注意插入操作的调整过程
注意点:头尾节点及空节点需要特殊考虑
双链表的插入与删除和单链表类似,但是需要额外考虑previous指针的指向

4.单链表的翻转操作

  •  当链表为空或者长度为1时,特殊处理
  1. 大量链表问题可以使用额外数据结构来简化调整过程,比如队列和栈、数组结构;
  2. 但链表问题最优解往往是不适用额外数据结构的方法,无疑需要更好的代码实现能力

算法题

剑指6:从尾到头打印链表

思路一(最简单):在动态数组从头开始加入链表的值

import java.util.ArrayList;
public class Solution {
    public ArrayList printListFromTailToHead(ListNode listNode) {
        ArrayList array = new ArrayList<>();
        //遍历单链表
        while(listNode!=null){
            array.add(0,listNode.val);
            listNode=listNode.next;
        }
        return array;
    }
}
思路二:使用头插法构建逆序链表(这里藏着一个反转链表!!!)

import java.util.ArrayList;
public class Solution {
    public ArrayList printListFromTailToHead(ListNode listNode) {
        ListNode head = new ListNode(-1);
        while(listNode!=null){
            ListNode t=listNode.next;
            listNode.next=head.next;
            head.next=listNode;
            listNode=t;
        }
        head=head.next;
        ArrayList array = new ArrayList<>();
        while(head!=null){
            array.add(head.val);
            head=head.next;
        }
        return array;
    }
}

思路三:使用栈,利用栈的先进后出的顺序弹出链表数值(这题没写对)
注意:栈的定义很不熟悉,需要总结和复习
Stack<> stack
add()
pop()

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public ArrayList printListFromTailToHead(ListNode listNode) {
        Stack stack=new Stack<>();
        while(listNode!=null){
            stack.add(listNode.val);
            listNode=listNode.next;
        }
        ArrayList array = new ArrayList<>();
        while(!stack.isEmpty()){
            array.add(stack.pop());
        }
        return array;
    }
}

思路四:使用递归(这题没写对)
注意:1、对递归结构的理解不够,不容易写对 2、addAll()方法没用过,现在学到了

1和2处要好好理解
import java.util.ArrayList;
public class Solution {
    public ArrayList printListFromTailToHead(ListNode listNode) {
        ArrayList array = new ArrayList<>();
        if(listNode!=null){//这里是if
            array.addAll(printListFromTailToHead(listNode.next));//1
            array.add(listNode.val);//2
        }
        return array;
    }
}

剑指18:删除链表中重复的节点

思路一:双指针法
疑问:假如1->2->3->4->2->5,能不能把两个2都删除?想写函数验证一下。

public class Solution {
    public ListNode deleteDuplication(ListNode pHead){
        //因为头节点可能删除,所以需要一个新节点保存链表入口
        ListNode head=new ListNode(-1);
        head.next=pHead;
        //每次都落在不重复的节点上的判断指针,功能是让链表不断
        ListNode pre=head;
        //搜索用的工作指针
        ListNode last=pre.next;
        while(last!=null){
            //假设当前的和后面相同
            if(last.next!=null&&last.val==last.next.val){
                while(last.next!=null&&last.val==last.next.val){
                    last=last.next;
                }
                last=last.next;
                pre.next=last;
            //当前的和后面已经不同    
            }else{
                last=last.next;
                pre=pre.next;
            }
        }
        return head.next;
    }
}
思路二:递归法

待完成

LC86:删除链表中的重复元素

(能做出来,改了很多遍才改对,边界条件注意的不好)

思路:跟上题很像,注意返回值

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null||head.next==null)
            return head;
        ListNode pHead=new ListNode(-1);
        pHead.next=head;
        ListNode pre=pHead;
        ListNode last=pre.next;
        while(last!=null){
            if(last.next!=null&&last.val==last.next.val){
                while(last.next!=null&&last.val==last.next.val){
                    last=last.next;
                }
                pre.next=last;
            }else{
                pre=pre.next;
                last=last.next;
            }
        }
        return pHead.next;
    }
}

LC707 设计链表

class MyLinkedList {

    Node head;
    int length;
    /** 初始化链表 */
    public MyLinkedList() {
        length=0;
        head = new Node(-1);  
    }
    
    /** 获取链表中第 index 个节点的值。如果索引无效,则返回-1 */
    public int get(int index) {
        if(index>=length||index<0)
            return -1;
        Node current = head;
        for(int i = 0; i <= index; i++)
            current = current.next;
        return current.val;
    }
    
    /** 在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点 */
    public void addAtHead(int val) {
        Node temp = new Node(val);
        temp.next = head.next;
        head.next = temp;
        length ++;
    }
    /** 将值为 val 的节点追加到链表的最后一个元素 */
    public void addAtTail(int val) {
        Node current = head;
        while(current.next != null)
            current = current.next;
        Node temp = new Node(val);
        current.next = temp;
        length ++; 
    }
    
    /** 在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点 */
    public void addAtIndex(int index, int val) {
        if(index > length)
            return;
        if(index == length){
            addAtTail(val);
            return;
        }
        if(index < 0)
            index = index+length+1;
        Node current = head;
        for(int i = 0; i < index; i++)
            current = current.next;
        Node ptr = current.next;
        Node temp = new Node(val);
        current.next = temp;
        temp.next = ptr;
        length ++;
    }
    
    /** 如果索引 index 有效,则删除链表中的第 index 个节点 */
    public void deleteAtIndex(int index) {
        if(index >= length || index < 0)
            return;
        Node current = head;
        for(int i = 0; i < index; i++)
            current = current.next;
        current.next = current.next.next;
        length --;
    }
    
     public void print(){
        Node current = head;
        StringBuilder builder = new StringBuilder();
        while(current != null){
            builder.append(String.valueOf(current.val) + " ");
            current = current.next;
        }
        System.out.
(builder);
    }
}

class Node{
    int val;
    Node next;
    Node(int x){
        this.val=x;
    }
}
/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

剑指24 反转链表(多练多练,写的不好)

public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head==null||head.next==null)
            return head;
        ListNode pre=null;
        ListNode cur=head;
        ListNode next=head.next;
        while(cur!=null){
            cur.next=pre;
            pre=cur;
            cur=next;
            if(cur==null)
                continue;
            next=next.next;
        }
        return pre;
    }
}

头插法

1、画好变化流程图
2、起点什么样
3、重点什么样
4、循环条件判断是什么
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode newList=new ListNode(-1);//创建出来的单节点它的下一个节点是null
        while(head!=null){
            ListNode next=head.next;
            head.next=newList.next;
            newList.next=head;
            head=next;
        }
        return newList.next;
    }
}

我写错的头插法:
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode newList=new ListNode(-1);
        while(head!=null){
            ListNode t=head.next;
            head.next=newList;
            newList=head;
            head=t.next;
        }
        return head;
    }
}

LC92 反转链表2(反转部分链表)

思路一:递归
思路二:迭代反转

剑指22:链表中倒数第k个节点

进度不同的双指针(不是快慢指针),注意边界点!
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        ListNode slow=head;
        ListNode fast=head;
        int flag=0;
        while(fast!=null){
            fast=fast.next;
            if(flag>=k)
                slow=slow.next;
            flag++;
        }
        if(flag

剑指23:链表中环的入口节点

public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead){
        if(pHead==null||pHead.next==null)
            return null;
        ListNode fast=pHead;
        ListNode slow=pHead;
        //只要能走就不停下来
        while(fast.next!=null&&fast.next.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow){
                //如果此时已经相遇,slow还有z到达入口
                fast=pHead;
                while(fast!=slow){
                    fast=fast.next;
                    slow=slow.next;
                }
                return fast;
            }
        }
        return null;   
    }
}

剑指25:合并两个排序链表

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        ListNode pre=new ListNode(-1);
        ListNode cur=pre;
        ListNode p1=list1;
        ListNode p2=list2;
        while(p1!=null&&p2!=null){//写错了N次,链表题必须细致画图,做太久或者做错就完蛋了!!
            if(p1.val<=p2.val){
                cur.next=p1;
                p1=p1.next;
                cur=cur.next;
            }else{
                cur.next=p2;
                p2=p2.next;
                cur=cur.next;
            }
        }
        if(p1==null)
            cur.next=p2;
        if(p2==null)
            cur.next=p1;
        return pre.next;     
    }
}

剑指52:两个链表的第一个公共节点

(值得反复做,解法多,边界条件容易出错)

public class Solution {
    /**
     * 思路:如果有公共节点,1)若两个链表长度相等,那么遍历一遍后,在某个时刻,p1 == p2
     *                   2)若两个链表长度不相等,那么短的那个链表的指针pn(也就是p1或p2)
     *                     必先为null,那么这时再另pn = 链表头节点。经过一段时间后,
     *                     则一定会出现p1 == p2。
     *      如果没有公共节点:这种情况可以看成是公共节点为null,顾不用再考虑。
     */
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1 != p2) {
            if(p1 != null) p1 = p1.next;    //防止空指针异常
            if(p2 != null) p2 = p2.next;
            if(p1 != p2) {                  //当两个链表长度不想等
                if(p1 == null) p1 = pHead1;
                if(p2 == null) p2 = pHead2;
            }
        }
        return p1;
    }
}

剑指35:复杂链表的复制

public RandomListNode Clone(RandomListNode pHead) {
    if (pHead == null)
        return null;
    // 插入新节点
    RandomListNode cur = pHead;
    while (cur != null) {
        RandomListNode clone = new RandomListNode(cur.label);
        clone.next = cur.next;
        cur.next = clone;
        cur = clone.next;
    }
    // 建立 random 链接
    cur = pHead;
    while (cur != null) {
        RandomListNode clone = cur.next;
        if (cur.random != null)
            clone.random = cur.random.next;
        cur = clone.next;
    }
    // 拆分:没搞懂为何这样拆
    cur = pHead;
    RandomListNode pCloneHead = pHead.next;
    while (cur.next != null) {
        RandomListNode next = cur.next;
        cur.next = next.next;
        cur = next;
    }
    return pCloneHead;
}

我的错解
public class Solution {
    public RandomListNode Clone(RandomListNode pHead){
        if(pHead==null)
            return null;
        //复制节点
        RandomListNode cur=pHead;       
        while(cur!=null){
            RandomListNode clone=new RandomListNode(cur.label);
            clone.next=cur.next;
            cur.next=clone;
            cur=clone.next;
        }
        //复制随机指针
        cur=pHead;
        while(cur!=null){
            RandomListNode clone=cur.next;
            if(cur.random!=null)
                clone.random=cur.random.next;
            cur=clone.next;
        }
        //拆分链表
        RandomListNode newHead=pHead.next;//定义出来以后就不用动了
        cur=newHead;
        while(cur.next!=null){
            RandomListNode next=cur.next.next;
            cur.next=next;
            cur=next;
        }
        return newHead;
    }
}

LC141 环形链表

思路一:哈希表(思路非常清晰)

import java.util.*;
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set set = new HashSet<>();
        while(head!=null){
            if(set.contains(head)){
                return true;
            }else{
                set.add(head);
            }
            head=head.next;
        }
        return false;
    }
}

思路二:快慢指针 

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null||head.next==null)
            return false;
        ListNode fast=head.next;//快指针得抢跑,不然刚进入循环就追上了
        ListNode slow=head;
        while(fast!=slow){
            if(fast==null||fast.next==null)
                return false;
            fast=fast.next.next;
            slow=slow.next;
        }
        return true;
    }
}

LC142 环形链表2(剑指offer23 链表中环的入口节点)

思路一:哈希表

public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set set=new HashSet<>();
        while(head!=null){
            if(set.contains(head)){
                return head;
            }else{
                set.add(head);
            }
           head=head.next;
        }
        return null;      
    }
}

public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set visited = new HashSet();

        ListNode node = head;
        while (node != null) {
            if (visited.contains(node)) {
                return node;
            }
            visited.add(node);
            node = node.next;
        }

        return null;
    }
}

思路二:快慢指针
注意:跟LC141不一样的是,两个指针是同起点出发的

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null||head.next==null)
            return null;
        ListNode fast=head;
        ListNode slow=head;
        //先判断是否有环
        while(fast.next!=null&&fast.next.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            //如果相遇,可以进行第二步操作
            if(fast==slow){
                fast=head;
                while(fast!=slow){
                    fast=fast.next;
                    slow=slow.next;
                }
                //出循环的时候第二次相遇
                return slow;
            } 
        }
        //出循环就说明没环
        return null;     
    }
}

LC160 相交链表(剑指52  两个链表的第一个公共节点)
哈希表法

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set set=new HashSet<>();
        while(headA!=null){
            set.add(headA);
            headA=headA.next;
        }
        while(headB!=null){
            if(set.contains(headB))
                return headB;
            headB=headB.next;
        }
        return null;
    }
}

双指针法

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1=headA;
        ListNode p2=headB;
        while(p1!=p2){
            if(p1==null)p1=headB;
            else
                p1=p1.next;
            if(p2==null)p2=headA;
            else
                p2=p2.next;
        }
        return p1;      
    }
}

LC19 删除链表的倒数第N个节点

不认真画图相当容易写错
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre=new ListNode(-1);
        pre.next=head;
        ListNode p1=pre;
        ListNode p2=pre;
        int flag=0;
        while(p2!=null){
            p2=p2.next;
            if(flag>n)
                p1=p1.next;
            flag++;
        }
        if(flag<=n)
            return head;
        p1.next=p1.next.next;
        return pre.next;
    }
}

待完成

移除链表元素
奇偶链表
回文链表
合并两个有序链表
两数相加
扁平化多级双向链表
复制带随机指针的链表
旋转链表