203.移除链表元素
题目链接/文章讲解/视频讲解::代码随想录
重点是添加头结点,避免分情况讨论首元素是不是需要删除的情况
注意不能直接用dummy遍历按,dummy记住头结点位置,cur遍历节点,需要记住几个位置就有几个指针
cur->next是要删的节点就做删除操作,不是就往下遍历
ListNode* curNode = dummyNode;
while(curNode->next)
{
if(curNode->next->val == val)
{
ListNode* tmpNode = curNode->next;
curNode->next = curNode->next->next;
delete tmpNode;
}
else
{
curNode = curNode->next;
}
}
注意返回值返回dummy->next,需要释放资源
head = dummyNode->next;
delete dummyNode;
return head
707.设计链表
题目链接/文章讲解/视频讲解:代码随想录
注意:链表长度length要跟着变化!!!
1. 要先定义node的struct,链表记住头结点head和长度
struct MyLinkedListNode{
int val;
MyLinkedListNode* next;
MyLinkedListNode(int val)
{
this->val = val;
next = nullptr;
}
};
MyLinkedListNode* head; // 头结点
int length; // 长度
2. 链表构造函数,是构造了一个虚拟头结点dummyNode,长度还是0
MyLinkedList() {
length = 0;
head = new MyLinkedListNode(0); // 这里定义的是虚拟头结点,所以长度还是0
}
3. 遍历列表时tmp_node指向头结点下一节点,就是需要操作的位置,返回时不用tmp_node->next
int get(int index) {
if(index < 0 || index + 1> length) return -1;
MyLinkedListNode* tmp_node = head->next;
while( index--)
{
tmp_node = tmp_node->next;
}
return tmp_node->val;
}
4. 添加节点
void addAtHead(int val) {
MyLinkedListNode* tmp_node = new MyLinkedListNode(val);
if(!tmp_node) return;
tmp_node->next = head->next;
head->next = tmp_node;
length++;
}
5. 尾部添加元素:注意cur_node是要操作节点的前一个位置cur_node = head 而不是head->
跳出while循环时cur->next = nullptr,就是要操作的节点cur->next = tmp_node,而不是cur->next->next
void addAtTail(int val) {
MyLinkedListNode* tmp_node = new MyLinkedListNode(val);
if(!tmp_node) return;
MyLinkedListNode* cur_node = head;
while(cur_node&&cur_node->next)
{
cur_node = cur_node->next;
}
cur_node->next = tmp_node;
length++;
}
6. 插入节点:
while中是--i和i--的问题
后置递减 (index--):先使用 index 的当前值,然后再将其减少1。在减少值之前使用当前值时用index--。
前置递减 (--index):先将 index 减少1,然后再使用新的值。需要立即使用减少后的值时时用--index。
void addAtIndex(int index, int val) {
if(index < 0) index = 0;
if(index > length) return;
if(index == length )
{
addAtTail(val);
return;
}
if(index == 0 )
{
addAtHead(val);
return;
}
MyLinkedListNode* tmp_node = new MyLinkedListNode(val);
if(!tmp_node) return;
MyLinkedListNode* cur_node = head; // cur_node指向要操作节点的上一个
while(index--) // 注意指针移动次数比插入位置小1
{
cur_node = cur_node->next;
}
tmp_node->next = cur_node->next;
cur_node->next = tmp_node;
length++;
}
tmp_node->next = cur_node->next;
cur_node->next = tmp_node;
length++;
}
7. 删除元素
void deleteAtIndex(int index) {
if(index < 0) return;
if(index >= length) return;
MyLinkedListNode* cur_node = head; // cur_node指向要操作节点的上一个
while(index-- && cur_node)
{
cur_node = cur_node->next;
}
MyLinkedListNode* tmp_node = cur_node->next;
cur_node->next = cur_node->next->next;
delete tmp_node;
tmp_node = nullptr;
length--;
}
206.反转链表
题目链接/文章讲解/视频讲解:代码随想录
双指针法:时间复杂度O(N) 空间复杂度O(1)
pre指向操作值前一个 cur指向操作值 tmp记录下一个,cur执行反转操作
!!!注意pre从nullptr开始,cur从head开始
循环体内:判断条件cur是否为空
tmp先指向cur->next
cur->next指向pre实现翻转
per指向cur向前进一步
cur指向tmp向前进一步
跳出循环时:pre指向了最后节点 ,cur是空,返回pre
错误写法:pre从head开始,cur从head->next开始
cur->next = pre;会产生闭环链表
想用pre->next = pos; cur->next = pre; 也会有误因为pre和pos之间产生了多余的连接,当指针向后移动的时候多余的解环操作导致pre会丢失前面的数据
ListNode* reverseList(ListNode* head) {
if(!head) return nullptr;
ListNode* pre =head;
ListNode* cur =head->next;
ListNode* pos =nullptr;
while(cur)
{
pos = cur->next;
cur->next = pre;
pre = cur;
cur = pos;
}
return pre;
}
正确写法:pre从nullptr开始,cur从head开始
pre指向空,当pre移动到列表中的时候,pre指向的点指向之前且没有多余的连接
ListNode* reverseList(ListNode* head) {
if(!head) return nullptr;
ListNode* pre =nullptr;
ListNode* cur =head;
ListNode* pos =nullptr;
while(cur)
{
pos = cur->next;
cur->next = pre;
pre = cur;
cur = pos;
}
return pre;
}
递归法:时间复杂度O(N) 空间复杂度O(N)
!!!重点: 类中要保留一个前指针
递归函数:对传进来的链表,每次处理头部元素,把头部的指针掉头,再把后面的链表丢进递归函数
返回值:转换完的头结点 参数表:需要处理的链表部分
终止条件:空指针,说明最后一个元素已经处理完了, 返回空指针前一个节点,所以在类中需要保存前指针
进行头部反转处理
剩余部分进行递归
最后返回当前头元素就是最后一个节点
ListNode* preNode;
ListNode* MyReverse(ListNode* head)
{
if(!head) return preNode;
ListNode* pos = head->next;
pos->next = head;
return MyReverse( head->next);
}
ListNode* reverseList(ListNode* head) {
preNode = new ListNode;
preNode->next = head;
preNode = MyReverse(head);
return preNode;
}
简化:
ListNode* MyReverse(ListNode* cur,ListNode* pre)
{
if(!cur) return pre;
ListNode* pos = cur->next;
pos->next = cur;
return MyReverse( cur->next, cur);
}
ListNode* reverseList(ListNode* head) {
return MyReverse(head, nullptr);
}
24. 两两交换链表中的节点
题目链接/文章讲解/视频讲解: 代码随想录
ListNode* swapPairs(ListNode* head) {
if(!head) return nullptr;
// 1. 加dummynode
ListNode* dummynode = new ListNode(0);
dummynode->next = head;
// 2. 设置cur指针,cur指向交换的相两邻元素之前的元素
ListNode* cur = dummynode;
// 3. 循环条件:两个交换的元素cur->next和cur->next->next不为空
while(cur && cur->next && cur->next->next)
{
// 4. first指向交换的第一个元素, second指向交换的第二个元素
ListNode* first = cur->next;
ListNode* second = cur->next->next;
// 5. second的下一个位置,否则后面移动指针的时候找不到位置
ListNode* next_pos = second->next;
// 6. 交换顺序:看图解
cur->next = second;
second->next = first;
first->next = next_pos;
// 7. 移动: cur向后移动两步
cur = cur->next->next;
}
// 8. 释放dummynode
ListNode* res = dummynode->next;
delete dummynode
return res;
}
19.删除链表的倒数第N个节点
题目链接/文章讲解/视频讲解:代码随想录
思路:
因为链表单向,要删倒数第n节点,且只遍历一次,考虑双指针
!!!采用快指针比慢指针多走n + 1步,结束时快指针指向空元素,慢指针指向删除元素前一个方便删除元素
为什么不采用快指针比慢指针多走n 步,结束时快指针指向最后一个元素,边界处理时对最后一个元素的处理可能会出错
!!!增删改查时必须给链表加dummynode, fast 和slow从dummynode开始操作,以免边界处理时对第一个元素的处理出错
!!!返回 `dummynode->next` 而不是 `head` 是因为:
1. **处理头节点**:当需要删除的节点是头节点时,直接操作 `head` 会导致链表头指针丢失。哑节点(`dummynode`)提供了一个始终指向链表起点的额外层,使得即使头节点被删除,也能通过 `dummynode->next` 访问剩余链表。
2. **统一处理**:无论删除哪个节点,使用哑节点都可以通过 `dummynode->next` 统一访问修改后的链表,无需区分删除的是头节点还是其他节点
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head) return nullptr;
if(n == 1&& !head->next) return nullptr;
ListNode* dummynode = new ListNode;
dummynode->next = head;
ListNode* fast = dummynode;
ListNode* slow = dummynode;
n++;
while(n--)
{
fast = fast->next;
}
while( fast)
{
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return dummynode->next;
}
面试题 02.07. 链表相交
题目链接/文章讲解:代码随想录
思路:
先分别遍历链表A 和 链表B 记录链表长度
快指针指向长的链表,先走完长度差
慢指针和快指针同步走,边走边比较是否是同一个指针
重点:
比较的是指针本身而不是指针指向的值,如下例:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(!headA || !headB) return nullptr;
int lengthA = 0;
int lengthB =0;
ListNode* tmp_A = headA;
while(tmp_A)
{
tmp_A= tmp_A->next;
lengthA++;
}
ListNode* tmp_B = headB;
while(tmp_B)
{
tmp_B= tmp_B->next;
lengthB++;
}
ListNode* tmp_fast = lengthA >= lengthB? headA:headB;
ListNode* tmp_slow = lengthA < lengthB? headA:headB;
int distance = abs(lengthA - lengthB);
while(distance--)
tmp_fast = tmp_fast->next;
while(tmp_fast)
{
if(tmp_fast == tmp_slow) return tmp_fast;
tmp_fast=tmp_fast->next;
tmp_slow = tmp_slow->next;
}
return nullptr;
}
142.环形链表II
题目链接/文章讲解/视频讲解:代码随想录
思路:
快慢指针遍历元素,快指针每次走两步,慢指针每次走一步
两指针一定会相遇,快慢指针相对步长是1,一旦入环,可以等效于慢指针不动,快指针一步步往前走直到相遇
慢指针x+y ,快指针x+y+n(y+z)
2*(x+y) = x+y+n(y+z) n>=1, 取n=1,得x = z
快指针和慢指针相遇说明有环,这时再从头出发一个指针out_ptr, 从快慢指针相遇处出发一个指针in_ptr,两指针同步走,直到在入环口出相遇
ListNode *detectCycle(ListNode *head) {
// 成环至少有两个节点
if(!head) return nullptr;
if(!head->next) return nullptr;
// 定义快慢指针
ListNode * slow = head;
ListNode * quick = head;
while(quick && quick->next )
{
// 快指针一次走两步,慢指针一次走一步
quick = quick->next->next;
slow = slow->next;
if(quick == slow) // 快慢指针相遇:有环
{
// 从头出发一个指针out_ptr
ListNode * out_ptr = head;
// 从相遇处出发一个指针in_ptr
ListNode * in_ptr = quick;
while(out_ptr != in_ptr)
{
out_ptr=out_ptr->next;
in_ptr=in_ptr->next;
}
// 返回相遇处指针
return out_ptr;
}
}
return nullptr;
}