【LeetCode】力扣刷题热题100道(6-10题)附源码 相交链表 回文链表 反转链表 合并链表 移动零(C++)

目录

1.合并两个有序链表

2.移动零

3.相交链表

4.反转链表

5.回文链表


1.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

复制代码
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

复制代码
输入:l1 = [], l2 = []
输出:[]

示例 3:

复制代码
输入:l1 = [], l2 = [0]
输出:[0]

方法一:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode *dummy = new ListNode(0);
        ListNode *current = dummy;
 
        while (list1 != nullptr && list2 != nullptr) {
            if (list1->val <= list2->val) {
                current->next = list1;
                list1 = list1->next;
            } else {
                current->next = list2;
                list2 = list2->next;
            }
            current = current->next;
        }
 
        if (list1 != nullptr) {
            current->next = list1;
        } else {
            current->next = list2;
        }
 
        return dummy->next;
    }
};

条件:list1 != nullptr && list2 != nullptr

只要两个链表都不为空,就可以比较它们的当前节点值。

分支逻辑:

如果 list1->val 小于等于 list2->val:

将 list1 的当前节点连接到合并链表的尾部,即 current->next = list1。

移动 list1 指针到下一个节点,即 list1 = list1->next。

否则:

将 list2 的当前节点连接到合并链表的尾部,即 current->next = list2。

移动 list2 指针到下一个节点,即 list2 = list2->next。

指针更新:无论进入哪个分支,都需要将 current 移动到当前链表的末尾:current = current->next。

因为两个链表可能长度不同,其中一个会先到达末尾 (nullptr)。

直接将剩余的链表连接到合并链表的末尾,完成操作。

方法二:递归

cpp 复制代码
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
       // 如果其中一个链表为空,直接返回另一个链表
        if (list1 == nullptr) return list2;
        if (list2 == nullptr) return list1;
 
        // 比较当前节点的值,选择较小的节点作为当前结果
        if (list1->val <= list2->val) {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        } else {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

递归逻辑

比较 list1 和 list2 当前节点的值:

如果 list1->val <= list2->val,说明 list1 的当前节点应出现在结果链表中:

递归调用 mergeTwoLists 合并剩余的 list1->next 和 list2。

将 list1 的当前节点与递归返回的结果连接。

否则,list2 的当前节点应出现在结果链表中:

递归调用 mergeTwoLists 合并 list1 和 list2->next。

将 list2 的当前节点与递归返回的结果连接。

返回结果每次递归都返回较小节点的指针,从而逐步构建合并后的链表。

方法三:原地修改迭代

cpp 复制代码
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
      if (list1 == nullptr) return list2;
      if (list2 == nullptr) return list1;
 
        ListNode* head = (list1->val <= list2->val) ? list1 : list2;
        if (head == list1) list1 = list1->next;
        else list2 = list2->next;
 
        ListNode* current = head;
 
        while (list1 != nullptr && list2 != nullptr) {
            if (list1->val <= list2->val) {
                current->next = list1;
                list1 = list1->next;
            } else {
                current->next = list2;
                list2 = list2->next;
            }
            current = current->next;
        }
 
        current->next = (list1 != nullptr) ? list1 : list2;
 
        return head;
    }
};

比较 list1 和 list2 当前节点的值:

如果 list1->val <= list2->val,将 list1 当前节点接到结果链表,并移动 list1。

否则,将 list2 当前节点接到结果链表,并移动 list2。

同时移动结果链表指针 current,保持末尾指向最新节点。

循环结束时,至少有一个链表到达末尾 (nullptr)。

直接将剩余的链表连接到结果链表末尾。

2.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

方法一:这是一个经典的双指针问题,要求在原地修改数组。我们可以使用两个指针,分别表示当前遍历的元素位置和下一个非零元素应该放置的位置。

cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
         int nonZeroIndex = 0; 
        
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] != 0) {
                nums[nonZeroIndex++] = nums[i];
            }
        }
        
        for (int i = nonZeroIndex; i < nums.size(); ++i) {
            nums[i] = 0;
        }
    }
};

方法二:通过一个指针 lastNonZeroFoundAt 记录下一个非零元素应该交换到的位置。

遍历数组:如果当前元素是非零元素,与 lastNonZeroFoundAt 位置的元素交换。然后将 lastNonZeroFoundAt 向前移动一位。

cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int lastNonZeroFoundAt = 0; // 记录下一个非零元素应该交换到的位置
        
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] != 0) {
                // 交换当前非零元素与 lastNonZeroFoundAt 位置的元素
                std::swap(nums[lastNonZeroFoundAt], nums[i]);
                lastNonZeroFoundAt++;
            }
        }
    }
};

方法三:可以尝试通过双指针交换法来解决这个问题。这种方法只需遍历一次数组,通过交换操作将非零元素移到前面,零元素移到后面。

cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int j = 0; // 指向下一个非零元素应该交换的位置
 
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] != 0) {
                if (i != j) {
                    std::swap(nums[i], nums[j]);
                }
                ++j; // 非零位置前移
            }
        }
    }
};

3.相交链表

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

图示两个链表在节点 c1 开始相交**:**

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0

listA - 第一个链表

listB - 第二个链表

skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数

skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数

评测系统将根据这些输入创建链式数据结构,并将两个头节点 headAheadB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3

输出:Intersected at '8'

解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。

从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。

在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

--- 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。。

输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1

输出:Intersected at '2'

解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。

从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。

在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2

输出:No intersection

解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。

由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。

这两个链表不相交,因此返回 null 。

解法如下:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (!headA || !headB) return nullptr;
        ListNode *pA = headA;
        ListNode *pB = headB;
        while (pA != pB) {
            pA = (pA == nullptr) ? headB : pA->next;
            pB = (pB == nullptr) ? headA : pB->next;
        }
        return pA;
    }
};

算法步骤:

初始化两个指针 pA 和 pB,分别指向链表 headA 和 headB。

遍历链表:

如果 pA 到达链表末尾,则将其指向链表 headB 的头节点。

如果 pB 到达链表末尾,则将其指向链表 headA 的头节点。

如果 pA 和 pB 在某一节点相遇,则该节点即为交点。

如果两者都遍历完链表且没有交点,则两者最终会同时为 null。

返回相交节点或 null。

4.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

方法一:迭代法

cpp 复制代码
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;  // 前一个节点
        ListNode* curr = head;    // 当前节点
 
        while (curr != nullptr) {
            ListNode* next = curr->next;  // 保存下一个节点
            curr->next = prev;           // 反转当前节点的指针
            prev = curr;                 // 移动 prev 到当前节点
            curr = next;                 // 移动 curr 到下一个节点
        }
 
        return prev;  // 返回新的头节点
    }
};

算法步骤:

初始化两个指针:

prev 指向 nullptr,表示当前节点的前驱。

curr 指向链表头节点 head。

遍历链表:

保存当前节点的下一个节点 next。

将当前节点的 next 指向 prev,完成反转。

移动 prev 和 curr 指针到下一个节点。

当 curr 为 nullptr 时,链表已反转,prev 即为新链表的头节点。

返回 prev。

方法二:递归法

cpp 复制代码
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 递归终止条件
        if (head == nullptr || head->next == nullptr) {
            return head;
        }
 
        // 反转剩余链表
        ListNode* reversedHead = reverseList(head->next);
 
        // 调整当前节点的指针
        head->next->next = head;
        head->next = nullptr;
 
        return reversedHead;  // 返回新的头节点
    }
};

算法步骤:

递归终止条件:当前节点为 nullptr 或下一个节点为 nullptr(即到达链表尾部)。

递归反转剩余链表:

让新链表的头节点 reversedHead 指向 head->next 反转后的链表头。

调整指针:

让 head->next->next 指向 head,将当前节点追加到反转链表尾部。

设置 head->next = nullptr,断开当前节点和其余节点的连接。

返回 reversedHead。

5.回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false

回文序列;回文 序列是向前和向后读都相同的序列。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (!head || !head->next) return true; // 如果链表为空或只有一个节点,则是回文
 
        // Step 1: 找到链表的中点
        ListNode* slow = head;
        ListNode* fast = head;
        
        // 使用快慢指针找到链表的中点
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
 
        // Step 2: 反转链表的后半部分
        ListNode* prev = nullptr;
        ListNode* curr = slow;
        while (curr) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
 
        // Step 3: 比较前半部分和反转后的后半部分
        ListNode* left = head;
        ListNode* right = prev; // prev是反转后的后半部分的头
        while (right) {
            if (left->val != right->val) {
                return false; // 如果不相等,说明不是回文链表
            }
            left = left->next;
            right = right->next;
        }
 
        return true; // 如果所有值都相等,返回 true
    }
};

快慢指针:fast 指针每次走两步,slow 指针每次走一步。当 fast 指针走到链表末尾时,slow 指针正好在链表的中间位置。

反转后半部分链表:从 slow 指针开始,我们通过迭代将链表的后半部分反转,使其指向前一节点。

比较前后两部分:通过遍历链表的前半部分和反转后的后半部分,逐个比较它们的值,如果有不相等的地方,说明链表不是回文的。

空间复杂度:这段代码的空间复杂度是 O(1),因为我们只使用了常量级别的额外空间(除了输入链表本身)。

时间复杂度:时间复杂度是 O(n),其中 n 是链表的节点数。我们遍历链表两次:一次是找到中点并反转后半部分,另一次是比较两个部分。边界情况:

空链表或者只有一个节点的链表是回文的,直接返回 true。

链表的长度为 2 的时候,只需要比较两个元素是否相等。

相关推荐
weixin_7499499033 分钟前
双向列表的实现(C++)
开发语言·c++·链表
NineData36 分钟前
NineData云原生智能数据管理平台新功能发布|2024年12月版
数据库·sql·算法·云原生·oracle·devops·ninedata
bachelores1 小时前
数据结构-栈、队列和数组
数据结构·算法
xianwu5431 小时前
反向代理模块开发,
linux·开发语言·网络·c++·git
AQin10121 小时前
【Leetcode·中等·数组】59. 螺旋矩阵 II(spiral matrix ii)
算法·leetcode·矩阵·数组
好记性+烂笔头1 小时前
hot100_73. 矩阵置零
数据结构·算法·矩阵
M_Lin_1 小时前
文件IO
算法
robin_suli1 小时前
穷举vs暴搜vs深搜vs回溯vs剪枝系列一>字母大小写全排列
算法·剪枝·深度优先遍历·回溯
lzb_kkk1 小时前
【C++】JsonCpp库
开发语言·c++·json·1024程序员节
WeeJot嵌入式2 小时前
【数据结构】顺序表
数据结构