目录
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList
类:
MyLinkedList()
初始化 MyLinkedList
对象。
int get(int index)
获取链表中下标为 index
的节点的值。如果下标无效,则返回 -1
。
void addAtHead(int val)
将一个值为 val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtTail(int val)
将一个值为 val
的节点追加到链表中作为链表的最后一个元素。
void addAtIndex(int index, int val)
将一个值为 val
的节点插入到链表中下标为 index
的节点之前。如果 index
等于链表的长度,那么该节点会被追加到链表的末尾。如果 index
比长度更大,该节点将 不会插入 到链表中。
void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为 index
的节点。
示例:
输入 ["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"] [[], [1], [3], [1, 2], [1], [1], [1]] 输出 [null, null, null, null, 2, null, 3] 解释 MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addAtHead(1); myLinkedList.addAtTail(3); myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3 myLinkedList.get(1); // 返回 2 myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3 myLinkedList.get(1); // 返回 3
提示:
0 <= index, val <= 1000
请不要使用内置的 LinkedList 库。
调用 get
、addAtHead
、addAtTail
、addAtIndex
和 deleteAtIndex
的次数不超过 2000
。
1.实现单向链表,每个节点保存本身的值和后继节点。除此之外,我们还需要一个哨兵(sentinel)节点作为头节点,和一个size参数保存有效节点数。初始化时,只需创建头节点head和size即可。
2.实现addAtIndex(index, val)时,先判断index是有效值,找到原来下标为 index的节点的前驱节点pred,并创建新节点 toAdd,将toAdd的后继节点设为pred 的后继节点,将pred的后继节点更新为toAdd,这样就将 toAdd插入到了链表中。最后需要更新size。哨兵节点的好处就在于这样的操作对于index=0也成立。
需要注意链表的插入操作,下面两行交换顺序会发生指针丢失和内存泄漏。
toAdd.next = pred.next; // toAdd节点值为25,pred节点值为0 pred.next = toAdd;
3.实现deleteAtIndex(index),先判断index是否有效。然后找到下标为index的节点的前驱节点pred,通过将 pred的后继节点更新为pred的后继节点的后继节点,来达到删除节点的效果。同时也要更新size。
4.至于addAtHead(int val)和addAtTail(int val)其实就是addAtIndex(int index, int val)中index分别是0和size时候的情况。
class MyLinkedList { int size = 0; ListNode head; public MyLinkedList() { // 链表节点数目 size = 0; // 哨兵节点,方便判断头节点为空的情况 head = new ListNode(0); } public int get(int index) { // 判断index无效,直接返回 if (index >= size || index < 0) { return -1; } // 找到index的前置节点 ListNode pred = head; for (int i = 0; i <= index; i++) { pred = pred.next; } return pred.val; } public void addAtHead(int val) { addAtIndex(0, val); } public void addAtTail(int val) { addAtIndex(size,val); } public void addAtIndex(int index, int val) { // 判断index无效,直接返回 if (index > size || index < 0) { return; } ListNode pred = head; // 找到index前置节点 for (int i = 0; i < index; i++) { pred = pred.next; } ListNode toAdd = new ListNode(val); toAdd.next = pred.next; pred.next = toAdd; size++; } public void deleteAtIndex(int index) { // 判断index无效,直接返回 if (index >= size || index < 0) { return; } ListNode pred = head; // 找到index前置节点 for (int i = 0; i < index; i++) { pred = pred.next; } pred.next = pred.next.next; size--; } }
时间复杂度:初始化O(1),get为O(index),addAtHead为O(1),addAtTail为O(n),n为链表长度,addAtIndex为O(index)。根据获取节点时循环次数可以知道时间复杂度的情况。
空间复杂度:单次调用空间复杂度O(1),链表结构总体空间复杂度O(n)。