标题:设计链表
出处:707. 设计链表
5 级
设计链表的实现。你可以选择使用单向链表或双向链表。
单向链表中的结点应该具有两个属性: val \texttt{val} val 和 next \texttt{next} next。 val \texttt{val} val 是当前结点的值, next \texttt{next} next 是指向下一个结点的指针/引用。
如果要使用双向链表,则还需要一个属性 prev \texttt{prev} prev 以指示链表中的上一个结点。假设链表中的结点下标从 0 \texttt{0} 0 开始。
实现 MyLinkedList \texttt{MyLinkedList} MyLinkedList 类:
示例 1:
输入:
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"] \texttt{["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]} ["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]] \texttt{[[], [1], [3], [1, 2], [1], [1], [1]]} [[], [1], [3], [1, 2], [1], [1], [1]]
输出:
[null, null, null, null, 2, null, 3] \texttt{[null, null, null, null, 2, null, 3]} [null, null, null, null, 2, null, 3]
解释:
MyLinkedList linkedList = new MyLinkedList(); \texttt{MyLinkedList linkedList = new MyLinkedList();} MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1); \texttt{linkedList.addAtHead(1);} linkedList.addAtHead(1);
linkedList.addAtTail(3); \texttt{linkedList.addAtTail(3);} linkedList.addAtTail(3);
linkedList.addAtIndex(1, 2); \texttt{linkedList.addAtIndex(1, 2);} linkedList.addAtIndex(1, 2); // 链表变为 1 → 2 → 3 \texttt{1} \rightarrow \texttt{2} \rightarrow \texttt{3} 1→2→3
linkedList.get(1); \texttt{linkedList.get(1);} linkedList.get(1); // 返回 2 \texttt{2} 2
linkedList.deleteAtIndex(1); \texttt{linkedList.deleteAtIndex(1);} linkedList.deleteAtIndex(1); // 现在链表是 1 → 3 \texttt{1} \rightarrow \texttt{3} 1→3
linkedList.get(1); \texttt{linkedList.get(1);} linkedList.get(1); // 返回 3 \texttt{3} 3
使用单向链表时,维护伪头结点 pseudoHead \textit{pseudoHead} pseudoHead 和链表的长度 size \textit{size} size。伪头结点的作用是方便各项操作,并不是链表的实际头结点,链表的实际头结点为伪头结点的后一个结点。链表的长度为链表的结点数,不包括伪头结点,初始时 size = 0 \textit{size} = 0 size=0。
对于 get \textit{get} get 方法,只有当参数 index \textit{index} index 满足 0 ≤ index < size 0 \le \textit{index} < \textit{size} 0≤index<size 时才是有效的下标,当下标无效时返回 − 1 -1 −1。当下标有效时,从伪头结点开始向后移动 index + 1 \textit{index} + 1 index+1 次,定位到下标为 index \textit{index} index 的结点,将该结点的值返回。
对于 addAtHead \textit{addAtHead} addAtHead 方法,需要创建一个值为参数 val \textit{val} val 的结点 addNode \textit{addNode} addNode,将该结点添加到伪头结点和实际头结点之间。依次执行 addNode . next : = pseudoHead . next \textit{addNode}.\textit{next} := \textit{pseudoHead}.\textit{next} addNode.next:=pseudoHead.next 和 pseudoHead . next : = addNode \textit{pseudoHead}.\textit{next} := \textit{addNode} pseudoHead.next:=addNode,即完成结点的添加操作,添加结点之后需要将 size \textit{size} size 的值加 1 1 1。
对于 addAtTail \textit{addAtTail} addAtTail 方法,需要创建一个值为参数 val \textit{val} val 的结点 addNode \textit{addNode} addNode,将该结点添加到尾结点之后。遍历链表,定位到尾结点 node \textit{node} node,令 node . next : = addNode \textit{node}.\textit{next} := \textit{addNode} node.next:=addNode,即完成结点的添加操作,添加结点之后需要将 size \textit{size} size 的值加 1 1 1。
对于 addAtIndex \textit{addAtIndex} addAtIndex 方法,只有当参数 index \textit{index} index 满足 0 ≤ index ≤ size 0 \le \textit{index} \le \textit{size} 0≤index≤size 时才是有效的下标,当下标无效时不添加结点。当下标有效时,需要创建一个值为参数 val \textit{val} val 的结点 addNode \textit{addNode} addNode,从伪头结点开始向后移动 index \textit{index} index 次,定位到下标为 index − 1 \textit{index} - 1 index−1 的结点 node \textit{node} node,即待添加结点的前一个结点。依次执行 addNode . next : = node . next \textit{addNode}.\textit{next} := \textit{node}.\textit{next} addNode.next:=node.next 和 node . next : = addNode \textit{node}.\textit{next} := \textit{addNode} node.next:=addNode,即完成结点的添加操作,添加结点之后需要将 size \textit{size} size 的值加 1 1 1。
对于 deleteAtIndex \textit{deleteAtIndex} deleteAtIndex 方法,只有当参数 index \textit{index} index 满足 0 ≤ index < size 0 \le \textit{index} < \textit{size} 0≤index<size 时才是有效的下标,当下标无效时不删除结点。当下标有效时,从伪头结点开始向后移动 index \textit{index} index 次,定位到下标为 index − 1 \textit{index} - 1 index−1 的结点 node \textit{node} node,即待删除结点的前一个结点。需要删除的结点是 node \textit{node} node 的后一个结点,将 node \textit{node} node 的 next \textit{next} next 指针更改为指向待删除结点的后一个结点即可完成删除,执行 node . next : = node . next . next \textit{node}.\textit{next} := \textit{node}.\textit{next}.\textit{next} node.next:=node.next.next,即完成结点的删除操作,删除结点之后需要将 size \textit{size} size 的值减 1 1 1。
class MyLinkedList {
Node pseudoHead;
int size;
public MyLinkedList() {
pseudoHead = new Node(0);
size = 0;
}
public int get(int index) {
if (index >= size) {
return -1;
}
Node node = pseudoHead;
for (int i = 0; i <= index; i++) {
node = node.next;
}
return node.val;
}
public void addAtHead(int val) {
Node addNode = new Node(val);
addNode.next = pseudoHead.next;
pseudoHead.next = addNode;
size++;
}
public void addAtTail(int val) {
Node addNode = new Node(val);
Node node = pseudoHead;
while (node.next != null) {
node = node.next;
}
node.next = addNode;
size++;
}
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
Node addNode = new Node(val);
Node node = pseudoHead;
for (int i = 0; i < index; i++) {
node = node.next;
}
addNode.next = node.next;
node.next = addNode;
size++;
}
public void deleteAtIndex(int index) {
if (index >= size) {
return;
}
Node node = pseudoHead;
for (int i = 0; i < index; i++) {
node = node.next;
}
node.next = node.next.next;
size--;
}
}
class Node {
int val;
Node next;
public Node(int val) {
this.val = val;
}
}
时间复杂度:构造方法的时间复杂度是 O ( 1 ) O(1) O(1),方法 get \textit{get} get 的时间复杂度是 O ( n ) O(n) O(n),方法 addAtHead \textit{addAtHead} addAtHead 的时间复杂度是 O ( 1 ) O(1) O(1),方法 addAtTail \textit{addAtTail} addAtTail 的时间复杂度是 O ( n ) O(n) O(n),方法 addAtIndex \textit{addAtIndex} addAtIndex 的时间复杂度是 O ( n ) O(n) O(n),方法 deleteAtIndex \textit{deleteAtIndex} deleteAtIndex 的时间复杂度是 O ( n ) O(n) O(n),其中 n n n 是链表的长度。
构造方法初始化伪头结点和链表长度,时间复杂度是 O ( 1 ) O(1) O(1)。
方法 addAtHead \textit{addAtHead} addAtHead 直接在伪头结点后面添加结点,不需要遍历链表,时间复杂度是 O ( 1 ) O(1) O(1)。
方法 addAtTail \textit{addAtTail} addAtTail 需要遍历链表定位到尾结点然后添加结点,时间复杂度是 O ( n ) O(n) O(n)。
其余方法都需要在下标有效的情况下遍历链表,定位到指定下标的结点,然后进行返回结点值、添加结点或删除结点的操作,遍历链表的最坏情况下的时间复杂度都是 O ( n ) O(n) O(n),遍历链表之后的操作的时间复杂度是 O ( 1 ) O(1) O(1),因此执行一个方法的时间复杂度是 O ( n ) O(n) O(n)。
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要创建长度为 n n n 的链表。
使用双向链表时,维护伪头结点 pseudoHead \textit{pseudoHead} pseudoHead、伪尾结点 pseudoTail \textit{pseudoTail} pseudoTail 和链表的长度 size \textit{size} size。伪头结点和伪尾结点的作用是方便各项操作,并不是链表的实际头结点和实际尾结点,链表的实际头结点为伪头结点的后一个结点,链表的实际尾结点为伪尾结点的前一个结点(只有当链表不为空时才存在实际头结点和实际尾结点)。初始时 pseudoHead \textit{pseudoHead} pseudoHead 和 pseudoTail \textit{pseudoTail} pseudoTail 相邻。链表的长度为链表的结点数,不包括伪头结点和伪尾结点,初始时 size = 0 \textit{size} = 0 size=0。
和单向链表相比,双向链表多了伪尾结点,遍历时可以向前移动或向后移动,因此每种功能的时间复杂度更优。对于 addAtHead \textit{addAtHead} addAtHead 方法和 addAtTail \textit{addAtTail} addAtTail 方法,时间复杂度都是 O ( 1 ) O(1) O(1)。对于 get \textit{get} get 方法、 addAtIndex \textit{addAtIndex} addAtIndex 方法和 deleteAtIndex \textit{deleteAtIndex} deleteAtIndex 方法,需要定位到下标为 index \textit{index} index 的结点或者相邻下标的结点,可以根据 index \textit{index} index 和 size \textit{size} size 计算从头结点向后遍历和从尾结点向前遍历分别需要经过的结点数,选择需要经过的结点数较少的一种遍历方案。
在添加和删除结点时,需要对当前结点和前后相邻结点的 prev \textit{prev} prev 和 next \textit{next} next 指针进行更新。
对于 get \textit{get} get 方法,只有当参数 index \textit{index} index 满足 0 ≤ index < size 0 \le \textit{index} < \textit{size} 0≤index<size 时才是有效的下标,当下标无效时返回 − 1 -1 −1。当下标有效时,从伪头结点开始向后移动 index + 1 \textit{index} + 1 index+1 次或者从伪尾结点开始向前移动 size − index \textit{size} - \textit{index} size−index 次,定位到下标为 index \textit{index} index 的结点,将该结点的值返回。
对于 addAtHead \textit{addAtHead} addAtHead 方法,需要创建一个值为参数 val \textit{val} val 的结点 addNode \textit{addNode} addNode,将该结点添加到伪头结点和实际头结点之间。添加结点之后需要将 size \textit{size} size 的值加 1 1 1。
对于 addAtTail \textit{addAtTail} addAtTail 方法,需要创建一个值为参数 val \textit{val} val 的结点 addNode \textit{addNode} addNode,将该结点添加到伪尾结点和实际尾结点之间。添加结点之后需要将 size \textit{size} size 的值加 1 1 1。
对于 addAtIndex \textit{addAtIndex} addAtIndex 方法,只有当参数 index \textit{index} index 满足 0 ≤ index ≤ size 0 \le \textit{index} \le \textit{size} 0≤index≤size 时才是有效的下标,当下标无效时不添加结点。当下标有效时,需要创建一个值为参数 val \textit{val} val 的结点 addNode \textit{addNode} addNode,然后遍历到待添加结点的前后相邻结点,完成添加操作:
从伪头结点开始向后移动,需要移动 index \textit{index} index 次,定位到下标为 index − 1 \textit{index} - 1 index−1 的结点 node \textit{node} node,即待添加结点的前一个结点,然后将 addNode \textit{addNode} addNode 添加到 node \textit{node} node 的后面;
从伪尾结点开始向前移动,需要移动 size − index \textit{size} - \textit{index} size−index 次,定位到下标为 index \textit{index} index 的结点 node \textit{node} node,即待添加结点的后一个结点(注意在添加结点之后,该结点的下标会后移一位变成 index + 1 \textit{index} + 1 index+1,因此在添加结点之前,下标为 index \textit{index} index 的结点即为待添加结点的后一个结点),然后将 addNode \textit{addNode} addNode 添加到 node \textit{node} node 的前面。
对于 deleteAtIndex \textit{deleteAtIndex} deleteAtIndex 方法,只有当参数 index \textit{index} index 满足 0 ≤ index < size 0 \le \textit{index} < \textit{size} 0≤index<size 时才是有效的下标,当下标无效时不删除结点。当下标有效时,从伪头结点开始向后移动 index \textit{index} index 次或者从伪尾结点开始向前移动 size − index − 1 \textit{size} - \textit{index} - 1 size−index−1 次,定位到下标为 index − 1 \textit{index} - 1 index−1 或者 index + 1 \textit{index} + 1 index+1 的结点,然后将下标为 index \textit{index} index 的结点删除。删除结点之后需要将 size \textit{size} size 的值减 1 1 1。
class MyLinkedList {
Node pseudoHead;
Node pseudoTail;
int size;
public MyLinkedList() {
pseudoHead = new Node(0);
pseudoTail = new Node(0);
pseudoHead.next = pseudoTail;
pseudoTail.prev = pseudoHead;
size = 0;
}
public int get(int index) {
if (index >= size) {
return -1;
}
boolean fromHead = index + 1 <= size - index;
Node node = fromHead ? pseudoHead : pseudoTail;
if (fromHead) {
for (int i = 0; i <= index; i++) {
node = node.next;
}
} else {
for (int i = size - 1; i >= index; i--) {
node = node.prev;
}
}
return node.val;
}
public void addAtHead(int val) {
Node addNode = new Node(val);
Node nextNode = pseudoHead.next;
addNode.next = nextNode;
nextNode.prev = addNode;
pseudoHead.next = addNode;
addNode.prev = pseudoHead;
size++;
}
public void addAtTail(int val) {
Node addNode = new Node(val);
Node prevNode = pseudoTail.prev;
addNode.prev = prevNode;
prevNode.next = addNode;
pseudoTail.prev = addNode;
addNode.next = pseudoTail;
size++;
}
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
Node addNode = new Node(val);
boolean fromHead = index <= size - index;
Node node = fromHead ? pseudoHead : pseudoTail;
if (fromHead) {
for (int i = 0; i < index; i++) {
node = node.next;
}
Node nextNode = node.next;
addNode.next = nextNode;
nextNode.prev = addNode;
node.next = addNode;
addNode.prev = node;
} else {
for (int i = size - 1; i >= index; i--) {
node = node.prev;
}
Node prevNode = node.prev;
addNode.prev = prevNode;
prevNode.next = addNode;
node.prev = addNode;
addNode.next = node;
}
size++;
}
public void deleteAtIndex(int index) {
if (index >= size) {
return;
}
boolean fromHead = index <= size - index - 1;
Node node = fromHead ? pseudoHead : pseudoTail;
if (fromHead) {
for (int i = 0; i < index; i++) {
node = node.next;
}
Node nextNode = node.next.next;
node.next = nextNode;
nextNode.prev = node;
} else {
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
Node prevNode = node.prev.prev;
node.prev = prevNode;
prevNode.next = node;
}
size--;
}
}
class Node {
int val;
Node prev;
Node next;
public Node(int val) {
this.val = val;
}
}
时间复杂度:构造方法的时间复杂度是 O ( 1 ) O(1) O(1),方法 get \textit{get} get 的时间复杂度是 O ( n ) O(n) O(n),方法 addAtHead \textit{addAtHead} addAtHead 的时间复杂度是 O ( 1 ) O(1) O(1),方法 addAtTail \textit{addAtTail} addAtTail 的时间复杂度是 O ( 1 ) O(1) O(1),方法 addAtIndex \textit{addAtIndex} addAtIndex 的时间复杂度是 O ( n ) O(n) O(n),方法 deleteAtIndex \textit{deleteAtIndex} deleteAtIndex 的时间复杂度是 O ( n ) O(n) O(n),其中 n n n 是链表的长度。
构造方法初始化伪头结点、伪尾结点和链表长度,时间复杂度是 O ( 1 ) O(1) O(1)。
方法 addAtHead \textit{addAtHead} addAtHead 直接在伪头结点后面添加结点,不需要遍历链表,时间复杂度是 O ( 1 ) O(1) O(1)。
方法 addAtTail \textit{addAtTail} addAtTail 直接在伪尾结点前面添加结点,不需要遍历链表,时间复杂度是 O ( 1 ) O(1) O(1)。
其余方法都需要在下标有效的情况下遍历链表,定位到指定下标的结点,然后进行返回结点值、添加结点或删除结点的操作,遍历链表的最坏情况下的时间复杂度都是 O ( n ) O(n) O(n),遍历链表之后的操作的时间复杂度是 O ( 1 ) O(1) O(1),因此执行一个方法的时间复杂度是 O ( n ) O(n) O(n)。
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要创建长度为 n n n 的链表。