2.两数相加
思路:
- 初始化: 创建一个新的链表用于存储结果,使用一个变量
carry
来跟踪进位。- 遍历两个链表: 同时遍历两个链表,将对应位置的数值相加,加上前一位置的进位。
- 处理进位: 计算每次相加后的和以及新的进位值。
- 处理多余的进位: 如果最后还有进位,需要在新链表的末尾添加一个节点。
- 返回结果链表的头部
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
//数据域
int val;
//指针域
ListNode* next;
//结点
ListNode(int val)
{
this->val = val;
next = nullptr;
}
};
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummyHead = new ListNode(0); // 创建一个虚结点,简化边界情况的处理
ListNode* current = dummyHead; // 用来构建结果链表
int carry = 0; // 进位初始化为0
while (l1 != nullptr || l2 != nullptr) { // 当任一链表还有节点未处理时循环
int x = (l1 != nullptr) ? l1->val : 0; // 获取l1当前节点的值,如果已经为空,则用0替代
int y = (l2 != nullptr) ? l2->val : 0; // 获取l2当前节点的值,如果已经为空,则用0替代
int sum = carry + x + y; // 将当前位的两个数字与进位相加
carry = sum / 10; // 更新进位
current->next = new ListNode(sum % 10); // 创建新节点存放当前位的结果(sum的个位数)
current = current->next; // 移动指针到新创建的节点
if (l1 != nullptr) l1 = l1->next; // 移动l1
if (l2 != nullptr) l2 = l2->next; // 移动l2
}
if (carry > 0) { // 如果最后还有进位,添加一个新节点
current->next = new ListNode(carry);
}
return dummyHead->next; // 返回哑结点的下一个节点,即结果链表的头节点
}
void printList(ListNode* head) {
while (head != nullptr) {
cout << head->val << " ";
head = head->next;
}
cout << endl;
}
int main() {
// 从键盘输入两个链表
ListNode* l1 = nullptr;
ListNode* l2 = nullptr;
int val;
cout << "输入第一个链表的节点值(输入-1结束):" << endl;
while (true) {
cin >> val;
if (val == -1) break;
if (l1 == nullptr) {
l1 = new ListNode(val);
} else {
ListNode* temp = l1;
while (temp->next != nullptr) {
temp = temp->next;
}
temp->next = new ListNode(val);
}
}
cout << "输入第二个链表的节点值(输入-1结束):" << endl;
while (true) {
cin >> val;
if (val == -1) break;
if (l2 == nullptr) {
l2 = new ListNode(val);
} else {
ListNode* temp = l2;
while (temp->next != nullptr) {
temp = temp->next;
}
temp->next = new ListNode(val);
}
}
// 调用 addTwoNumbers 函数计算结果链表
ListNode* result = addTwoNumbers(l1, l2);
// 输出结果链表的值
cout << "结果链表的值为: ";
printList(result);
return 0;
}
删除中间节点
思路:
复制下一节点的数据:将
node->next
(即下一个节点)的值复制到当前的节点中。这样,当前节点就拥有了下一个节点的数据。更新指针:将当前节点的
next
指针指向下下个节点(node->next->next
),这样当前节点就绕过了下一个节点。删除下一节点:由于当前节点已经取代了下一个节点的数据,我们现在可以安全地删除下一个节点。
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
void deleteNode(ListNode* node) {
ListNode* next_node = node->next;
node->val = next_node->val; // 将下一个节点的值复制到当前节点。
node->next = next_node->next; // 将当前节点的指针指向下下个节点。
delete next_node; // 删除下一个节点。
}
合并两个有序链表
思路:
创建虚拟头节点:一个虚拟(哑)节点可以作为结果链表的起始点,这样可以避免单独处理头节点为空的特殊情况。
比较节点值:比较两个链表的当前节点的值,将较小的节点连接到结果链表的末尾,并移动该链表的指针到下一节点。
遍历链表:重复这一过程,直到达到其中一个链表的末尾。
连接剩余节点:如果一个链表已经完全合并,而另一个链表还有剩余的节点,直接将这些节点连接到结果链表的末尾。
返回结果:返回虚拟头节点的下一个节点,即合并后的链表的头节点。
class Solution {
public:
ListNode* trainningPlan(ListNode* l1, ListNode* l2) {
ListNode*head=new ListNode();//创建虚头
ListNode*tail=head;
while(l1&&l2)
{
if (l1->val < l2->val)
{
tail->next = l1;
l1 = l1->next;
}
else
{
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
if (l1) {// 如果l1还有剩余节点,直接连接到合并链表的末尾
tail->next = l1;
}
if (l2) { // 如果l2还有剩余节点,直接连接到合并链表的末尾
tail->next = l2;
}
return head->next;
}
};
链表中倒数第k个节点
LCR 140. 训练计划 IIhttps://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/
思路:
定义快慢双指针:
fast
指针:开始时指向链表的头节点head
。slow
指针:同样开始时指向链表的头节点head
。初始化
将fast
指针:fast
指针向前移动cnt
步。这样做的目的是创建一个从fast
到slow
指针之间固定为cnt
个节点的窗口。这一步是关键,因为它将允许我们定位到链表的倒数第cnt
个节点。同时移动
当fast
和slow
指针:fast
指针没有到达链表尾部(即fast
不为nullptr
)时,同时将fast
和slow
指针向链表的下一个节点移动。这样,当fast
指针到达链表尾部时,slow
指针刚好位于倒数第cnt
个节点。返回结果:
函数返回slow
指针,此时slow
指向的就是链表的倒数第cnt
个节点。
class Solution {
public:
ListNode* trainingPlan(ListNode* head, int cnt) {
ListNode*fast=head;
ListNode*slow=head;
for(int i=1;i<=cnt;i++)
fast=fast->next;
while(fast)
{
fast=fast->next;
slow=slow->next;
}
return slow;
}
};
链表的中间节点
思路:
边界条件处理:
如果链表为空(head
是nullptr
),直接返回nullptr
。初始化指针:
fast
指针和slow
指针都初始化为指向链表的头节点head
。遍历链表:
使用while
循环来遍历链表,循环条件是fast
和fast->next
均不为nullptr
。这个条件确保了快指针可以安全地前进两步而不会越界。在每次循环中,fast
指针向前移动两步(fast = fast->next->next
),而slow
指针向前移动一步(slow = slow->next
)。寻找中间节点:
当fast
指针到达链表末尾(或超出末尾)时,循环结束,此时slow
指针指向的就是链表的中间节点。如果链表长度是偶数,则返回的是中间两个节点中的第二个。返回结果:
函数返回slow
指针,即链表的中间节点。
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode*fast=head;
ListNode*slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
};
回文链表
思路:
- 快慢指针:
fast
指针每次移动两步,而slow
指针每次移动一步。这样,当fast
指针到达链表尾部时,slow
指针正好在链表中间。- 反转链表:在寻找中点的同时,逐步将链表的前半部分反转,这样可以在后续步骤中直接进行前后部分的比较。
- 奇数长度链表处理:如果链表长度是奇数,
fast
指针不会指向NULL
,此时slow
指针需要向前移动一步,以跳过中间的节点。- 比较前后部分:最后比较反转后的前半部分和原链表的后半部分是否相等。
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (!head || !head->next) return true;
ListNode*fast=head,*slow=head;
ListNode*temp=nullptr,*pre=nullptr;
while(fast&&fast->next)
{
fast=fast->next->next;
//反转前半部分
temp=slow->next;
slow->next=pre;
pre=slow;
slow=temp;
}
// 如果链表长度为奇数,跳过中间节点
if(fast)slow=slow->next;
// 比较前半部分和后半部分
while (pre && slow) {
if (pre->val != slow->val) return false;
pre = pre->next;
slow = slow->next;
}
return true;
}
};
反转链表
206. 反转链表https://leetcode.cn/problems/reverse-linked-list/
思路:
初始化三个指针:
prev
(前一个节点),初始值为nullptr
。curr
(当前节点),初始值为链表的头节点head
。next
(下一个节点),在迭代中动态更新。遍历链表:
- 在每次迭代中,首先保存
curr->next
到next
,因为修改curr->next
之后会丢失对原链表中下一个节点的引用。- 将
curr->next
指向prev
,完成对当前节点的反转。- 移动
prev
和curr
指针:prev
移动到curr
,curr
移动到next
。完成反转:
- 当
curr
变为nullptr
,即达到链表末尾时,结束迭代。此时prev
指针指向新链表的头节点。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head) return head;
ListNode* prev = nullptr;
ListNode* curr = head;
ListNode* next = nullptr;
while (curr != nullptr) {
next = curr->next; // 保存当前节点的下一个节点
curr->next = prev; // 反转当前节点的指向
prev = curr; // 移动prev和curr指针
curr = next;
}
return prev; // prev现在指向新的头节点
}
};
移除链表元素
203. 移除链表元素https://leetcode.cn/problems/remove-linked-list-elements/
思路:
创建虚拟头节点:
首先创建一个虚头节点HEAD
,并将其指向原链表的头节点head
前面。这样做的目的是为了处理删除链表头节点的情况,同时简化删除节点时的操作。初始化指针:
初始化两个指针fast
和slow
,分别指向原链表的头节点head
和虚拟头节点HEAD
。遍历链表:
使用fast
指针遍历整个链表,直到fast
指向nullptr
,即遍历完整个链表。在遍历过程中,判断当前节点的值是否等于目标值val
。删除节点:
如果当前节点的值等于目标值,则将slow
指针的next
指向当前节点的下一个节点,相当于删除了当前节点。否则,将slow
指针移动到下一个节点。返回结果:
最后返回虚拟头节点HEAD
的下一个节点,即删除节点后的链表头节点。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if (!head) return head;
ListNode* HEAD = new ListNode(0, head);
ListNode* fast = head, * slow = HEAD;
while (fast)
{
if (fast->val == val)
slow->next = fast->next;
else
slow = slow->next;
fast = fast->next;
}
return HEAD->next;
}
};
相交链表
160. 相交链表https://leetcode.cn/problems/intersection-of-two-linked-lists/
思路:
初始化指针:
初始化两个指针la
和lb
,分别指向两个链表的头节点headA
和headB
。遍历链表:
使用一个循环来遍历链表,循环条件是la != lb
,即当两个指针相遇时结束循环。在循环中,判断la
是否为空,如果不为空,则将la
指针移动到下一个节点;如果为空,则将la
指针重新指向另一个链表的头节点headB
。同样地,判断lb
是否为空,如果不为空,则将lb
指针移动到下一个节点;如果为空,则将lb
指针重新指向另一个链表的头节点headA
。返回结果:
循环结束后,返回la
指针指向的节点,即为两个链表的交点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode*la=headA,*lb=headB;
while(la!=lb)
{
if(la)
la=la->next;
else
la=headB;
if(lb)
lb=lb->next;
else
lb=headA;
}
return la;
}
};
环形链表
141. 环形链表https://leetcode.cn/problems/linked-list-cycle/
思路:
初始化快慢指针:
初始化两个指针fast
和slow
,初始都指向链表的头节点head
。遍历链表:
使用一个循环来遍历链表,循环条件是fast
和fast->next
都不为空,即快指针能够走到链表尾部。在循环中,快指针fast
每次走两步,慢指针slow
每次走一步,这样它们之间的距离会逐渐拉近。检测环:
在每次循环中,检查快指针fast
是否等于慢指针slow
,如果相等,说明链表中存在环,返回true
。如果快指针fast
走到了链表尾部(即fast
或fast->next
为空),则说明链表中不存在环,返回false
。
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode*fast=head,*slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
return true;
}
return false;
}
};
删除链表中的重复元素
83. 删除排序链表中的重复元素https://leetcode.cn/problems/remove-duplicates-from-sorted-list/
思路:
初始化指针:
初始化两个指针slow
和fast
,分别指向链表的头节点head
和头节点的下一个节点。遍历链表:
使用一个循环来遍历链表,循环条件是fast
不为空。
- 在循环中,比较
fast
指针指向的节点的值与slow
指针指向的节点的值是否相等。如果相等,则说明出现了重复元素,将slow
的next
指针指向fast
的下一个节点,从而删除了重复元素。如果不相等,则将slow
指针移动到下一个节点。- 不论是否删除了重复元素,都需要将
fast
指针移动到下一个节点。返回结果:
循环结束后,返回原链表的头节点head
。因为链表已经被修改,所以头节点可能已经改变了,但是头节点不会重复。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(!head) return head;
ListNode*slow=head,*fast=head->next;
while(fast)
{
if(fast->val==slow->val)
slow->next=fast->next;
else
slow=slow->next;
fast=fast->next;
}
return head;
}
};
合并两个有序链表
21. 合并两个有序链表https://leetcode.cn/problems/merge-two-sorted-lists/
思路:
初始化指针:
- 创建一个新的节点
Head
作为合并后链表的虚拟头节点。- 创建指针
Tail
并指向虚拟头节点Head
。遍历链表:
- 使用
while
循环遍历两个链表l1
和l2
,只要它们都还有节点。- 在循环中,比较
l1
和l2
当前节点的值。拼接节点:
- 如果
l1
的值小于等于l2
的值,将Tail
的next
指针指向l1
,并移动l1
指针到下一个节点。- 否则,将
Tail
的next
指针指向l2
,并移动l2
指针到下一个节点。移动指针:
无论拼接了l1
还是l2
的节点,都将Tail
移动到新添加的节点上。拼接剩余节点:
循环结束后,可能l1
或l2
中还有剩余的节点,直接将剩余节点接在合并后链表的末尾。返回结果:
返回虚拟头节点Head
的下一个节点,即合并后链表的头节点。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode*Head=new ListNode();//创建虚头
ListNode*Tail=Head;
while(l1&&l2)
{
if(l1->val<=l2->val)
{
Tail->next=l1;
l1=l1->next;
}
else
{
Tail->next=l2;
l2=l2->next;
}
Tail=Tail->next;//无论拼谁都移动
}
//拼接剩余的
if(l1) Tail->next=l1;
if(l2) Tail->next=l2;
return Head->next;
}
};
剑指offer06.从头到尾打印
LCR 123. 图书整理 Ihttps://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/
思路:
与反转链表思路相同只不过是把逆序得到的链表存在动态数组中返回。
class Solution {
public:
vector<int> reverseBookList(ListNode* head) {
if (!head) // 检查头指针是否为空
return {};
ListNode*cur=head,*pre=nullptr,*next=head->next;
vector<int>res;// 用于存储逆序后的链表节点值
// 将链表逆序
while (cur) {
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
// 将逆序后的链表节点值添加到vector中
cur = pre;
while (cur) {
res.push_back(cur->val); // 将节点值添加到vector中
cur = cur->next; // 移动到下一个节点
}
return res; // 返回包含逆序后的链表节点值的vector
}
};
二进制链表转整数
1290. 二进制链表转整数https://leetcode.cn/problems/convert-binary-number-in-a-linked-list-to-integer/
思路:
链表逆序
遍历链表,将链表逆序,即将每个节点的指针指向它的前一个节点。在遍历过程中,记录链表的前一个节点、当前节点和下一个节点。二进制转十进制
遍历逆序后的链表,将每个节点表示的二进制位转换为十进制值,并累加到一个变量中。在循环中,利用指数函数pow
计算每个节点的值对应的十进制权重,然后乘以当前节点的值,累加到总和中。
class Solution {
public:
int getDecimalValue(ListNode* head) {
ListNode*cur=head,*pre=nullptr,*next=head->next;
while(cur)
{
cur->next=pre;
pre=cur;
cur=next;
if(next) next=next->next;
}
cur=pre;
int sum=0;
int i=0;
while(cur)
{
sum+=cur->val*pow(2,i);
i++;
cur=cur->next;
}
return sum;
}
};
如果你对探索机器学习的无限可能性、掌握Python编程的技巧、以及玩转各种框架的技能充满了好奇心,那么恭喜你,你来对地方了!赶紧扫描下方二维码,加入我们的微信公众号吧!这里有最新的技术趋势、独家教程、精彩案例等着你,让我们一起探索未知的领域,开启编程之旅吧!🚀🌟