数据结构算法100天Day01

数据结构算法100天

Day01

链表经典操作

  • 虚拟头节点(涉及插入和删除操作时使用)
  • 快慢指针(涉及查找链表元素时使用)
  • 递归
  • 迭代

链表经典习题

//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;
}

//快慢指针判断链表是否有环
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;
}
//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;
}

【分析】: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;
}
//返回链表的中间节点
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;
}

链表常见问题题解总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值