题1.从尾到头打印链表
题目:输入一个链表的头结点,从尾到头反过来打印出每个节点的值。链表的定义如下:
struct ListNode
{int mValue;ListNode *mNext;ListNode *mPrev;
};
1.1 方法一:栈
思路:要反过来打印,首先需要遍历,那么从先遍历的节点后打印,典型的后进先出,符合栈的结构。
#include <iostream>
#include <vector>
#include <stack>
using namespace std;struct ListNode
{int mValue;ListNode *mNext;
};void reverselist(ListNode *pHead)
{if (pHead == nullptr){return;}stack<ListNode *> reversenodes; // 创建一个栈容器ListNode *node = pHead;while (node != nullptr) // 遍历链表{reversenodes.push(node); // 将其放入栈容器中node = node->mNext;}while (!reversenodes.empty()) // 循环从栈取出节点{node = reversenodes.top();cout << node->mValue << endl;reversenodes.pop();}
}// 添加到链表的尾部
struct ListNode *AddToTail(ListNode *pHead, int value)
{ListNode *pNew = new ListNode(); // 创建了一个子节点pNew->mValue = value;pNew->mNext = nullptr; // 因为是向尾部节点插入值,所以其下一个节点一定是空if (pHead == nullptr) // 如果头结点为空,则当前节点为头结点{pHead = pNew;}else // 如果头结点不为空{ListNode *pnode = pHead; // 则先取出链表的头结点while (pnode->mNext != nullptr) // 如果当前节点有子节点,则说明不是最后一个节点{pnode = pnode->mNext;}pnode->mNext = pNew; // 找到了最后的节点,将此时要添加的节点放到链表的尾部}return pHead;
}int main()
{ListNode *Head = nullptr;Head = AddToTail(Head, 1);Head = AddToTail(Head, 2);Head = AddToTail(Head, 3);reverselist(Head);return 0;
}
1.2 方法二:递归
思想:递归从本质上将就是一个栈的结构,后进先出,所以我们可以当输出一个节点时候,先输入其下一个节点,然后再输出当前节点。
#include <iostream>
#include <vector>
#include <stack>
using namespace std;struct ListNode
{int mValue;ListNode *mNext;
};void recursionlist(ListNode *pHead)
{if(pHead == nullptr)//注意递归的返回条件{return;}if(pHead->mNext != nullptr)//如果当前节点还有子节点,则递归先打印子节点的值{recursionlist(pHead->mNext);}cout<<pHead->mValue<<endl;
}// 添加到链表的尾部
struct ListNode *AddToTail(ListNode *pHead, int value)
{ListNode *pNew = new ListNode(); // 创建了一个子节点pNew->mValue = value;pNew->mNext = nullptr; // 因为是向尾部节点插入值,所以其下一个节点一定是空if (pHead == nullptr) // 如果头结点为空,则当前节点为头结点{pHead = pNew;}else // 如果头结点不为空{ListNode *pnode = pHead; // 则先取出链表的头结点while (pnode->mNext != nullptr) // 如果当前节点有子节点,则说明不是最后一个节点{pnode = pnode->mNext;}pnode->mNext = pNew; // 找到了最后的节点,将此时要添加的节点放到链表的尾部}return pHead;
}int main()
{ListNode *Head = nullptr;Head = AddToTail(Head, 2);Head = AddToTail(Head, 3);Head = AddToTail(Head, 4);recursionlist(Head);return 0;
}
题2:两数相加
题目:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
难度:中等
思路:
我们知道其数字为逆序,那么两个链表的相同位置可以直接相加,如果相同位置相加值大于10,则代表要在下一位相加的时候,加1进位。
需要注意的点是:当两个链表都遍历完成,但是进位标志仍然为1,代表我们需要再创建一个节点用于进位。例如:链表1为999,链表二为1,两个相加,当访问完链表1的第三个位置时,循环退出,此时我们的结果为000并且进位标志不为0,因此我们要再创建一个节点存储1的结果,然后将其输出变为0001
代码一
struct ListNode
{int val;ListNode *next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}
};class Solution
{
public:ListNode *addTwoNumbers(ListNode *l1, ListNode *l2){if (l1 == nullptr) // 如果第一个链表为空,则返回第二个链表{return l2;}if (l2 == nullptr) // 如果第二个链表为空,则返回第一个链表{return l1;}ListNode *returnlistPhead = nullptr; // 先创建返回链表的头节点链表ListNode *returnlistptail = nullptr; // 先创建返回链表的头节点链表int carry = 0; // 代表进位while (l1 != nullptr || l2 != nullptr){ListNode *node = new ListNode();if (l1 != nullptr && l2 != nullptr){if (carry + l1->val + l2->val < 10) // 如果两位相加不超过10,则不需要进位{node->val = carry + l1->val + l2->val;carry = 0; // 重置进位,代表当前位数不需要进位}else{node->val = carry + l1->val + l2->val - 10; // 如果两位相加超过10,则需要进位carry = 1; // 代表当前位数需要进位}l1 = l1->next;l2 = l2->next;}else if (l1 == nullptr) // 此时l2不为空{if (carry + l2->val < 10) // 如果两位相加不超过10,则不需要进位{node->val = carry + l2->val;carry = 0; // 重置进位,代表当前位数不需要进位}else{node->val = carry + l2->val - 10; // 如果两位相加超过10,则需要进位carry = 1; // 代表当前位数需要进位}l2 = l2->next;}else if (l2 == nullptr) // 此时l1不为空{if (carry + l1->val < 10) // 如果两位相加不超过10,则不需要进位{node->val = carry + l1->val;carry = 0; // 重置进位,代表当前位数不需要进位}else{node->val = carry + l1->val - 10; // 如果两位相加超过10,则需要进位carry = 1; // 代表当前位数需要进位}l1 = l1->next;}// 插入链表中if (returnlistPhead == nullptr) // 如果返回的链表第一个元素为空{returnlistPhead = returnlistptail = node;returnlistptail->next = nullptr;}else{returnlistptail->next = node;returnlistptail = returnlistptail->next;}}if(carry == 1)//当我们每次使用进位后都会将carry重置为0,而此时代表链表一和链表二都遍历完毕,但是还有一次进位没使用,所以要多加一位//例如链表1为999,链表2为 1,此时必须多创建一个节点,用以进位{ListNode *node = new ListNode(1, nullptr);returnlistptail->next = node; }return returnlistPhead;}
};
代码二:
此处主要是将上面的代码简化。
struct ListNode
{int val;ListNode *next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}
};class Solution
{
public:ListNode *addTwoNumbers(ListNode *l1, ListNode *l2){if (l1 == nullptr) // 如果第一个链表为空,则返回第二个链表{return l2;}if (l2 == nullptr) // 如果第二个链表为空,则返回第一个链表{return l1;}ListNode *prevreturnPhead = new ListNode(-1,nullptr); // 先创建返回链表的头节点链表的前一个节点ListNode *prevreturntail = prevreturnPhead;int carry = 0; // 代表进位while (l1 || l2 || carry){int sum = 0;if (l1){sum += l1->val;l1 = l1->next;}if (l2){sum += l2->val;l2 = l2->next;}sum += carry;prevreturntail->next = new ListNode((sum % 10),nullptr);prevreturntail = prevreturntail->next;carry = sum /10;}return prevreturnPhead->next;}
};
题3:删除链表的倒数第n个节点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
难度:中等
3.1方法一:递归
使用cur标志位找到倒数要删除的节点,如果是要删除的节点,则将当前节点的next指针指向下一个节点的next指针,如果不是要删除的节点,则指针保持不变。
注意在递归调用中指针名称相同,但其代表的含义是不同的。
class Solution
{
public:int cur = 0;ListNode *removeNthFromEnd(ListNode *head, int n){if (!head)return NULL;head->next = removeNthFromEnd(head->next, n);cur++;if (n == cur)return head->next;return head;}
};
3.2 方法二:栈
在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了。
第一步:添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。
第二步:将其循环放入栈中。
第三步:找到要删除的元素的上一个元素,即栈顶元素。
第四步:重置要删除的元素的上一个元素的next指针为删除元素的next指针。
第五步:返回哑节点的next指针,即为链表的头结点。
struct ListNode
{int val;ListNode *next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}
};class Solution
{
public:int cur = 0;ListNode *removeNthFromEnd(ListNode *head, int n){if (head == nullptr || n <= 0){return nullptr;}ListNode *dummynode = new ListNode(0, head);//添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了ListNode *dummynode2 = dummynode;stack<ListNode *> stacklists;while (dummynode2 != nullptr){stacklists.push(dummynode2);dummynode2 = dummynode2->next;}while (n != 0 && !stacklists.empty()){n--;stacklists.pop();}ListNode *prevnode = stacklists.top();//要删除节点的上一个节点prevnode->next = prevnode->next->next;ListNode *returnhead = dummynode->next;return returnhead;}
};
题4:两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3]
思路:
第一步:创建哑结点 dummy,令 dummy.next = head。令 temp 表示当前到达的节点,初始时 temp = dummy。每次需要交换 temp 后面的两个节点。
如果 temp 的后面没有节点或者只有一个节点,则没有更多的节点需要交换,因此结束交换。否则,获得 temp 后面的两个节点 node1 和 node2,通过更新节点的指针关系实现两两交换节点。
第二步:交换之前的节点关系是 temp -> node1 -> node2,交换之后的节点关系要变成 temp -> node2 -> node1
temp.next = node2
node1.next = node2.next
node2.next = node1
完成上述操作之后,节点关系即变成 temp -> node2 -> node1。
第三步:再令 temp = node1,对链表中的其余节点进行两两交换,直到全部节点都被两两交换。
两两交换链表中的节点之后,新的链表的头节点是 dummyHead.next,返回新的链表的头节点即可。
#include <iostream>
#include <vector>
#include <stack>
using namespace std;struct ListNode
{int val;ListNode *next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}
};class Solution
{
public:ListNode *swapPairs(ListNode *head){if (head == nullptr || head->next == nullptr){return head;}ListNode *dummy = new ListNode(0, head);//创建一个哑节点,因为除了链表首两个节点交换,其他节点交换会有三个节点的指针发生变化。//所以为了不特殊处理头节点,创建了哑节点。ListNode *temp = dummy;while (temp->next != nullptr && temp->next->next != nullptr)//当哑结点的下一个元素和下两个元素都不为空的时候,才发生交换//{ListNode *one = temp->next;//第一个节点ListNode *two = temp->next->next;//第二个节点temp->next = two;one->next = two->next;two->next = one;temp = one;}ListNode *retrunhead= dummy->next;delete dummy;return retrunhead;}
};
题5:删除排序链表中重复的元素
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
示例 1:
输入:head = [1,1,2] 输出:[1,2]
思路:
本题可以用快慢指针的方法,将慢指针指向第一个节点,快指针指向第二个节点,如果第一个节点的值等于第二个节点的值,则删除当前节点,并移动快指针到下一个值,如果慢指针和快指针指向的值不相等,则慢指针和快指针都向前移动一次,直到遍历完链表。
#include <iostream>
#include <vector>
#include <stack>
using namespace std;struct ListNode
{int val;ListNode *next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}
};class Solution
{
public:ListNode *deleteDuplicates(ListNode *head){if (head == nullptr || head->next == nullptr)//如果链表中没有元素或者只有一个元素则直接返回{return head;}ListNode *slow = head;ListNode *fast = head->next;while (fast != nullptr){if (slow->val == fast->val)//如果慢指针的值等于快指针的值,则删除当前节点,慢指针的下一个节点指向要删除节点的下一个节点{slow->next = fast->next;fast = fast->next;//更新快指针为要删除节点的下一个节点}else //如果慢指针的值不等于快指针的值,则移动慢指针的值到下一位,移动快指针的值也到下一位{slow =slow->next;fast = fast->next;}}return head;}
};