【链表】707. 设计链表

707. 设计链表

题目

707. 设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

示例:

MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3

解法

链表是一个包含零个或多个元素的数据结构。每个元素都包含一个值和到另一个元素的链接。根据链接数的不同,可以分为单链表,双链表和多重链表。

单链表是最简单的一种,它提供了在常数时间内的 addAtHead 操作和在线性时间内的 addAtTail 的操作。
双链表是最常用的一种,因为它提供了在常数时间内的 addAtHead 和 addAtTail 操作,并且优化的插入和删除。

双链表在 Java 中的实现为 LinkedList,在 Python 中为 list。这些结构都比较常用,有两个要点:

  • 哨兵节点:哨兵节点在树和链表中被广泛用作伪头、伪尾等,通常不保存任何数据。我们将使用伪头来简化我们简化插入和删除。在接下来的两种方法中应用此方法。

  • 双链表的双向搜索:我们可以从头部或尾部进行搜索。

单链表

在这里插入图片描述

哨兵节点dummyHead被用作伪头始终存在,这样结构中永远不为空,它将至少包含一个伪头。MyLinkedList 中所有节点均包含:值 + 链接到下一个元素的指针。

因为伪头的关系 addAtHead 和 addAtTail 可以使用 addAtIndex 来完成。

这个想法很简单:

  • 找到要插入位置节点的前驱节点。如果要在头部插入,则它的前驱节点就是伪头。如果要在尾部插入节点,则前驱节点就是尾节点。
  • 通过改变 next 来插入节点。
public class MyLinkedList {
    
    
    int size;
    ListNode dummyHead;
    public MyLinkedList() {
    
    
        size = 0;
        dummyHead = new ListNode(-1);
    }

    public int get(int index) {
    
    
        if (index < 0 || index >= size) {
    
    
            return -1;
        }
        ListNode cur = dummyHead;
        for (int i = 0; i < index + 1; i++) {
    
    
            cur = cur.next;
        }
        return cur.val;
    }

    public void addAtHead(int val) {
    
    
        addAtIndex(0, val);
    }

    public void addAtTail(int val) {
    
    
        addAtIndex(size, val);
    }

    public void addAtIndex(int index, int val) {
    
    
        if (index > size) {
    
    
            return;
        }
        index = index < 0 ? 0 : index;
        ++size;
        ListNode pre = dummyHead;
        for (int i = 0; i < index; i++) {
    
    
            pre = pre.next;
        }

        ListNode toAdd = new ListNode(val);
        toAdd.next = pre.next;
        pre.next = toAdd;
    }

    public void deleteAtIndex(int index) {
    
    
        if (index < 0 || index >= size) {
    
    
            return;
        }
        size--;
        ListNode pre = dummyHead;
        for (int i = 0; i < index; i++) {
    
    
            pre = pre.next;
        }
        pre.next = pre.next.next;
    }
}

双链表

双链表比单链表快得多,测试用例花费的时间比单链表快了两倍。但是它更加复杂,它包含了 size,记录链表元素个数,和伪头伪尾。
在这里插入图片描述

  • 找到要插入节点的前驱节点和后继节点。如果要在头部插入节点,则它的前驱结点是伪头。如果要在尾部插入节点,则它的后继节点是伪尾。
  • 通过改变前驱结点和后继节点的链接关系添加元素。
toAdd.prev = pred
toAdd.next = succ
pred.next = toAdd
succ.prev = toAdd

在这里插入图片描述
get:

  • 通过比较 index 和 size - index 的大小判断从头开始较快还是从尾巴开始较快。
  • 从较快的方向开始。
public class ListNode {
    
    
  int val;
  ListNode next;
  ListNode prev;
  ListNode(int x) {
    
     val = x; }
}

class MyLinkedList {
    
    

    int size;
    ListNode dummyHead;
    ListNode dummyTail;

    public MyLinkedList() {
    
    
        size = 0;
        dummyHead = new ListNode(-1);
        dummyTail = new ListNode(-1);
        dummyHead.next = dummyTail;
        dummyTail.prev = dummyHead;
    }

    public int get(int index) {
    
    
        if (index < 0 || index >= size) {
    
    
            return -1;
        }
        ListNode curr = dummyHead;
        // 判断从头部开始比较快还是从尾部比较快
        if (index + 1 < size - index) {
    
    
            for (int i = 0; i < index + 1; i++) {
    
    
                curr = curr.next;
            }
        } else {
    
    
            curr = dummyTail;
            for (int i = 0; i < size - index; i++) {
    
    
                curr = curr.prev;
            }
        }
        return curr.val;
    }

    public void addAtHead(int val) {
    
    
        ++size;
        ListNode pre = dummyHead;
        ListNode succ = dummyHead.next;
        ListNode toAdd = new ListNode(val);
        toAdd.prev = pre;
        toAdd.next = succ;
        pre.next = toAdd;
        succ.prev = toAdd;
    }

    public void addAtTail(int val) {
    
    
        ++size;
        ListNode succ = dummyTail;
        ListNode pre = dummyTail.prev;
        ListNode toAdd = new ListNode(val);
        toAdd.prev = pre;
        toAdd.next = succ;
        pre.next = toAdd;
        succ.prev = toAdd;
    }

    public void addAtIndex(int index, int val) {
    
    
        if (index > size) {
    
    
            return;
        }
        if (index < 0) {
    
    
            index = 0;
        }
        ListNode pre;
        ListNode succ;
        // 判断从头部开始比较快还是从尾部比较快
        if (index < size - index) {
    
    
            pre = dummyHead;
            for (int i = 0; i < index; ++i) {
    
    
                pre = pre.next;
            }
            succ = pre.next;
        } else {
    
    
            succ = dummyTail;
            for (int i = 0; i < size - index; i++) {
    
    
                succ = succ.prev;
            }
            pre = succ.prev;
        }
        ++size;
        ListNode toAdd = new ListNode(val);
        toAdd.prev = pre;
        toAdd.next = succ;
        pre.next = toAdd;
        succ.prev = toAdd;
    }

    public void deleteAtIndex(int index) {
    
    
        if (index < 0 || index >= size) {
    
    
            return;
        }
        ListNode pre;
        ListNode succ;
        // 判断从头部开始比较快还是从尾部比较快
        if (index < size - index) {
    
    
            pre = dummyHead;
            for (int i = 0; i < index; ++i) {
    
    
                pre = pre.next;
            }
            // 注意这里的succ是删除之后的后继节点
            succ = pre.next.next;
        } else {
    
    
            succ = dummyTail;
            // 注意这里是size - index - 1,前面add是size - index
            for (int i = 0; i < size - index - 1; i++) {
    
    
                succ = succ.prev;
            }
            // 注意这里的pre是删除之后的前序节点
            pre = succ.prev.prev;
        }
        --size;
        pre.next = succ;
        succ.prev = pre;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_17677907/article/details/113439632