链表理论基础
单链表:每一个节点由数据域和指针域组成。指针只能指向下一个节点,第一个节点是头节点,最后一个节点的指针指向null。
双链表:每一个节点有一个数据域和两个指针域,分别指向下一个和上一个节点。因此既可以向前查询也可以向后查询。
循环链表:就是链表首尾相连,可以用来解决约瑟夫环问题。
存储方式:链表是通过指针域的指针链接在内存中各个节点。所以链表中的节点是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
定义:
C/C++的定义链表节点方式
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
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;
}
}
(注:如果使用默认构造函数的话,在初始化的时候不能直接给变量赋值!)
删除节点:
删除普通节点,只要将要删除的节点的前一个节点的next指针 指向其后一个节点就可以了。
删除头结点:head=head->next 或 引入虚拟头节点。
(在C++里最好是再手动释放这块内存。其他语言例如Java、Python,有自己的内存回收机制,就不用自己手动释放了。)
添加节点:
添加节点和删除节点都是O(1)操作,也不会影响到其他节点。
(但要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。)
链表与数组对比:
Leetcode203 移除链表元素
题目链接:Leetcode203 移除链表元素
第一想法:应该就是链表中遍历的活,找到了就进行删除操作,没找到就继续。
讲解后想法:这里的一个大前提是如果删除节点,必须保证它前面有节点。所以就要考虑头结点删除的问题。如果在原链表删除,势必要写两种判断条件。还有一种方法是引入“虚拟头节点dummy node”,这样只需要统一的一次判断条件即可。
遇到的困难:听了讲解感觉还可以👍!
代码:虚拟头节点版
/**
* 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) {
if(head == null){
return head;
}
ListNode dummy = new ListNode(-1, head);
ListNode pre = dummy;
ListNode cur = head;
while(cur != null){
if(cur.val == val){
pre.next = cur.next;
}else{
pre = cur;
}
cur = cur.next;
}
return dummy.next;
}
}
Leetcode707 设计链表
题目链接:Leetcode707 设计链表
第一想法:此题是在链表中实现五个功能:取值、添加头节点、添加尾节点、添加任意节点、删除节点。乍一看好像是中规中矩的,要是写起来,又要有很多细节注意了,应该还是离不开“虚拟头节点”的使用。
讲解后想法:注意几个点。首先,题目中的n是从0开始,0代表头节点,要先搞清楚界定范围,0<n<size-1才是合理的。其次,操作的时候不要直接动头节点,而是定义临时的cur变量进行操作。第三,遍历的时候控制n从大到小,直至小于零就可以控制循环结束,就不用思考太多的结束条件,很巧妙。第四,插入节点操作顺序很重要。要先将新节点指向下一个节点,再由前面的节点指向新节点,这个思路对链表中每一个节点操作都适用。第五,无论是添加还是删除,都要知道要操作节点的前一个节点的位置,才能进行操作。例如,要删除第n个节点,那么第n个节点是cur->next,前一个节点是cur。最后,添加记得size++,删除记得size--。
遇到的困难:思路很清晰,代码实现的时候又花了点时间去理解。
代码:单链表版
//定义单链表
class ListNode{
int val;
ListNode next;
ListNode(){
}
ListNode(int val){
this.val = val;
}
}
class MyLinkedList {
int size; //链表的元素个数
ListNode dummy; //虚拟头节点
//初始化链表
public MyLinkedList() {
size = 0;
dummy = new ListNode(0);
}
//获取链表中第 index 个节点的值。头节点为0,如果索引无效,则返回-1。
public int get(int index) {
if(index < 0 || index >= size){
return -1;
}
ListNode cur = dummy;
for(int i = 0; i < index + 1; i++){
cur = cur.next;
}
return cur.val;
}
//在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
public void addAtHead(int val) {
addAtIndex(0, val);
}
//将值为 val 的节点追加到链表的最后一个元素。
public void addAtTail(int val) {
addAtIndex(size, val);
}
//在链表中的第 index 个节点之前添加值为 val 的节点。
//如果 index 等于链表的长度,则该节点将附加到链表的末尾。
//如果 index 大于链表长度,则不会插入节点。
//如果index小于0,则在头部插入节点,相当于index=0。
public void addAtIndex(int index, int val) {
if(index > size){
return ;
}
if(index < 0){
index = 0;
}
size++;
ListNode cur = dummy;
for(int i = 0; i < index; i++){
cur = cur.next;
}
ListNode toAdd = new ListNode(val);
toAdd.next = cur.next;
cur.next = toAdd;
}
public void deleteAtIndex(int index) {
if(index < 0 || index >= size){
return ;
}
size--;
if(index == 0){
dummy = dummy.next;
return ;
}
ListNode cur = dummy;
for(int i = 0; i < index; i++){
cur = cur.next;
}
cur.next = cur.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);
*/
Leetcode206 反转链表
题目链接:Leetcode206 反转链表
第一想法:虽然题目看着很简洁,但是没想出什么好方法。
讲解后想法:通过两个指针进行倒序操作,听完了讲解就回忆起来了,尤其是临时指针temp这块。(丢失的数据结构知识重新回到脑子里)。
遇到的困难:双指针的解法还可以,写递归需要照着双指针写才行。
代码:双指针版
/**
* 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) {
ListNode pre = null;
ListNode cur = head;
ListNode temp = null;
while(cur != null){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
递归版:
/**
* 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);
}
private ListNode reverse(ListNode pre, ListNode cur) {
if(cur == null){
return pre;
}
ListNode temp = null;
temp = cur.next;
cur.next = pre;
return reverse(cur, temp);
}
}