题目
- leetcode 203 移除链表元素
- leetcode 707 设计链表
- leetcode 206 反转链表
- leetcode 24 两两交换链表中的节点
- leetcode 19 删除链表的倒数第N个节点
- leetcode 142 环形链表II
leetcode 203 移除链表元素
思路:考察删除链表节点的操作,该链表头结点有值,可以创建一个虚拟节点指向头结点。
这样头结点和其他节点的操作就可以一致。
之后返回虚拟节点的next
ListNode* removeElements(ListNode* head, int val) {
// 构造虚拟头结点
ListNode *vhead = (ListNode*)calloc(1, sizeof(ListNode));
vhead -> next = head; // 指向头节点
ListNode *p = vhead;
while (p != nullptr){
if (p->next != nullptr && p -> next->val == val){
ListNode *q = p->next; // 要删除的节点
p->next = q->next;
delete q;
}else{
p = p->next;
}
}
head = vhead -> next;
free(vhead);
return head;
}
leetcode 707 设计链表
思路:本题为链表的基础题,考察增加,删除,查找元素。构造一个虚拟头结点,指向第一个节点。
- 增加元素:
a. 头插法:常规方法。
b. 尾插法:查找到最后一个节点,然后进行插入。
c. 在下标为index的位置上插入元素x:查找到下标为index-1的节点,然后进行插入- 删除元素:
a. 删除下标为index的元素:查找到下标为index-1的节点,然后进行删除- 查找元素
a. 查找下标为index的元素,并返回其值。
要注意的是:插入和删除元素都需要找到其插入位置的前置节点,才可以插入下列代码还可以优化。
class MyLinkedList {
public:
struct LinkNode{
int val;
LinkNode *next;
};
MyLinkedList() {
vhead = (LinkNode *)calloc(1, sizeof(LinkNode)); // 初始化
}
int get(int index) {
if (index > size - 1) // size相当于数组的length,所以index最大只能到size-1
return -1;
LinkNode *p = vhead -> next;
int i = 0; // 下标,从0开始
while (p != nullptr && i != index){
i ++;
p = p->next;
}
return p->val;
}
void addAtHead(int val) {
// 头插法
LinkNode *p = (LinkNode*)malloc(sizeof(LinkNode));
p -> val = val;
p->next = vhead->next;
vhead -> next = p;
size += 1; // 添加一个元素,size + 1
}
void addAtTail(int val) {
// 尾插法
// 找到最后一个元素
LinkNode *r = vhead;
// 最后一个元素的next为空
while (r->next != nullptr){
r = r->next;
}
LinkNode *q = (LinkNode *)malloc(sizeof(LinkNode));
q->val = val;
q->next = r->next;
r->next = q;
size ++;
}
void addAtIndex(int index, int val) {
// 将节点插入到下标为index的节点之前,即将节点插入到index的位置
if (index > size) // index 可以插入到size的位置,即插入到尾部
return;
// index为0单独考虑,如果判断的话index-1为负数
if (index == 0){
LinkNode *p = (LinkNode *)malloc(sizeof(LinkNode));
p -> val = val;
p->next = vhead->next;
vhead->next = p;
size ++;
return;
}
LinkNode *p = vhead->next;
int i = 0;
// 查找到下标为i-1的节点
while (p != nullptr && i < index - 1){
i ++;
p = p->next;
}
LinkNode *q = (LinkNode *)calloc(1, sizeof(LinkNode));
q->val = val;
q->next = p->next;
p->next = q;
size ++;
}
void deleteAtIndex(int index) {
if (index == 0)
{
LinkNode *p = vhead -> next;
vhead->next = p->next;
free(p);
size --;
return;
}
// 删除下标为index的节点
if (index > size - 1)
return;
int i = 0;
LinkNode *p = vhead->next;
while (p != nullptr && i < index - 1){
i ++;
p = p->next;
}
LinkNode *q = p->next;
p->next = q->next;
free(q);
size --;
}
private:
LinkNode *vhead; // 虚拟头结点
int size = 0; // 表示有几个节点
};
leetcode 206 反转链表
思路:
用p,q,t三个指针来表示第一,第二,第三个节点,然后进行反转
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// p, q, t, 交换p,q,t用来保存第三个节点
ListNode* p = head;
ListNode* q;
ListNode* t;
// 存在两个节点时
if (p != nullptr && p->next != nullptr) {
q = p->next;
t = q->next;
// 让第一个节点的next指向空
p->next = nullptr;
// 当第三个指针不为空时,说明至少还有三个节点
while (t != nullptr) {
q->next = p; // 第二个指向第一个
p = q;
q = t;
t = t->next;
}
// t为空,最后只有两个节点,进行交换
q->next = p;
head = q;
}
// 存在一个节点或者没有节点不需要考虑
return head;
}
};
leetcode 24 两两交换链表中的节点
思路:
与反转链表相似,只不过加了一点条件,为两两交换,需要考虑第三个节点以及第三个的next是否为空
ListNode* swapPairs(ListNode* head) {
// 两两交换
// 构造一个虚拟头结点
ListNode *vhead = (ListNode *)malloc(sizeof(ListNode));
vhead->next = head;
// 第一个p,第二个q,第三个t
ListNode *p, *q, *t;
p = head;
// l 指向每两个节点中的第一个节点
ListNode *l = vhead;
// 第二个节点存在
if (p != nullptr && p->next != nullptr){
q = p->next;
t = q->next;
while (t != nullptr && t->next != nullptr){
// 第三个节点不为空,以及第三个节点的next不为空时
// 交换p和q
p->next = t;
q->next = p;
// l指向q
l->next = q;
// 更新p,q,t,l
l = p;
p = t;
q = p->next;
t = q->next;
}
// 此时t为空,交换p,q
q->next = p;
p->next = t;
l->next = q;
}
// 第二个节点不存在,只有一个节点时
return vhead->next;
}
leetcode 19 删除链表的倒数第N个节点
思路:
常规方法:找到倒数第N个节点,需要求出节点总数,在找到目标节点,需要进行两次链表遍历。
双指针方法:j指针先遍历,j指针与i指针相差n个节点,j指针到头,就可以知道i的位置,i之后的节点就为要删除的节点。
所以j先走;然后i,j同时走,直到j遍历结束,就可求得i。
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 要求倒数第n个节点,先求出总结点数,才能知道倒数第n个节点
// 遍历
ListNode* p = head;
int count = 0; // 记录节点的数量
while (p != nullptr) {
count++;
p = p->next;
}
// 构造虚拟头结点
ListNode* vhead = (ListNode*)malloc(sizeof(ListNode));
vhead->next = head;
// 遍历链表,找到目标节点,然后删除该节点
// 一共有count个节点,删除倒数第n个节点,就是删除第count - n + 1个节点
if (count - n + 1 >= 2) {
p = vhead;
int i = 0;
// 找到第count - n个节点
while (p->next != nullptr) {
p = p->next;
i++;
// 如果是第count - n个节点
if (i == count - n) {
ListNode* q = p->next;
p->next = q->next;
delete q;
break;
}
}
}
else
{
// 删除第一个节点
ListNode *q = head;
head = head->next;
vhead->next = head;
delete q;
}
return vhead->next;
}
leetcode 面试题 02.07. 链表相交
思路:
- 暴力:时间复杂度 O(N^2)
先遍历第一个链表,再遍历第二个链表,然后两两进行比较,判断节点是否相等- 找到相交的节点,两个链表之间应该是右对齐;
从最短的那个链表初始位置开始进行遍历,然后节点进行比较,相等则返回,没有则返回null。
先遍历长的链表,遍历到短的链表开始的位置。此时需要知道两个链表的长度。然后求它们的差值,让长的链表遍历那个差值。
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
// 计算链表a和链表b的长度,那么相交的节点数量是a和b节点数量的最小值
int countA = 0;
int countB = 0;
ListNode *a = headA;
while (a != nullptr)
{
countA ++;
a = a->next;
}
ListNode *b = headB;
while (b != nullptr){
countB ++;
b = b->next;
}
// 计算a和b长度的差值
int sub = abs(countA - countB);
// 如果countA > countB, a指针移动到第sub + 1个位置
if (countA > countB){
int i = 0;
a = headA;
while (i != sub){
i ++;
a = a->next;
}
b = headB;
}
else{
int i = 0;
b = headB;
while (i != sub){
i ++;
b = b->next;
}
a = headA;
}
// 同时遍历,判断是否相等,相等则返回
while (a != nullptr && b != nullptr){
if (a == b)
return a;
a = a->next;
b = b->next;
}
return a;
}
leetcode 142 环形链表II
思路:
- 首先要判断链表是否有环:
用双指针来判断。链表如果有环,快慢指针必定会在环内相遇。- 找到相遇的节点
- 相遇之后,罗列快慢指针路程等式,进行解方程,可得到起始位置到环内起点的距离 == 相遇点到环内起点的距离
- 快慢指针分别从起始位置和相遇点开始遍历,相等时即得到链表环内起始节点
ListNode *detectCycle(ListNode *head) {
// 利用双指针判断是否有环,如果有环,快慢指针在环内相遇、
ListNode *i = head;
ListNode *j = head;
while (j != nullptr && j->next != nullptr){
j = j->next->next; // 快指针走两步
i = i->next; // 慢指针走一步
// 相同的时间下,快指针所走的位移应该是慢指针所走位移的两倍
// 找到环内相遇的节点后,就可以开始寻找环的起始节点
if (i == j){
// 画图可得,慢指针从头开始,快指针从相遇节点开始,一次一个节点,相遇节点就是环的起始节点
i = head;
while (i != j){
i = i->next;
j = j->next;
}
return i;
}
}
return nullptr;
}