数据结构算法100天
Day01
链表经典操作
- 虚拟头节点(涉及插入和删除操作时使用)
- 快慢指针(涉及查找链表元素时使用)
- 递归
- 迭代
链表经典习题
- 单链表反转 leetcode_206–递归/迭代
//1.双指针迭代法
struct ListNode* reverseList(struct ListNode* head) {
//定义两个指针 pre 在前, curr在后
struct ListNode* curr = NULL;
struct ListNode* pre = head;
//每次让 pre的next指向curr,实现一次局部反转,直至pre为空
while (pre) {
//每次局部逆转之前,先保存pre的后一个节点的位置
struct ListNode* next = pre->next;
//局部逆转
pre->next = curr;
//局部逆转完成后,分别让 curr 和 pre 指针前移一位
curr = pre;
pre = next;
}
return pre;
}
//2.递归法
struct ListNode* reverseList(struct ListNode* head)
//递归终止条件,链表为空或者 当前指针已经移动至最后一个结点
if (head == NULL || head->next == NULL) {
//返回当前节点
return head;
}
//递归调用传入下一个节点,目的是为了到达最后一个节点
struct ListNode* newHead = reverseList(head->next);
//到达最后一个节点后,开始出栈;
//将当前节点的下一个节点的next指向当前节点
head->next->next = head;
//并将当前节点的next的next置空
head->next = NULL;
return newHead;
}
- 链表中环的检测 leetcode_141–快慢指针
//快慢指针判断链表是否有环
bool hasCycle(LNode* head) {
//【思路】
//设计两个快慢指针,两指针从头节点出发开始移动;
//快指针每次移动两步,慢指针每次移动一步;
//链表没有环时,快指针一直在慢指针前面,此时当快指针移动到最后一个元素时,返回false;
//如果链表中有环,快指针将会在环中追上慢指针,此时返回true,表示存在环.
LNode* fast = head;
LNode* slow = head;
//如果快指针不为空(空链表情况)以及快指针的next(一个结点和快指针移动到最后一个结点时)不为空时循环
while(fast != NULL && fast->next != NULL) {
//快指针移动两步
fast = fast->next->next;
//慢指针移动一步
slow = slow->next;
//快指针追上慢指针时,表示存在环
if (slow == fast) {
return true;
}
}
//循环结束时(快指针移动到最后一个元素)没有发现有环,返回false
return false;
}
- 两个有序的链表合并 leetcode_21—递归/迭代
//1.递归
LNode* mergeTwoLists(LNode* l1, LNode* l2) {
//递归边界
if(l1 == NULL) {
return l2;
}
if(l2 == NULL) {
return l1;
}
//子问题: mergeTwoLists(l1,l2)等价于 l1->next = mergeTwoLists(l1->next,l2);
//如果l1对应的结点值小于等于l2对应的结点值,最终合并的链表头节点一定为最底层栈中的l1
if(l1->val <= l2->val) {
//当前层需要将以l1后一个结点为头节点的链表与l2合并,返回的链表连到l1->next后面
l1->next = mergeTwoLists(l1->next,l2);
//递归工作栈全部结束后,最底层栈中l1指向的链表即最终合并的链表
return l1;
}
//如果l2对应的结点值小于l1对应的结点值
l2->next = mergeTwoLists(l1,l2->next);
return l2;
}
//2.迭代【使用虚拟(哑)结点】
LNode* mergeTwoLIsts(LNode* l1, LNode* l2) {
//定义一个虚拟结点(初始时其 next 指向l1)
LNode* prehead = (LNode*)malloc(sizeof(LNode));
prehead->next = l1;
//定义一个初始时指向虚拟结点的指针
LNode* prev = prehead;
//当其中一个链表已经被合并完时,跳出循环
while(l1 != NULL && l2 != NULL) {
//如果l1的值小于等于l2的值,将prev的next指向l1,并将l1向后移动
//尾插法
if(l1->val <= l2->val) {
prev->next = l1;
l1 = l1->next;
}else {
prev->next = l2;
l2 = l2->next;
}
//每趟循环更新prev指针(prev指针后移一位)
prev = prev->next;
}
//合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev->next = (l1 == NULL) ? l2 : l1;
//最后返回以虚拟结点下一个节点为头节点链表
return prehead->next;
}
- 删除链表倒数第 n 个结点 leetcode_19–双指针+哑结点
【分析】:first指针先移动 n 个位置, 假设链表长 length , 此时链表的表尾 NULL 距离 first 指针为 (length + 1 )- (n + 1),即为 length - n 个距离 , first指针继续向后移动至 NULL,需要移动 length - n 步,此时工作指针也从 head 向后移动 length - n 步,移动完毕后,工作指针距离链表的最后一个节点为 length - (length-n + 1),即为 n -1 个距离,所以此时工作指针所指节点为 倒数第 n 个节点
//删除单链表倒数第k个节点
//1.双指针 + 哑节点
LNode* removeNthFromEnd(LNode* head, int n) {
//定义两个指针(双针)
//【思路】
//先让first指针移动到第n个位置,再让preDelte指针开始从头移动,
//同时first指针继续向后移动,直至first指针移动到最后的NULL
//删除节点时,需要得到该节点的前一个节点,所以这里采用虚拟结点,
//并使虚拟结点的 next 指向head
LNode* first = head;
//创建虚拟结点并初始化,next指向 head
LNode* dummy = (LNode*) malloc(sizeof(LNode));
dummy->val = -1;
dummy->next = head;
//将 preDelte 指向 dummy ,此后使用 preDelte 进行移动
LNode* preDelte = dummy;
//first先移动 n 个位置
for (int i = 0; i < n; i++)
{
first = first->next;
}
//first继续移动,直至first移动到NULL,preDelte同时与其移动相同的步数
//此时 preDelte刚好移动至被删除
while(first != NULL) {
first = first->next;
preDelte = preDelte->next;
}
//循环结束后preDelte结点即为目标结点的前驱结点
preDelte->next = preDelte->next->next;
//防止头节点被删除
LNode* ans = dummy->next;
//释放dummy 的内存空间
free(dummy);
//返回最终的头节点
return ans;
}
//2.两次遍历,第一次遍历求出链表长度 length ,第二次遍历求出所要删除位置(倒数第 n 个节点)
// 即 length - n + 1 (下标从1开始)
// 删除节点还是需要找到 目标结点的前驱节点便于删除
int getLength(struct ListNode* head) {
int length = 0;
while (head) {
++length;
head = head->next;
}
return length;
}
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
//创建 虚拟结点(哑巴节点)
struct ListNode* dummy = malloc(sizeof(struct ListNode));
//初始化 哑节点
dummy->val = 0, dummy->next = head;
//获取链表长度
int length = getLength(head);
//工作指针指向初始时指向 哑节点
struct ListNode* cur = dummy;
//根据长度,遍历到目标结点的前驱节点
for (int i = 1; i < length - n + 1; ++i) {
cur = cur->next;
}
//删除目标结点
cur->next = cur->next->next;
//防止目标结点为头节点,所以不能 直接返回 head, 因为 head 可能已经被删除了
//创建指针 指向 dummy的下一个节点
struct ListNode* ans = dummy->next;
//释放 dummy 节点内存空间
free(dummy);
//返回最终的头节点
return ans;
}
//3.使用栈:弹出栈的第 n 个节点即为倒数 第 n 个节点 (先进后出)
struct Stack {
struct ListNode* val;
struct Stack* next;
};
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* dummy = malloc(sizeof(struct ListNode));
dummy->val = 0, dummy->next = head;
struct Stack* stk = NULL;
struct ListNode* cur = dummy;
while (cur) {
struct Stack* tmp = malloc(sizeof(struct Stack));
tmp->val = cur, tmp->next = stk;
stk = tmp;
cur = cur->next;
}
for (int i = 0; i < n; ++i) {
struct Stack* tmp = stk->next;
free(stk);
stk = tmp;
}
struct ListNode* prev = stk->val;
prev->next = prev->next->next;
struct ListNode* ans = dummy->next;
free(dummy);
return ans;
}
- 求链表的中间结点 leetcode_876–快慢指针
//返回链表的中间节点
LNode* middleNode(LNode* head){
if(head == NULL) {
return NULL;
}
//定义快慢指针,fast每次移动两步,slow 每次移动一步
//当链表中一共有 奇数 个节点时 ,fast 移动到链表最后一个节点时停止,同时 slow 也移动相同次数
//当链表中一共有 偶数 个节点时 ,fast 移动到链表表尾的 NULL 时停止,同时 slow 也移动相同次数
//slow 指针指向的位置即为中间节点
LNode* fast = head;
LNode* slow = head;
//如果 fast不为空(偶数长度时),并且 fast的next也不为空(奇数长度时)
while(fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}