链表基础组成:一个元素+一个指针+一个构造函数
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
调用:
在每次迭代中,newNode
变量被声明并指向一个新的节点,然后cur->next
指向newNode
,接着cur
移动到newNode
。
只需一个指针来跟踪链表的当前位置,并在每次迭代中更新它。
ListNode *newNode = new ListNode(val); // 创建一个新节点并初始化其值为val
cur->next = newNode; // 将新节点接入链表
cur = cur->next; // 移动cur指针到新节点
非连续性
链表相较于数组的优越之处即在于其非连续性,不用像数组一样需要专门的大片空间来存储。正相反,通过指针之间的链接,链表可以充分利用各种小型存储空间
力扣题目部分
基础功能函数构建与虚拟头结点的初步使用
203.移除链表元素——学习虚拟头结点
关键点:
使用虚拟头结点。在链表中,操作当前节点必须要找到前一个节点才能操作。在这种情况下,链表原本的头结点无法按照常规方式处理。我们对此的处理方式是转换此类头结点为普通节点——通过人为添加虚拟头结点。此时有了新的头结点,原先的头结点自然在形式上变为了普通节点。
定义方式:这里将虚拟头结点和原先的虚拟头结点连接在一起是非常重要的操作,
否则之后虚拟头结点一直指向的会是空指针
ListNode *DummyHead = new ListNode(0);
DummyHead->next = head;
/*这里将虚拟头结点和原先的虚拟头结点连接在一起是非常重要的操作,
否则之后虚拟头结点一直指向的会是空指针
*/
之后再用常规操作即可完成移除
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *DummyHead = new ListNode(0);
DummyHead->next = head;
ListNode *current = DummyHead;
//此时是给当前指针赋初值。如果是再加个next的话,那我们就无法针对性修改对应节点了,因为删除之后链接的永远是下下个节点、
while (current->next != nullptr) {
if (current->next->val == val) {
ListNode* temp = current->next;
current->next = temp->next;
delete temp;
}
else {
current = current->next;
}
}
head = DummyHead->next;
delete DummyHead;
return head;
};
707.设计链表
个人非常推荐去完成这道题目,做完以后就能对链表的基础操作都有较好的了解了
关键点:理解每项函数的功能与要求,正确定义current指针的位置
(1)get函数——检索对应位置的值
int get(int index) {
if (index > (size - 1) || index < 0) {
return -1;
}
LinkList* cur = dummyHead->next;
//这里很关键,因为是从n=0开始的,当然,不同条件要因势利导
while (index--) {
cur = cur->next;
}
return cur->val;
}
这里我们把current指针定义在原先的头结点位置,index--的执行次数是index次,从下标为0的头结点开始,执行index次恰好就是下标为index的节点,此时返回对应值即可。
(2)addAtIndex函数——对应位置添加节点
想要在对应位置添加节点,我们就得掌握这个对应位置节点的前一个节点,由此才能完成添加。
对于不好判断边界的情况,我们可以取极端例子,即只有一个节点——此时我们发现虚拟头结点的使用必不可少,并且此时的current指针的初始化位置也应该为dummyHead而非dummyHead->next,
void addAtIndex(int index, int val) {
if (index > size) { return; }
if (index < 0) { index = 0; }
LinkList* newOne = new LinkList(val);
LinkList* cur = dummyHead;//考虑在头部添加的情况
while (index--) {
cur = cur->next;
}
newOne->next = cur->next;
cur->next = newOne;
size++;
}
功能为删除对应索引节点的函数在大体上与添加一致,故不过多赘述。
双指针思想的加入
206.反转链表
关键点:同时定义两个指针,verse指针指向null,current指针指向头结点。每次用temp保存current指针指向的下一个节点,再让current指针所在节点指向verse指针所在,verse再移动至cur指针所指向的节点即可。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
cur = head;
preForVerse = nullptr;
ListNode* temp;
while (cur) {
temp = cur->next;
cur->next = preForVerse;
preForVerse = cur;
cur = temp;
}
return preForVerse;
}
先前疑惑:此时需要使用虚拟头结点吗?——>在我看来是不用的,因为我们对原来的头结点只需让其指向null这样一个简单情况,没必要额外再消耗内存。
24. 两两交换链表中的节点
两两交换的关键点在于想明白怎么交换的,看这张图就懂了:参考:代码随想录 (programmercarl.com)
19.删除链表的倒数第N个节点
用双指针能够更高效的实现要求——>快慢指针
如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。