系列文章目录
【链表系列01】反转链表
【链表系列02】两两交换链表中的节点
【链表系列03】实现双向链表
前言
虽然双向链表在面试中出现的频率不高,但是学好它能加深我们对链表这个知识点的理解,使得我们对于链表问题能够游刃有余。前面两篇文章,我们已经一起学习了单向链表,有了这个基础,相信你可以轻松搞定双向链表,最终实现“链表自由”。😎
1. 题目概述
1.1. 题目内容
【题目来源】力扣(Leetcode) - 第707题,双向链表问题。
【难度等级】中等。
【题目内容】双链表的实现——节点应该具有三个属性:val 、prev、next,val 是当前节点的值,prev 是指向其上一个节点的指针,next 是指向其下一个节点的指针。假设链表中的所有节点都是 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 个节点。
1.2. 题目解析
本题虽然是中等难度,但是如果你实现过“单链表”,就会觉得比较简单了,无非就是增加了一个前向指针(prev)。
2. 解体思路
【本题的考点】
数据结构:双向链表
本题主要考察数据结构——双链表。
我们首先来看一下双链表的结构:
其中,val 为节点的值;prev 为前向指针,指向前一个节点;next 为后继指针,指向后一个节点。
因此,双向链表的节点类有三个属性:val、prev、next,比单向链表中的节点类多了一个 prev 属性,节点类的实现请查看第 3 章。
3. 代码实现
3.1. python
双向链表节点类:
class Node:
def __init__(self, val: int):
self.val = val
self.prev = None
self.next = None
双链表的 python 实现:
class MyLinkedList:
def __init__(self):
"""
构造函数,创建三个私有变量,并初始化
"""
self._head, self._tail = Node(0), Node(0) # 虚拟节点
self._head.next, self._tail.prev = self._tail, self._head # 首指向尾,尾指向首
self._count = 0 # 记录节点数
def _get_node(self, index: int) -> Node:
"""
私有函数:根据索引获取节点
:param index: 索引
:return: 节点
"""
if index >= self._count // 2:
# index 位于链表后半部分,从尾部往前找
node = self._tail
for _ in range(self._count - index):
node = node.prev
else:
# index 位于链表前半部分,从头部往后找
node = self._head
for _ in range(index + 1):
node = node.next
return node
def get(self, index: int) -> int:
"""
根据索引获取节点,通过调用私有函数 _get_node() 来实现
:param index: 索引
:return: 节点的值
"""
if 0 <= index < self._count:
node = self._get_node(index)
return node.val
else:
return -1
def _add_new_node(self, prev_node: Node, next_node: Node, new_node: Node) -> None:
"""
抽象的公共函数:添加新节点
:param prev_node: 新节点的前一个节点
:param next_node: 新节点的后一个节点
:param new_node: 新节点
:return: None
"""
self._count += 1
prev_node.next, next_node.prev, new_node.prev, new_node.next = new_node, new_node, prev_node, next_node
def add_at_head(self, val: int) -> None:
"""
在链表头部增加一个节点
:param val: 待添加的节点的值
:return: None
"""
self._add_new_node(self._head, self._head.next, Node(val))
def add_at_tail(self, val: int) -> None:
"""
在链表尾部增加一个节点
:param val: 待添加的节点的值
:return: None
"""
self._add_new_node(self._tail.prev, self._tail, Node(val))
def add_at_index(self, index: int, val: int) -> None:
"""
在指定索引处添加一个节点
:param index: 索引
:param val: 待添加的节点的值
:return: None
"""
if index <= 0:
self.add_at_head(val)
return
elif index == self._count:
self.add_at_tail(val)
return
elif index > self._count: # 注意,这里不能包括等号
return
node = self._get_node(index)
self._add_new_node(node.prev, node, Node(val))
def delete_at_index(self, index: int) -> None:
"""
删除指定索引处的节点
:param index: 索引
:return: None
"""
if 0 <= index < self._count:
node = self._get_node(index)
self._count -= 1
node.prev.next, node.next.prev = node.next, node.prev
LeetCode运行结果
3.2. java
双向链表节点类:
class Node {
private int val;
private Node prev;
private Node next;
public Node(int val) {this.val = val;}
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
public Node getPrev() {
return prev;
}
public void setPrev(Node prev) {
this.prev = prev;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
实现双向链表:
class MyLinkedList {
private Node dummyHead;
private Node dummyTail;
private int count;
public MyLinkedList() {
this.dummyHead = new Node(0);
this.dummyTail = new Node(0);
dummyHead.setNext(dummyTail);
dummyTail.setPrev(dummyHead);
this.count = 0;
}
/**
* 根据索引获取节点
* @param index 索引
* @return 节点
*/
private Node getNode(int index) {
Node node;
int curIndex;
// 从尾部往前查找
if (index >= this.count / 2) {
node = this.dummyTail.getPrev();
curIndex = this.count - 1; // 当前索引在最后一个节点
while(curIndex > index) {
node = node.getPrev(); // 获取前一个节点
curIndex -= 1; // 更新索引
}
}
// 从头部往后查找
else {
node = this.dummyHead.getNext();
curIndex = 0; // 当前索引在第一个节点
while (curIndex < index) {
node = node.getNext(); // 获取后一个节点
curIndex += 1; // 更新索引
}
}
return node;
}
/**
* 抽象出公共方法:添加新节点
* @param prev 新节点的前一个节点
* @param next 新节点的后一个节点
* @param newNode 新节点
*/
private void addNewNode(Node prev, Node next, Node newNode) {
this.count += 1;
prev.setNext(newNode);
next.setPrev(newNode);
newNode.setPrev(prev);
newNode.setNext(next);
}
/**
* 根据索引获取节点的值
* @param index 索引
* @return 节点的值
*/
public int get(int index) {
if (index >= 0 && index < this.count) {
return this.getNode(index).getVal();
} else {
// 超出索引范围,返回 -1
return -1;
}
}
/**
* 在头部添加一个节点
* @param val 待添加节点的值
*/
public void addAtHead(int val) {
Node newNode = new Node(val);
addNewNode(this.dummyHead, this.dummyHead.getNext(), newNode);
}
/**
* 在尾部添加一个节点
* @param val 待添加的节点的值
*/
public void addAtTail(int val) {
Node newNode = new Node(val);
addNewNode(this.dummyTail.getPrev(), this.dummyTail, newNode);
}
/**
* 在指定索引处添加一个节点
* @param index 索引
* @param val 待添加节点的值
*/
public void addAtIndex(int index, int val) {
if (index == 0) {
addAtHead(val);
return;
} else if (index < 0) {
index = 0;
} else if (index == this.count) {
addAtTail(val);
return;
} else if (index > this.count) {
return;
}
Node newNode = new Node(val);
addNewNode(this.getNode(index).getPrev(), this.getNode(index), newNode);
}
/**
* 删除指定索引的值
* @param index 索引
*/
public void deleteAtIndex(int index) {
if (index >= 0 && index < this.count) {
Node node = this.getNode(index); // 找到待删除的节点
Node tempPrev = node.getPrev();
Node tempNext = node.getNext();
this.count -= 1;
tempPrev.setNext(tempNext);
tempNext.setPrev(tempPrev);
}
}
}
LeetCode运行结果
3.3. scala
双链表节点类: