链表定义
struct ListNode { 
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
链表的遍历

ListNode p=head; while(p!=null) p=p.next; 

找到链表的尾结点

p=head; while(p.next!=null)p=p.next;

链表节点的个数

         p=head; cnt=0; //cnt与p不同步对应

         while(p!=null):cnt++;  p=p.next

         cnt与p不同步对应的原因:

         while停止后,p为null,cnt为对应到最后一个节点上面

         规律:开始的时候同步和结束时候的同步规律是一致的。

链表删除

 注意:1.删除节点需要dummyNode  2.最后返回的是dummyNode.next而不是head.

       因为head可能被删掉

 ListNode dummyNode=new ListNode();

   dummyNode.next=head;

   (p.next为待删除节点)

    p.next=p.next.next;

   return dummyNode.next;

ListNode* deleteNode(ListNode* head, ListNode* p) {
        // 创建一个虚拟头节点,便于处理链表头部的删除操作
        ListNode dummyNode(0);
        dummyNode.next = head;
        // 将虚拟头节点的下一个节点指向原链表头节点
        ListNode* prev = &dummyNode;
        // 遍历链表,找到待删除节点的前驱节点
        while (prev->next&& prev->next != p) {
            prev = prev->next;
        }
        // 如果待删除节点存在,则进行删除操作
        if (prev->next) {
            prev->next = prev->next->next;
        } 
        else {
            std::cout << "Target node not found in the list." << std::endl;
        }
        // 返回处理后的新链表头节点(即原链表头节点)
        return dummyNode.next;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
链表插入

  (1)头插法: dummyNode,p:

p.next=dummyNode.next;
dummyNode.next=p;
  • 1.
  • 2.

  (2)尾插法

tail,p:
   tail.next=p; tail=p;
  • 1.
  • 2.

   在尾插法的过程中,tail.next不需要清空:

   每次插入时tail.next都会重定向到待插入的节点

   最后tail.next=null

找到链表的第index个节点

int i=1;
p=head;
//计数变量和p索引始终同步对应:while停止后,i对应p.
while(i<index&&p!=null)
     p=p.next;
     i++;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
链表翻转

算法整理:链表_尾插法

ListNode *a = nullptr;
ListNode *b = head;
while(b)
     ListNode *c=b->next;
     b->next=a;
     a=b;
     b=c;
return a;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
指定区间反转链表

增加虚拟头结点。

  如果说从第一个开始就翻转,那么就得分情况讨论,所以要添加一个虚拟头结点。

算法整理:链表_尾插法_02

两个链表第一个公共的节点

 (1)求两个链表长度n1,n2  

 (2)长的链表走|n1-n2|步,使两个链表长度相同

 (3)两个链表一起走,相等返回,走到头返回null

倒数第k个节点

(1) 求节点个数n

(2)求第n-k+1个节点

删除重复节点

 (1)增加虚拟头结点

   (2)链表节点的删除

     p.next=p.next.next

删除链表倒数第n个节点

   (1)添加虚拟头结点

   (2)求链表长度l

   (3)找第l-n的节点并删除

判断环

         如果有环:slow和fast走一定会相遇

         如果没有环:在有限次遍历链表后,一定为空

if head==null:return false//特判
slow=head;
fast=head.next;   
while(slow!=fast&&slow!=null&&slow.next!=null&&fast!=null&&fast.next!=null)
       slow=slow.next;
       fast=fast.next.next;
       if(slow==fast)return true;
       return false
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
判断环的入口

(1)fast和slow置头结点,速度为2,1

(2)相遇后fast置头结点,速度为1

(3)fast和slow相遇的节点是入口

fast=pHead;
slow=pHead;
while(fast!=null&&fast.next!=null){
     fast=fast.next.next;slow=slow.next;
     if(fast==slow){
          fast=pHead;
          while(fast!=slow){
              fast=fast.next;
              slow=slow.next;}
          return fast;
return nullptr//如果遍历到空节点,说明没有环,返回null
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
合并两个排序的链表
p=head1,q=head2;
tail = dummyNode;
while(p!=null&&q!=null){
     if(p.val<q.val)tail.next=p;tail=p;p=p.next;
     else  tail.next=q;tail=q;q=q.next;
}

tail直接指向非空的一个链表,如果两个链表都是空,那么就指向空
tail.next=(p==null)?q:p;
return dummyNode.next;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
回文链表

存储链表的值

奇偶链表

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。 O(1) 的额外空间复杂度和 O(n) 的时间复杂度

算法整理:链表_算法_03

两个虚拟头结点,使用尾插法,然后合并到一起。

ListNode *odd = new ListNode(0);
ListNode *even = new ListNode(0);

 ListNode *tail1=odd;
 ListNode *tail2=even;
 ListNode *p = head;
 int idx=1;
  while(p!=nullptr){
       if(idx%2!=0){
                tail1->next=p;
                tail1=p;
                p=p->next;
                idx++;
        }else{
                tail2->next=p;
                tail2=p;
                p=p->next;
                idx++;
            }
        }
//最后需要把tail2->next置空
        tail2->next=nullptr;
        tail1->next=even->next;
        return odd->next;
 }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

尾插法最后置空,否则会出现野指针错误。

LeetCode 25 k个一组翻转链表

使用栈,进栈k次,尾插法就是逆序的。需要考虑剩下的链表个数够不够 k 个(因为不够 k 个不用翻转)。已经翻转的部分要与剩下链表连接起来。

stack<ListNode*>st
ListNode dummy(0)
ListNode* tail = &dummy
while (true):
      int count = 0;
      ListNode* p = head;
      / 将k个节点压入栈中
      while (p&&count<k):
           st.push(p);
           p=p->next;
           count++;
      if(count != k)
           tail->next=head
           break
      while (!stack.empty())
                tail->next = stack.top()
                stack.pop()
                tail = tail->next   
            tail->next = p
            head = p
return dummy->next
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
LeetCode 23  合并 K 个升序链表

使用优先队列。
 

struct CompareListNode {
    bool operator()(const ListNode* l1, const ListNode* l2) const {
        return l1->val > l2->val;
    }
};
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
         if (lists.empty()) return nullptr;
        priority_queue<ListNode*, vector<ListNode*>, CompareListNode> queue;
        for (auto node : lists) {
            if (node != nullptr) queue.push(node);
        }
        ListNode dummy(0);
        ListNode* p = &dummy;
        while (!queue.empty()) {
            p->next = queue.top();
            queue.pop();
            p = p->next;
            if (p->next) queue.push(p->next);
        }
        return dummy.next;
    }

};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

分析时间复杂度:

  • 初始化优先队列:将所有链表的头节点加入优先队列的时间复杂度为 O(k),其中 k 是链表数组 lists 的长度,这是因为每个节点仅需一次入队操作。
  • 主循环:每次循环中,我们执行一次弹出操作(O(log k)),然后可能执行一次入队操作(O(log k))。由于每个链表中的节点最多会被入队和弹出各一次,且总共有 n 个节点(n 是所有链表节点总数),因此主循环部分的时间复杂度为 O(n log k)。
  • 构造结果链表:此过程仅涉及指针赋值,时间复杂度可视为常数,即 O(1)。
LeetCode 143  重排链表

算法整理:链表_尾插法_04

void reorderList(ListNode* head) {
        vector<ListNode*> list;
        ListNode* p = head;
        while (p != nullptr) {
            list.push_back(p);
            p = p->next;
        }
        int i = 0, j = list.size() - 1;
        while (i < j) {
            
            list[i]->next = list[j];
            ++i;
            if (i==j) break;

            list[j]->next = list[i];
            --j;
            if(i==j) break;
        }
        list[i]->next = nullptr;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
leetcode82 删除排序链表的重复元素||  

算法整理:链表_算法_05

算法整理:链表_尾插法_06

ListNode* deleteDuplicates(ListNode* head)
        ListNode *dNode = new ListNode(0);
        dNode->next =  head;
        ListNode *p = dNode;
        while(p->next){
            ListNode *q=p->next->next;
            while(q&&p->next->val==q->val)q=q->next;
            if(p->next->next==q)p=p->next;
            else p->next=q;
        return dNode->next;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
LeetCode 2 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。

算法整理:链表_头结点_07

算法整理:链表_头结点_08

ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
        ListNode *res = new ListNode(-1);   //添加虚拟头结点,简化边界情况的判断
        ListNode *cur = res;
        int carry = 0;  //表示进位
        while (l1 || l2) 
        {
            int n1 = l1 ? l1->val : 0;
            int n2 = l2 ? l2->val : 0;
            int sum = n1 + n2 + carry;
            carry = sum / 10;
            cur->next = new ListNode(sum % 10);
            cur = cur->next;
            if (l1) l1 = l1->next;
            if (l2) l2 = l2->next;
        }
        if (carry) cur->next = new ListNode(1); //如果最高位有进位,则需在最前面补1.
        return res->next;   //返回真正的头结点
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

 有序表的归并算法:??

算法整理:链表_算法_09

算法整理:链表_算法_10