设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性: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 个节点。
public class MyLinkedList {
/**
* 链表头:一个虚拟的链表头,用来表示链表的头部,用于链接其他节点。所有其它节点的增删操作都不会影响该链表头的地位。该链表头在链表中的角标设计为-1.
* 实际链表数据依然从角标0开始。
*/
private Node head;
/**
* 链表数据大小:表示当前链表数据容量大小个数。默认初始化为0
*/
private int size;
/**
* 初始化单链表,创建虚拟头节点
*/
public MyLinkedList() {
this.head = new Node(null, 0);
}
/**
* 通过角标来获取指定角标节点的值。
* @param index 指定的角标值
* @return 返回指定角标元素节点数据的值(Node element)
*/
public int get(int index) {
if(index < 0) {
return -1;
}
Node node = node(index);
return node.element;
}
/**
* 根据传入的数据值,创建一个节点(Node),并将该节点放在链表的头部(即虚拟头节点的下一个节点,也即是实际上的第一个节点)
* @param val 传入的数据值
*/
public void addAtHead(int val) {
/* 根据传入的数据值,创建一个无链接的新节点(Node next值为null)*/
Node newNode = new Node(null, val);
/* 如果链表中已存在节点,通过虚拟头节点head来获取原第一个节点 */
if(size > 0) {
Node originalFirstNode = head.next;
/* 将无链接的新节点的下一个节点指向原第一个节点 */
newNode = new Node(originalFirstNode, val);
}
/* 虚拟头节点的下一个节点指向新节点 */
head.next = newNode;
/* 链表数据+1 */
size++;
}
/**
* 根据传入的数据值,创建一个节点(Node),并将该节点放在链表的尾部
* @param val
*/
public void addAtTail(int val) {
/* 根据传入的数据值,创建一个无链接的新节点(Node next值为null)*/
Node newNode = new Node(null, val);
/* 获取尾节点 */
Node tailNode = node(size-1);
/* 设置尾节点的下一个节点为当前新节点*/
tailNode.next = newNode;
/* 链表数据+1 */
size++;
}
/**
* 在链表指定的角标索引下的节点的前面插入一个指定数据值为 val的新节点
* @param index 指定的角标值
* @param val 传入的数据值
*/
public void addAtIndex(int index, int val) {
/* 如果index大于链表大小,不做操作 */
if(index > size||index < 0) {
return;
/* 如果角标索引为0,或者链表中数据大小为0,则插入到头部 */
}else if(index == 0||size == 0) {
addAtHead(val);
return;
/* 如果index == size 则插入到尾部(因为最后一个节点(尾部节点)的角标为 size-1) */
}else if(index == size) {
addAtTail(val);
return;
}else {
/* 否则说明当前插入点介于两个节点之间
* 1 获取到要插入节点的前一个节点
* 2 获取要插入在其前面的当前节点
* 3 创建新节点 将该新节点的下一个节点指向 2 中节点
* 4 将1中节点的下一个节点指向我们要插入的新节点
* 5 size+1
*/
Node insertBeforePreNode = node(index-1);
Node insertBeforeNode = insertBeforePreNode.next;
Node newNode = new Node(insertBeforeNode, val);
insertBeforePreNode.next =newNode;
size++;
}
}
/**
* 删除指定角标索引下的节点
* @param index
*/
public void deleteAtIndex(int index) {
/* 如果当前角标大于或等与size,什么都不做
* 1 链表为空时,size = 0 = index,不需要删除
* 2 最后一个节点(尾部节点)的角标为 size-1 ,不需要删除
* */
if(index >= size||index < 0) {
return;
}
//以下建立在size>0前提条件下
/* 获取当前角标索引的前一个节点。如果index为0,则获取到虚拟头节点 */
Node preNode = node(index-1);
/* 获取要删除的当前节点 */
Node curNode = preNode.next;
/* 获取下一个节点 */
Node nextNode = curNode.next;
/* 如果要删除的当前节点的下一个节点为空,则不需要将该节点的前一个节点指向该节点的下一个节点 */
if(nextNode == null) {
preNode.next = null;
}else {
/* 否则,将该节点的前一个节点指向该节点的下一个节点 */
preNode.next = nextNode;
}
/* size -1 */
size--;
}
/**
* 返回指定角标下的节点。从0---index(index<size),即index范围是从0---size-1
* @param index
* @return
*/
private Node node(int index) {
if(index == -1){
return head;
}
Node next = head.next;//0号角标节点
if(index < size) {
for (int i = 1; i <= index; i++) {
next = next.next;
}
}else {
//如果index小于0且不等于-1 返回 val为-1 的节点
return new Node(null, -1);
}
return next;
}
public int size() {
return size;
}
public class Node {
public Node next;
public int element;
public Node(MyLinkedList.Node node, int element) {
super();
this.next = node;
this.element = element;
}
}
}