刷题记录---链表总结

链表性质

链表是一种物理存储单元上非连续非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
单链表只有一个后驱节点next,双向链表还会有一个前驱节点pre。

链表的基本操作:

  1. 插入
			tempnode = cur->next;
			cur->next = insertNode;
			insertNode->next = tempnode
  1. 删除
			tempnode = precur;
			precur->next = precur->next->next;
			free(tempnode);
  1. 遍历
   			当前指针 =  头指针
   			while 当前节点不为空 {
   			   print(当前节点)
   			   当前指针 = 当前指针.next
   			}

leetcode中节点定义

结构中定义了构造函数,用于构造带有初始值的节点

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) {}
};

链表操作(可作为轮子)

链表反转(单向链表)

方法一:前序遍历

ListNode* reverse(ListNode* head)
{
	ListNode* pre = nullptr;//设定一个尾节点指向的空指针
	while(ListNode* != nullptr)
	{
			ListNode* next = head->next;//记录此时节点的下一个节点的位置
			head->next = pre;//将此节点的节点指向前一个节点
			pre = head; //将前一个节点指向这时的节点
		  head = next; //将这时的节点指向下一个节点、也就是将两个几点都往后移动一位
	}
	return pre;//这里是因为head已经指向了空指针,只有pre指向的是链表的最后一个位置
}

方法二:后续遍历,递归调用
图示

ListNode* function2(ListNode* head)//递归解法---后续遍历
{
    if(head == nullptr || head->next == nullptr){return head;}//一直走到最后一个节点后返回,作为反转链表的头节点
    ListNode* cur = function2(head->next);//cur一直指向的都是最后一个节点
    head->next->next = head;//将原本顺序的下一节点反置
    head->next = nullptr;//将这一节点指向下一节点的指针赋空
    return cur;//返回反转的头节点
}

方法三:当有前后两个节点,头节点和尾节点的下一个节点(建议画图理解)

ListNode* reversePart(ListNode* head, ListNode* tail) {
		//添加一个尾节点,即在最后一个节点的最后再添加一个节点
        //head 为链表第一个节点,tail 为最后一个节点后面一个节点
        if (head == tail || head->next == tail) {
            return head;
        }
        ListNode* p, *q;
        q = tail;
        while (head != tail) {
            p = head->next; //q节点是用来存储head的下一个节点位置
            head->next = q; //q是head要指向的节点位置
            q = head; //每次更新完需要把指向节点的位置一刀下一次要指向的位置
            head = p; //将head往后移一次
        }
        return q;
    }

链表拼接

合并了两个有序链表—结果还是有序
递归调用:

//时间复杂度和空间复杂度都是O(n+m)
ListNode* function1(ListNode* l1,ListNode *l2)//递归调用也需要到最后一个指针处,可以考虑承接节点的前一个节点
{
    if(l1==nullptr) //如果有一个节点是空了,直接返回另一个节点就可以
    {return l2;}
    if(l2==nullptr)
    {return l1;}

    if(l1->val < l2->val) //如果两个值的大小不等的话,以小的节点为起始节点,并计算当前链表的下一个节点和另一个链表节点值
    {
        l1->next = function1(l1->next,l2); //接着找下一个节点
        return l1;
    }

    if(l1->val > l2->val)
    {   
        l2->next = function1(l1,l2->next);
        return l2;
    }
}

不使用递归调用,使用迭代:

ListNode* function2(ListNode* l1,ListNode* l2)
{
    ListNode* prehead = new ListNode(-1);
    ListNode* pre = prehead;
    //如果两者由一个为空了,那么后面的直接从另一个链表获取
    while(l1!=nullptr && l2!=nullptr)//起初假设两者都不为空
    {
        if(l1->val > l2->val) //比较值的大小,小的值放到节点后,每次只放进一个
        {
            pre->next = l1;
            l1 = l1->next;
        }
        else
        {
            pre->next = l2;
            l2= l2->next;
        }
        pre = pre->next; //记录当前拼接链表的最后一个节点
    }

    pre->next = l1 == nullptr ? l2 : l1;//如果后续链表有一个没有值了,就直接读取另一个链表的值(因为两者已经拍好序了)

    return prehead->next;//返回头节点的下一个节点
}

链表排序

无序链表按照大小排序
红黑树,利用set容器实现:

      multiset<int> slist;

      ListNode* res = head;
      ListNode* result = head;

      while(head != nullptr)
      {
          slist.insert(head->val);
          head = head->next;
      }

      for(auto it = slist.begin(); it!= slist.end();it++)
      {
          res->val = *it;
          res = res->next;
      }

      return result;

归并排序—将链表二分,直至不能再划分,再通过子链表按大小拼接

if(head == nullptr || head->next == nullptr){return head;}
ListNode* slow = head,*fast = head;
while(!fast->next && !fast->next->next)//这里和判断是否是回文链表的条件还不一样,灵活运用
{
    slow = slow->next;//slow到达中点的前面一个
    fast = fast->next->next;
}

fast = slow->next; 
slow->next = nullptr;

return merge(sortList(head),sortList(fast));//用到的子函数
ListNode* merge(ListNode* l1,ListNode* l2)
   {
       ListNode res(0);
       ListNode* ptr = &res;
       while(l1 && l2)
       {
            if(l1->val > l2->val)
            {
                ptr->next = l2;
                l2 = l2->next;
            }
            else
            {
                ptr->next = l1;
                l1 = l1->next;
            }
            ptr = ptr->next;
       }
       ptr->next = l1 == nullptr ? l2 : l1;
       return res.next;
   }

堆排序,利用stl中的优先队列,其底层是堆排序算法

priority_queue<int,vector<int>,greater<int>> worker;
        auto sub = head;
        while(sub)
        {
            worker.push(sub->val);//每次放进去都是把最小的放到最前面
            sub=sub->next;
        }
        sub = head;
        while(sub)
        {
            sub->val = worker.top();
            worker.pop();
            sub = sub->next;
        }

        return head;

链表操作技巧

快慢指针

设定一个快指针一个慢指针,每次快指针走的步数是慢指针的两倍,最后返回慢指针就到达中点。
考虑一下几种情况
到达中点指针

ListNode* head;
ListNode* fast = head,* slow = head;

while(fast != nullptr && fast->next != nullptr){
    fast = fast->next->next;
    slow = slow->next;
}
if(fast != nullptr){
    slow = slow->next;
}

到达中心节点的前一个节点

while(fast->next != nullptr && fast->next->next != nullptr){
    fast = fast->next->next;
    slow = slow->next;//这时的slow始终会在中心节点的前一个节点
}

虚拟头节点

新加一个虚拟头节点方便操作
在反转子链表的时候用到了

ListNode* virhead = new ListNode(0);
virhead->next = head;

注意切断链表

在重排链表这一题需要把一个链表分割成两个链表,需要把中间节点断开,即先记录中心节点的位置,再将中心节点前一个节点的next置为nullptr,利用快慢指针到中间节点的前一个节点。

算法实例

  1. 合并两个有序链表
  2. 删除排序链表中的重复元素 II
  3. 删除排序链表中的重复元素
  4. 分隔链表
  5. 反转链表 II
  6. 环形链表
  7. 环形链表 II
  8. 重排链表
  9. 排序链表
  10. 反转链表
  11. 回文链表
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值