C++链表题

在C++中,链表是一种常见的数据结构,用于存储一系列的元素,其中每个元素都指向下一个元素。这种结构允许动态地增加或删除元素,而不需要像数组那样预先分配固定大小的内存。

// 定义链表节点结构
typedef struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
} ListNode;

反转链表

给定一个单链表的头结点pHead(该头节点是有值的,如它的val是1),长度为n,反转该链表后,返回新链表的表头。

// 反转整个链表
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* curr = head;
        while(curr != nullptr) {
            ListNode* next = curr->next;
            curr->next = pre;
            pre = curr;
            curr = next;
        }
        return pre;
    }

链表内指定区间反转

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。

// 反转链表中m到n的部分
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // 创建虚拟头节点,简化边界情况
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        
        // 找到第m-1个节点(反转的前一个节点)
        ListNode* pre = dummy;
        for(int i=1; i<m; i++) {
            pre = pre->next;
        }
        
        // 反转从m到n的节点
        ListNode* curr = pre->next;
        ListNode* next;
        for(int i=m; i<n; i++) {
            next = curr->next;
            curr->next = next->next;
            next->next = pre->next;
            pre->next = next;
        }
        
        ListNode* result = dummy->next;
        delete dummy;
        return result;
    }

链表中的节点每k个一组翻转

将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样你不能更改节点中的值,只能更改节点本身。

// 反转链表中每k个节点一组的部分(O(1)空间复杂度)
    ListNode* reverseKGroup(ListNode* head, int k) {
        // 创建虚拟头节点,简化边界情况
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        
        // pre指向每组反转的前一个节点
        ListNode* pre = dummy;
        // end指向每组反转的最后一个节点
        ListNode* end = dummy;
        
        while(end->next != nullptr) {
            // 移动end到第k个节点
            for(int i=0; i<k && end != nullptr; i++) {
                end = end->next;
            }
            // 如果剩余节点不足k个,结束循环
            if(end == nullptr) break;
            
            // start指向每组反转的第一个节点
            ListNode* start = pre->next;
            // nextGroup指向每组反转的下一组的第一个节点
            ListNode* nextGroup = end->next;
            
            // 将当前组与下一组断开连接
            end->next = nullptr;
            
            // 反转当前组
            pre->next = reverseList(start);
            
            // 将反转后的组与下一组重新连接
            start->next = nextGroup;
            
            // 更新pre和end为下一组反转的前一个节点
            pre = start;
            end = pre;
        }
        
        ListNode* result = dummy->next;
        delete dummy;
        return result;
    }

合并两个排序的链表

输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。

// 合并两个有序链表
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        // 创建虚拟头节点,简化边界处理
        ListNode* dummy = new ListNode(0);
        ListNode* curr = dummy;
        
        // 同时遍历两个链表,将较小的节点添加到结果链表
        while(l1 != nullptr && l2 != nullptr) {
            if(l1->val <= l2->val) {
                curr->next = l1;
                l1 = l1->next;
            } else {
                curr->next = l2;
                l2 = l2->next;
            }
            curr = curr->next;
        }
        
        // 连接剩余节点
        if (l1 != nullptr) {
            curr->next = l1;
        } else {
            curr->next = l2;
        }
        
        ListNode* result = dummy->next;
        delete dummy;
        return result;
    }

合并k个已排序的链表

合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。

    // 定义优先队列的比较函数(小根堆)
    struct ListNodeCompare {
        bool operator()(ListNode* a, ListNode* b) {
            return a->val > b->val; // 小根堆:值小的优先级高
        }
    };
    
    // 小根堆(优先队列)实现合并k个链表
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        // 创建小根堆,存储每个链表的当前头节点
        priority_queue<ListNode*, vector<ListNode*>, ListNodeCompare> minHeap;
        
        // 将所有非空链表的头节点加入堆
        for (ListNode* list : lists) {
            if (list != nullptr) {
                minHeap.push(list);
            }
        }
        
        // 虚拟头节点,简化边界处理
        ListNode* dummy = new ListNode(0);
        ListNode* curr = dummy;
        
        // 不断从堆中取出最小节点,添加到结果链表
        while (!minHeap.empty()) {
            // 取出堆顶(最小)节点
            ListNode* minNode = minHeap.top();
            minHeap.pop();
            
            // 将最小节点添加到结果链表
            curr->next = minNode;
            curr = curr->next;
            
            // 如果该节点所在链表还有后续节点,将其加入堆
            if (minNode->next != nullptr) {
                minHeap.push(minNode->next);
            }
        }
        
        // 获取结果链表的头节点并删除虚拟节点
        ListNode* result = dummy->next;
        delete dummy;
        
        return result;
    }

判断链表中是否有环

判断给定的链表中是否有环。如果有环则返回true,否则返回false。

// 检测链表中是否有环(Floyd's Tortoise and Hare算法)
    bool hasCycle(ListNode *head) {
        if(head == nullptr) return false;
        
        ListNode* slow = head;  // 慢指针(乌龟),每次移动1步
        ListNode* fast = head;  // 快指针(兔子),每次移动2步
        
        while(fast != nullptr && fast->next != nullptr) {
            slow = slow->next;        // 慢指针移动1步
            fast = fast->next->next;  // 快指针移动2步
            
            if(slow == fast) {  // 如果相遇,说明有环
                return true;
            }
        }
        
        return false;  // 快指针到达链表末尾,说明没有环
    }

链表中环的入口结点

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

// 找到链表中环的入口节点(Floyd's Tortoise and Hare算法)
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        
        ListNode* slow = pHead;  // 慢指针,每次移动1步
        ListNode* fast = pHead;  // 快指针,每次移动2步
        
        // 第一步:检测环的存在
        while(fast != nullptr && fast->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
            
            if(slow == fast) {  // 快慢指针相遇,说明有环
                // 第二步:找到环的入口
                slow = pHead;  // 将慢指针重置到链表头部
                while(slow != fast) {  // 两个指针都以相同速度移动,再次相遇点即为环入口
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;  // 返回环的入口节点
            }
        }
        
        return nullptr;  // 快指针到达链表末尾,说明没有环
    }

链表中倒数最后k个结点

输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。

如果该链表长度小于k,请返回一个长度为 0 的链表。

// 找到链表中倒数第k个节点(双指针法)
    ListNode* FindKthToTail(ListNode* pHead, int k) {
        // 处理边界情况:链表为空或k<=0
        if(pHead == nullptr || k <= 0) return nullptr;
        
        ListNode* slow = pHead;  // 慢指针,初始指向链表头部
        ListNode* fast = pHead;  // 快指针,初始指向链表头部
        
        // 第一步:让快指针先走k步
        for(int i=0; i<k; i++) {
            if(fast == nullptr) {  // 如果链表长度小于k,返回nullptr
                return nullptr;
            }
            fast = fast->next;
        }
        
        // 第二步:快慢指针同时移动,直到快指针到达链表末尾
        while(fast != nullptr) {
            slow = slow->next;
            fast = fast->next;
        }
        
        return slow;  // 慢指针此时指向倒数第k个节点
    }

删除链表的倒数第n个节点

给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针

// 删除链表中倒数第n个节点(双指针法)
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // 创建虚拟头节点,简化删除头节点的边界情况
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        
        // 慢指针指向虚拟头节点
        ListNode* slow = dummy;
        // 快指针指向虚拟头节点
        ListNode* fast = dummy;
        
        // 快指针先走n+1步,这样快慢指针之间的距离为n+1
        for(int i=0; i<n+1; i++){
            // 题目保证n有效,此检查可以省略,但保留以增强鲁棒性
            if(fast == nullptr){
                return head;
            }
            fast = fast->next;
        }
        
        // 快慢指针同时移动,直到快指针到达链表末尾
        while (fast != nullptr) {
            slow = slow->next;
            fast = fast->next;        
        }
        
        // 此时慢指针指向倒数第n+1个节点,删除倒数第n个节点
        ListNode* toDelete = slow->next;
        slow->next = slow->next->next;
        delete toDelete;  // 释放被删除节点的内存
        
        // 保存新的头节点
        ListNode* newHead = dummy->next;
        // 释放虚拟头节点
        delete dummy;
        return newHead;
    }

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

输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。

ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
        // 双指针交叉遍历法:时间复杂度O(n), 空间复杂度O(1)
        if (pHead1 == nullptr || pHead2 == nullptr) return nullptr;
        
        ListNode* p1 = pHead1;
        ListNode* p2 = pHead2;
        
        // 两个指针同时遍历,当走到末尾时切换到另一个链表的头部
        // 如果有公共节点,它们会在公共节点相遇
        // 如果没有公共节点,最终都会走到nullptr
        while (p1 != p2) {
            p1 = (p1 == nullptr) ? pHead2 : p1->next;
            p2 = (p2 == nullptr) ? pHead1 : p2->next;
        }
        
        return p1;  // 返回公共节点或nullptr
    }

链表相加

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。

给定两个这种链表,请生成代表两个整数相加值的结果链表。

例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。

ListNode* addInList(ListNode* head1, ListNode* head2) {
        // 边界处理
        if (head1 == nullptr) return head2;
        if (head2 == nullptr) return head1;

        // 1. 反转两个链表,使链表头指向原链表的最低位
        ListNode* reversed1 = reverseList(head1);
        ListNode* reversed2 = reverseList(head2);

        // 2. 创建结果链表的虚拟头节点
        ListNode* dummy = new ListNode(0);
        ListNode* curr = dummy;
        int carry = 0;  // 进位

        // 3. 逐位相加,处理进位
        while (reversed1 != nullptr || reversed2 != nullptr || carry != 0) {
            int val1 = (reversed1 != nullptr) ? reversed1->val : 0;
            int val2 = (reversed2 != nullptr) ? reversed2->val : 0;

            // 计算当前位的和与进位
            int sum = val1 + val2 + carry;
            int digit = sum % 10;
            carry = sum / 10;

            // 将结果位添加到新链表
            curr->next = new ListNode(digit);
            curr = curr->next;

            // 移动指针
            if (reversed1 != nullptr) reversed1 = reversed1->next;
            if (reversed2 != nullptr) reversed2 = reversed2->next;
        }

        // 4. 反转结果链表,得到正确的顺序
        ListNode* result = reverseList(dummy->next);
        
        // 恢复原始链表的结构(可选,根据需求决定)
        // reverseList(reversed1); // 恢复head1
        // reverseList(reversed2); // 恢复head2
        
        // 释放虚拟头节点
        delete dummy;

        return result;
    }

单链表的排序

给定一个节点数为n的无序单链表,对其按升序排序。

// 找到链表的中点(快慢指针法)
    ListNode* findMiddle(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return head;
        
        ListNode* slow = head;  // 慢指针,每次移动1步
        ListNode* fast = head->next;  // 快指针,每次移动2步
        
        while (fast != nullptr && fast->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        
        return slow;  // 返回中点的前一个节点
    }
    
    // 基于归并排序的链表排序(O(n log n)时间,O(n)空间)
    ListNode* sortInList(ListNode* head) {
        // 边界处理:空链表或只有一个节点的链表已经有序
        if (head == nullptr || head->next == nullptr) {
            return head;
        }
        
        // 1. 找到链表的中点,将链表分成两个子链表
        ListNode* mid = findMiddle(head);
        ListNode* secondHalf = mid->next;
        mid->next = nullptr;  // 将链表断开
        
        // 2. 递归地对两个子链表进行排序
        ListNode* left = sortInList(head);
        ListNode* right = sortInList(secondHalf);
        
        // 3. 合并两个已排序的子链表
        return mergeTwoLists(left, right);
    }

判断一个链表是否为回文结构

给定一个链表,请判断该链表是否为回文结构。回文是指该字符串正序逆序完全一致。

bool isPail(ListNode* head) {
        // write code here
        // 边界处理:空链表或只有一个节点的链表都是回文
        if (head == nullptr || head->next == nullptr) {
            return true;
        }
        
        // 1. 使用快慢指针找到链表中点
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast != nullptr && fast->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        
        // 2. 反转后半部分链表
        ListNode* secondHalfHead = reverseList(slow);
        ListNode* secondHalfTemp = secondHalfHead; // 保存后半部分反转后的头节点,用于恢复
        
        // 3. 比较前半部分和反转后的后半部分
        bool isPalindrome = true;
        ListNode* firstHalf = head;
        while (secondHalfHead != nullptr) {
            if (firstHalf->val != secondHalfHead->val) {
                isPalindrome = false;
                break;
            }
            firstHalf = firstHalf->next;
            secondHalfHead = secondHalfHead->next;
        }
        
        // 4. 恢复原链表结构(可选,但良好的编程实践)
        reverseList(secondHalfTemp);
        
        // 5. 返回结果
        return isPalindrome;
    }

链表的奇偶重排

给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。

注意是节点的编号而非节点的数值。

ListNode* oddEvenList(ListNode* head) {
        // write code here
        // 边界处理:空链表、单节点或双节点链表直接返回
        if (!head || !head->next) {
            return head;
        }
        
        // 创建奇偶节点的指针
        ListNode* odd = head;  // 奇数位置节点的头指针
        ListNode* even = head->next;  // 偶数位置节点的头指针
        ListNode* evenHead = even;  // 保存偶数链表的头节点
        
        // 遍历链表,分离奇偶节点
        while (even != nullptr && even->next != nullptr) {
            odd->next = even->next;  // 奇数节点指向后一个奇数节点
            odd = odd->next;  // 移动奇数节点指针
            even->next = odd->next;  // 偶数节点指向后一个偶数节点
            even = even->next;  // 移动偶数节点指针
        }
        
        // 将偶数链表连接到奇数链表的末尾
        odd->next = evenHead;
        
        return head;
    }

删除有序链表中重复的元素

删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次例如:给出的链表为1→1→2,返回1→2.

给出的链表为1→1→2→3→3,返回1→2→3.

ListNode* deleteDuplicates(ListNode* head) {
        // write code here
        if (!head) {
            return head;
        }
        
        ListNode* current = head;
        while (current->next != nullptr) {
            if (current->val == current->next->val) {
                // 删除重复节点
                ListNode* temp = current->next;
                current->next = current->next->next;
                delete temp; // 释放内存,避免内存泄漏
            } else {
                // 移动到下一个节点
                current = current->next;
            }
        }
        
        return head;
    }

给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。

例如:给出的链表为1→2→3→3→4→4→5, 返回1→2→5.

给出的链表为1→1→1→2→3, 返回2→3.

ListNode* deleteDuplicatesSec(ListNode* head) {
        // 边界处理:空链表直接返回
        if (!head) {
            return head;
        }
        // 创建虚拟头节点,简化边界情况处理
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* prev = dummy;
        ListNode* current = head;
        
        while (current != nullptr) {
            // 检查当前节点是否与下一个节点重复
            bool isDuplicate = false;
            while (current->next != nullptr && current->val == current->next->val) {
                isDuplicate = true;
                // 删除重复节点并释放内存
                ListNode* temp = current->next;
                current->next = current->next->next;
                delete temp;
            }
            
            if (isDuplicate) {
                // 当前节点也是重复的,需要删除
                ListNode* temp = current;
                prev->next = current->next;
                current = current->next;
                delete temp;
            } else {
                // 当前节点不重复,移动到下一个节点
                prev = prev->next;
                current = current->next;
            }
        }
        
        // 获取新的头节点并释放虚拟头节点
        head = dummy->next;
        delete dummy;
        
        return head;
    }
posted @ 2026-01-02 22:07  yangly  阅读(8)  评论(0)    收藏  举报