一、链表的定义
链表由一个个节点连接而成的,即一个个对象,该对象包含两个属性:节点值、下一个节点的引用。同时也可以看到value属性是用来保存实际信息的,而next属性是用来建立节点间的联系。对于双向链表,需要两个这样的属性来指向前面节点和后面节点。
public class ListNode {
int value; //节点值
ListNode next = null; //节点的下一个指针
ListNode(int value) {
this.value = value;
}
}
二、单向链表基本的操作
1、遍历
*链表是一个带头大哥,拉着一群小弟。*所以先找到带头大哥,然后顺藤摸瓜往后揪出所有小弟。小弟的数量也是有限的,最后一个小弟的next属性为null时,表示不再指向其它小弟了,即链表结束了。
public static void printList(ListNode pNode) {
if (pNode == null) {
System.out.println("链表不存在");
}
while (pNode != null) { //当前节点不为空
System.out.print(pNode.value + " ");
pNode = pNode.next;
}
}
可以类比数组的遍历操作去理解,arr.length == 0
与pNode == null
都是表示数组或链表中没有元素这种情况。数组中遍历元素的方式:
int i = 0;
while (i != arr.length) {
System.out.println(arr[i]);
i ++;
}
链表中遍历元素的方式:
while (pNode != null) {
System.out.println(pNode.value);
pNode = pNode.next;
}
对比发现,数组和链表都具有流动性,即从头到尾的索引变化。数组受长度限制,而链表受当前节点是否存在限制。
2、删除链表中的某个节点
在数组中删除的思想是覆盖,先找到被删除元素的位置,然后将其后的元素向前移动一位。而链表中就不是覆盖了,思路是,先找到要删除节点的前一个节点preNode
,然后由当前节点curNode
找到它的下个节点afterNode = curNode.next
,再将preNode.next = afterNode
,最后释放curNode
节点的空间。这是由链表这种数据结构决定的,只用改变链表的指向,再释放被删节点的空间。
public static void deleteNode(ListNode pHead, ListNode pToBeDeleted) {
ListNode pre = pHead; // 前一个节点
ListNode curNode = pHead; //当前节点
//找到要删的节点
while (curNode != pToBeDeleted) {
pre = curNode; //记录被删节点的前节点
curNode = curNode.next;
}
ListNode aimNode = curNode.next; //目标节点
pre.next = aimNode; //前节点指向目标节点
}
在链表的操作中,要明白curNode = curNode.next;
跟数组中i ++
的目的是一样的。这种方法需要先找到被删节点的前一个节点,这需要0(n)
的时间复杂度。有没有0(1)
的解法内,我们想想链表中有效的东西是value
值不同,next
只起到串联的作用。
我们可以这样做,既然入参传入被删除的节点,可拿到它后一个节点 pNext=pToBeDeleted.next
将有一个节点value值赋给要删除节点pToBeDeleted.value =pNext.value
,这样就不用遍历删除pNext
即可。
public static void deleteNode1(ListNode pHead, ListNode pToBeDeleted) {
if (pHead == null || pToBeDeleted == null)
return;
//如果链表只有一个节点且是删除节点
if (pHead == pToBeDeleted && pHead.next == null) {
pHead =null;
}
ListNode pNext = pToBeDeleted.next;
ListNode pNextNext = pNext.next;
pToBeDeleted.value = pNext.value;
pToBeDeleted.next = pNextNext; //要删节点指向下下个节点
}
这有点像数组中覆盖删除元素。
3、实际问题
链表中最多的是指针操作,搞清每个指针的定义,维持他们之间的不变关系。
典型题目分析
1、链表反转
三个指针解法
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode next = null;
ListNode cur = head;
while (cur != null) {
// 当前结点的后一结点需要保存下来
next = cur.next;
// 反转
cur.next = pre;
// 指针前移
pre = cur;
cur = next;
}
// 最后pre到达的位置刚好是最末尾那个结点,即反转后的头结点
return pre;
}
解题思想
设置三个指针,分别指向当前一个节点、当前节点、后一个节点。当前节点的next本来是指向它的后一个节点的。现在让它指向它前一个节点,就实现了链表的反转。但是当前节点与它后一个节点连接就断开了,因此在反转链表之前需要保存当前节点的下个节点。以便链表反向的过程中向前推进(当前指针和前一指针前移)。