工欲善其事,必先利其器
上一篇介绍了leetcode上的接雨水问题的解决方案,这个算法的重点在于对问题的抽象能力,但是往往大多数算法,只具备对问题的抽象能力是不够的,还需要数据结构的基础。
接下来的几天,忘忧将总结一些数据结构的基础知识,包括链表,树,栈,图等,万丈高楼平地起,在开始学习算法之前,一定要把数据结构的基础掌握扎实。
今天忘忧将要介绍的是数据结构中最基础的一种——链表。
链表和数组的对比
- 数组在物理内存中是连续的单位,使用数组前需要预先申请出一块连续的空间;链表在物理内存中是非连续的,由前一个节点保存后一个节点的位置,在使用时可动态分配空间。
- 数组定位第n个位置的元素,可通过数组的下标直接定位到,时间复杂度为O(1);链表定位第n个节点,需要从头节点开始,依次向后查找,时间复杂度为O(n)。
- 数组在第n个位置上移除或者新增元素需要平移n位置后续位置的元素,链表在第n个位置上删除或者新增元素,只需修改n的前置节点即可。
链表的遍历
链表的遍历,只需找到头节点,然后依次向后遍历即可,直到最后一个节点(没有后置节点的节点)结束。
代码实现:
public static void traversalList(ListNode head){
while(head != null){
System.out.println(head.val);
head = head.next;
}
}
删除第n个节点
删除链表的第n(从0开始计数)个节点,需要先遍历,找到n的前置节点,即第n-1个节点,然后将n-1的next修改为n的后置节点即可;
代码实现:
/**
* 删除第n个位置上的节点
* @param head 头节点
* @param n 位置
*/
public static ListNode deleteNode(ListNode head, int n){
//链表为null,不操作
if(head == null){
return null;
}
//如果移除第0个位置的节点,即移除头节点,只需将头节点后移一位
if(n == 0){
return head.next;
}
//n节点的前置节点,即第n-1个节点
ListNode preNode = head;
for (int i = 0; i < n - 1; i++) {
if(preNode == null){
break;
}
preNode = preNode.next;
}
//如果删除的位置超过链表长度,不删除,直接返回原链表
if(preNode == null || preNode.next == null){
return head;
}
//将n的前置节点的next改为n的后置节点
preNode.next = preNode.next.next;
return head;
}
在第n个位置新增节点
在链表的第n个(从0开始计数)位置新增节点,第一步,找到第n-1个节点;第二步,将新增节点的next设置为n-1的next(即当前的n节点);第三步,将n-1的next设置为新增节点。
代码实现:
/**
* 在第n个位置新增节点
* @param head 头节点
* @param newNode 要新增的节点
* @param n 位置
*/
public static ListNode insertNode(ListNode head, ListNode newNode, int n){
if(n == 0){
newNode.next = head;
return newNode;
}
//n节点的前置节点,即第n-1个节点
ListNode preNode = head;
for (int i = 0; i < n - 1; i++) {
if(preNode == null){
break;
}
preNode = preNode.next;
}
//n的前置为null,新增失败
if(preNode == null){
return head;
}
newNode.next = preNode.next;
preNode.next = newNode;
return head;
}
常见面试题
反转链表
手写反转链表是在初中级和应届生工程师面试当中,出场率非常高的一个题目,在此忘忧给大家整理一下思路和代码实现,希望大家在实际面试当中遇到之后能够得心应手。
代码实现:
/**
* 反转链表
* @param head 当前链表头节点
* @return 反转后的链表
*/
public static ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
ListNode result = null;
do{
ListNode tempNode = head;
head = head.next;
tempNode.next = result;
result = tempNode;
}while(head != null);
return result;
}
判断链表有环
思路:声明两个指针,一个指针每次往前移动一步,另一个指针每次往前移动两部。如果有一瞬间,跑的快的指针与跑的慢的指针重合时,则判定为链表有环,如果指针最终指向了null,则判定为无环。类似体育测试的时候,如果无限的围着操场跑下去,跑的快的同学总是将跑得慢的同学“套圈”。
如上图,跑的快的指针终于在多跑一圈之后,在“5”号节点的位置与跑的满的指针在此重逢了!
/**
* 判断链表有环
* @return 是否有环
*/
private static boolean cycle(ListNode head){
if(head == null){
return false;
}
ListNode aNode = head;
ListNode bNode = head;
while(bNode.next != null && bNode.next.next != null){
if(bNode.next.next == aNode.next){
return true;
}
bNode = bNode.next.next;
aNode = aNode.next;
}
return false;
}
其他景点面试题
- 链表环的大小
- 链表环的入口
关于链表的扩展
- Java中ArrayList和LinkedList的区别(参考数组和链表的区别)
结束语:正如某人所言,凡事到最后必将皆大欢喜。