编程导航算法通关村第一关 | 链表青铜挑战

442 阅读3分钟

什么是链表

什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链表的入口节点称为链表的头结点也就是head

为什么要有虚拟头结点

虚拟头节点没有数据,只是指向链表的头结点,这也做是为了在删除链表头节点的时候能统一方式

现在我们有这么一个需求:删除单链表的头节点

下面是没有虚拟头节点的操作

image.png

要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点

当有虚拟头节点时

image.png 我们只需要删除dummyNode.next就可以了,这样就达到和移除链表其他节点的方式统一了

创建链表

public class ListNode {
    // 节点的值
    public int val;
    // 下一个节点
    public ListNode next;

    // 节点的构造函数(无参)
    public ListNode() {
    }

    // 节点的构造函数(有参)
    public ListNode(int val) {
        this.val = val;
    }

    // 节点的构造函数(有两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

链表的基础操作

遍历

public static int getLength(Node head) {
    int length = 0;
    Node node = head;
    while (node != null) {
        length++;
        node = node.next;
    }
    return length;
}

插入

/**
     * 链表插入
     *
     * @param head       链表头节点
     * @param nodeInsert 待插入节点
     * @param position   待插入位置,取值从2开始
     * @return 插入后得到的链表头节点
     */
    public static Node insertNode(Node head, Node nodeInsert, int position) {
        // 需要判空,否则后面可能会有空指针异常
        if (head == null) {
            return nodeInsert;
        }
        //越界判断
        int size = getLength(head);
        if (position > size + 1 || position < 1) {
            System.out.println("位置参数越界");
            return head;
        }

        //在链表开头插入
        if (position == 1) {
            nodeInsert.next = head;
//            return nodeInsert;
            //上面return还可以这么写:
            head = nodeInsert;
            return head;
        }

        Node pNode = head;
        int count = 1;
        while (count < position - 1) {
            pNode = pNode.next;
            count++;
        }
        nodeInsert.next = pNode.next;
        pNode.next = nodeInsert;

        return head;
    }

删除

/**
 * 删除节点
 *
 * @param head     链表头节点
 * @param position 删除节点位置,取值从1开始
 * @return 删除后的链表头节点
 */
public static Node deleteNode(Node head, int position) {
    if (head == null) {
        return null;
    }
    int size = getLength(head);
    //思考一下,这里为什么是size,而不是size+1
    if (position > size || position <1) {
        System.out.println("输入的参数有误");
        return head;
    }
    if (position == 1) {
        //curNode就是链表的新head
        return head.next;
    } else {
        Node cur = head;
        int count = 1;
        while (count < position - 1) {
            cur = cur.next;
            count++;
        }
        Node curNode = cur.next;
        cur.next = curNode.next;
    }
    return head;
}

自己写一个链表

下面我们自己来写一个链表

class ListNode{
    int val;
    ListNode next,prev;
    ListNode() {};
    ListNode(int val){
        this.val = val;
    }
}


class MyLinkedList {  

    //记录链表中元素的数量
    int size;
    //记录链表的虚拟头结点和尾结点
    ListNode head,tail;
    
    public MyLinkedList() {
        //初始化操作
        this.size = 0;
        this.head = new ListNode(0);
        this.tail = new ListNode(0);
        //这一步非常关键,否则在加入头结点的操作中会出现null.next的错误!!!
        head.next=tail;
        tail.prev=head;
    }
    
    public int get(int index) {
        //判断index是否有效
        if(index<0 || index>=size){
            return -1;
        }
        ListNode cur = this.head;
        //判断是哪一边遍历时间更短
        if(index >= size / 2){
            //tail开始
            cur = tail;
            for(int i=0; i< size-index; i++){
                cur = cur.prev;
            }
        }else{
            for(int i=0; i<= index; i++){
                cur = cur.next; 
            }
        }
        return cur.val;
    }
    
    public void addAtHead(int val) {
        //等价于在第0个元素前添加
        addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
        //等价于在最后一个元素(null)前添加
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        //index大于链表长度
        if(index>size){
            return;
        }
        //index小于0
        if(index<0){
            index = 0;
        }
        size++;
        //找到前驱
        ListNode pre = this.head;
        for(int i=0; i<index; i++){
            pre = pre.next;
        }
        //新建结点
        ListNode newNode = new ListNode(val);
        newNode.next = pre.next;
        pre.next.prev = newNode;
        newNode.prev = pre;
        pre.next = newNode;
        
    }
    
    public void deleteAtIndex(int index) {
        //判断索引是否有效
        if(index<0 || index>=size){
            return;
        }
        //删除操作
        size--;
        ListNode pre = this.head;
        for(int i=0; i<index; i++){
            pre = pre.next;
        }
        pre.next.next.prev = pre;
        pre.next = pre.next.next;
    }
}