链表理论基础
概念
由许多节点组成,每个节点分为数据域和指针域两个部分,数据域负责存放数据,指针域保存下一个节点的地址。
没有指针指向的是头结点,没有指向节点的是尾结点。
可以设置数据域为空的虚拟子节点以简化指针操作(无需额外考虑头结点)
链表便于增删数据,不便于查找数据。
链表在内存中不是连续存储的。
类型
单链表,双链表(指针域存放前后两个节点的位置)、循环链表(尾结点指向头结点,可以解决约瑟夫问题)
JAVA定义链表
public class ListNode {
// 结点的值
int val;
// 下一个结点
ListNode next;
// 节点的构造函数(无参)
public ListNode() {
}
// 节点的构造函数(有一个参数)
public ListNode(int val) {
this.val = val;
}
// 节点的构造函数(有两个参数)
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
203.移除链表元素
题意:删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
解题思路
让前一个节点的指针不再指向被删除的节点,而是指向被删除节点的下一个节点。
JAVA有内存回收机制,不需要手动回收被删除节点的内存,如果用C/C++等语言则需要手动回收内存。
可以使用虚拟头结点简化操作。
源码
/**
* 虚拟头结点
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next =next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode empty = new ListNode(-1, head);
ListNode pre = empty;
ListNode i = head;
while(i!=null){
if(i.val == val){
pre.next=i.next;
}
else{
pre=i;
}
i=i.next;
}
return empty.next;
}
}
/**
* 不使用虚拟头结点
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 先判断头结点
while(head!=null && head.val==val){
head=head.next;
}
if(head==null){
return head;
}
ListNode pre=head;
ListNode i = head.next;
while(i!=null){
if(i.val==val){
pre.next=i.next;
}
else{
pre=i;
}
i=i.next;
}
return head;
}
}
707.设计链表
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
示例:
输入
[“MyLinkedList”, “addAtHead”, “addAtTail”, “addAtIndex”, “get”, “deleteAtIndex”, “get”]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3
解题思路
可以采用虚拟头结点,可简化操作。这道题包含了链表常见的基础操作,非常全面。
源码
代码如下(示例):
public class ListNode{
int val;
ListNode next;
ListNode(){}
ListNode(int val) {
this.val=val;
}
}
class MyLinkedList {
int size;
//虚拟头结点
ListNode head;
// 初始化链表
public MyLinkedList() {
size=0;
head = new ListNode(0);
}
// 获取链表中某个元素的值
public int get(int index) {
if(index < 0 || index >= size){
return -1;
}
ListNode target = head;
//第一个节点是虚拟头结点,所以要查到index+1
for(int i = 0; i<index+1; i++){
target=target.next;
}
return target.val;
}
// 在某个节点插入的两种特殊情况
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
// 在某个节点插入元素
public void addAtIndex(int index, int val) {
if(index>size){
return;
}
size++;
ListNode pre = head;
for(int i = 0; i<index; i++){
pre = pre.next;
}
ListNode Add = new ListNode(val);
Add.next = pre.next;
pre.next = Add;
}
public void deleteAtIndex(int index) {
if(index>=size){
return;
}
size--;
if(index==0){
head = head.next;
return;
}
ListNode pre = head;
for(int i = 0; i<index; i++){
pre = pre.next;
}
pre.next = pre.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
206.反转链表
题意:反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解题思路
不需要重新定义一个新的链表浪费空间,可以采用双指针法(快慢指针)在原本的链表上完成反转操作。记得多定义一个指针保存反转前指针的指向.
也可以使用递归法,就是稍微抽象一点,逻辑差不多
源码
/**
* 双指针法
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next==null){
return head;
}
ListNode pre = head;
ListNode cul = head.next;
ListNode temp;
temp=pre;
pre.next=null;
while(cul.next!=null){
pre=cul;
cul=cul.next;
pre.next=temp;
temp=pre;
}
pre = cul;
pre.next=temp;
return pre;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
/**
* 递归
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
public ListNode reverse(ListNode pre, ListNode cur){
if(cur==null){
return pre;
}
ListNode temp = null;
temp = cur.next;
cur.next = pre;
return reverse(cur, temp);
}
}
时间复杂度: O(n)
空间复杂度: O(n)
总结
开始学链表,之前都是直接调函数,从头实现还是第一次,感觉对链表的理解加深了不少。加油!