链表 01
leetcode 203 移除表元素
题目描述
删除链表中等于给定值 val 的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
题目分析
C++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *dummy = new ListNode(0);//申请一个哨兵结点
dummy->next = head;//初始化,置于头节点前
ListNode *pre = dummy;//设置前继指针
ListNode *cur = head;//设置当前指针
while(cur != NULL)//遍历
{
if(cur -> val == val)//判断当前指针的值是否为需要删除的值
{
pre -> next = cur -> next; //直接让前继指针的后一个结点指向当前指针的后一个结点
cur = cur -> next;//当前指针向后移
}else
{ //如果不是需要删除的结点 向后遍历
cur = cur -> next;//当前指针后移
pre = pre -> next;//前继指针后移
}
}
return dummy -> next;
}
};
leetcode 206 反转链表
题目描述
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
题目分析
迭代
采用双指针的方法,对单链表进行依次局部反转。
定义两个指针: pre 和 cur ;pre 在前 cur 在后。
每次让 pre 的 next 指向 cur ,实现一次局部反转
局部反转完成之后, pre 和 cur 同时往前移动一个位置
循环上述过程,直至 pre 到达链表尾部
递归
使用递归函数,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作 ret .
此后,每次函数在返回的过程中,让当前结点的下一个结点的 next 指针指向当前节点。
同时让当前结点的 next 指针指向 NULL ,从而实现从链表尾部开始的局部反转
当递归函数全部出栈后,链表反转完成。
C++代码
迭代:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *cur = NULL;//当前指针 指向null
ListNode *pre = head;//前继指针 指向head
while(pre != NULL)
{ //遍历
ListNode *t = pre -> next; //保存 前继指针的后继
pre -> next = cur; //反转
cur = pre; //后移
pre = t;//后移
}
return cur;
}
};
递归:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == NULL || head -> next == NULL)
{
return head;
}
ListNode *ret = reverseList(head -> next);//递归到最后一个结点 保存
head -> next -> next = head; // 修改指向 反转
head -> next = NULL;
return ret;
}
};
leetcode 876 链表的中间结点
题目描述
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
题目分析
一般情况下,链表问题中设计到了位置都应该考虑通过构造双指针的移动规则来解决,因为链表无法知晓长度也无法随机访问。
本题可使用快慢双指针解决:
- 初始时两个指针均指向头指针。
- 如果快指针为空或者快指针的next为空,那么此时慢指针即为答案,移动结束。
- 每轮移动,快指针需要移动两次,慢指针需要移动一次。跳转步骤二。
尝试证明一下:
如果链表只有一个元素,那么不会触发移动逻辑,此时慢指针指向head,显然正确。
设链表总共有n个元素,且n > 1。当算法结束时,必然进行了 n/2轮移动。此时慢指针必然指向第 1+n/2个元素。
C++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *fast = head;
ListNode *slow = head;
while(fast != NULL && fast -> next != NULL)
{
slow = slow -> next;
fast = fast -> next -> next;
}
return slow;
}
};
剑指offer JZ14 链表中倒数第k个结点
题目描述
输入一个链表,输出该链表中倒数第k个结点。
题目分析
使用 快慢指针,首先让快指针先行k步,然后让快慢指针每次同行一步,直到快指针指向空节点,慢指针就是倒数第K个节点。
C++代码
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode *slow = pListHead , *fast = pListHead;//创建快慢指针
if(pListHead == NULL || k <= 0)
return NULL;
while(k--)
{ //让快指针先行 K 步
if(fast)
{ // 判断 k 是否满足 结点个数
fast = fast -> next;
}else{
return NULL;//K大于 结点个数 返回空
}
} //初始位置 设置完成
while(fast)
{ // 向后平移 直到结点末尾
slow = slow -> next;
fast = fast -> next;
}
return slow;
}
};
leetcode 21 合并两个有序链表
题目描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
题目分析
思路
我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
算法
首先,设定一个哨兵节点 prehead ,这可以在最后比较容易地返回合并后的链表。我们维护一个 prev 指针,需要做的是调整它的 next 指针。然后,重复以下过程,直到 l1 或者 l2 指向了 null :如果 l1 当前节点的值小于等于 l2 ,就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。否则,对 l2 做同样的操作。不管将哪一个元素接在了后面,我们都需要把 prev 向后移一位。
在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
C++代码
/**
* Definition for singly-linked list.
* 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* preHead = new ListNode(-1);
ListNode* prev = preHead;
while (l1 != nullptr && l2 != nullptr) {
if (l1->val < l2->val) {
prev->next = l1;
l1 = l1->next;
} else {
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev->next = l1 == nullptr ? l2 : l1;
return preHead->next;
}
};