LeetCode 热题 100之链表1

1.相交链表


思路分析(直接上双指针):

  • 初始化两个指针,分别指向两个链表的头节点 headA 和 headB
  • 遍历两个链表,当指针到达链表的末尾时,将指针移动到另一个链表的头部
    • 如果链表相交,两个指针会在相交节点相遇
    • 如果没有相交,两个指针会在遍历完整个链表后都变成 None,最终返回 None。

具体实现代码(详解版):

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;

       //当指针相等时,表示找到了相交节点,或者两个指针都为nullptr
       while(pA != pB){
        //当指针到达末尾时,重定向到另一个链表的头部
         pA = pA ? pA->next : headB;//不为空则指向下一个节点,为空指向另一个链表头
         pB = pB ? pB->next : headA;
       }

       return pA;// 返回相交节点或 nullptr

        
    }
};

2.反转链表

思路分析1(迭代法):通过迭代的方法,我们可以使用三个指针来反转链表。

  • 初始化三个指针:
    • prev指向当前节点的前一个节点,初始化为nullptr;
    • current指向当前节点,初始为头节点head;
    • next用于保存当前节点的下一个节点,防止丢失。
  • 遍历链表:
    • 每一步中,保存当前节点的下一个节点;
    • 将当前节点的next指针指向前一个节点
    • 移动指针,继续反转

具体实现代码(详解版):

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* current = head;  // 当前节点

        while (current) {
            ListNode* next = current->next; // 保存下一个节点
            current->next = prev;            // 反转当前节点的指针
            prev = current;                  // 移动 prev 到当前节点
            current = next;                  // 移动到下一个节点
        }

        return prev; // prev 成为新的头节点
    }
};

思路分析2(递归无敌法):

  • 递归基本情况:如果head为空或只有一个节点,直接返回head
  • 递归步骤:反转后面的链表并将当前节点的 next 指针指向 head,同时将当前节点的 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* reverseList(ListNode* head) {
        //链表为空或者只有一个元素
        if(!head || !head -> next) return head;
        
        //递归反转链表
        ListNode* newHead = reverseList(head->next);

        //反转当前指针
        head->next->next = head;
        head->next = nullptr;

        return newHead;//返回新的头节点
    }
};

3.回文链表


思路分析1(赋值+头尾比较)

  • 遍历链表,将每个节点的值存储到vals中;
  • 比较元素
    • 使用两个指针i和j,分别指向vals数组的开始和结束,在for循环中,依次比较vals[i]和vals[j]的值
    • 如果发现任何不相等的元素,立即返回false,说明链表不是回文;
    • 如果所有元素都匹配,返回true,说明链表是回文。

具体实现代码(详解版):

cpp 复制代码
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        vector<int> vals; // 用于存储链表中的节点值
        
        // 1. 遍历链表并将节点值存储到数组中
        while (head != nullptr) {
            vals.emplace_back(head->val); // 将当前节点值加入数组
            head = head->next; // 移动到下一个节点
        }

        // 2. 比较数组的前半部分和后半部分
        for (int i = 0, j = (int)vals.size() - 1; i < j; ++i, --j) {
            if (vals[i] != vals[j]) {
                return false; // 如果不相等,则不是回文
            }
        }
        return true; // 所有值都匹配,则是回文链表
    }
};

思路分析2(使用栈):利用栈的特性来存储链表的前半部分,然后在遍历后半部分时与栈中的值进行比较

具体实现代码(详解版):

cpp 复制代码
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (!head || !head->next) {
            return true; // 空链表或只有一个节点是回文
        }

        std::stack<int> s;
        ListNode* current = head;

        // 1. 将前半部分的节点值压入栈中
        while (current) {
            s.push(current->val);
            current = current->next;
        }

        // 2. 比较栈中的值与链表中的值
        current = head;
        while (current) {
            if (s.top() != current->val) {
                return false;
            }
            s.pop();
            current = current->next;
        }

        return true; // 如果所有值都匹配,则是回文链表
    }
};

以上两种方法的时间复杂度和空间复杂度都是 O ( n ) . O(n). O(n).下面利用快慢指针法,以空间复杂度 O ( 1 ) O(1) O(1)实现回文链表的判断。

思路分析3(快慢指针):

  • 使用快慢指针找到链表的中间节点:快指针 fast 每次移动两个节点,慢指针 slow 每次移动一个节点。当快指针到达链表末尾时,慢指针位于链表的中间位置
  • 反转链表的后半部分:从中间节点开始,反转后半部分的链表,使其变为从尾部到中间的逆序链表。
  • 比较前半部分和反转后的后半部分:用两个指针从头部和反转后的中间开始向后比较,若有不相等的值则不是回文链表。

具体实现代码(详解版):

cpp 复制代码
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(!head || !head->next) return true;

        //1.使用快慢指针找到中间节点
        ListNode *slow = head,*fast = head;
        while(fast && fast->next){
            slow = slow->next;
            fast = fast->next->next;
        }

        //2.反转后半部分链表
        ListNode *prev = nullptr;
        while(slow){
            ListNode *nextNode = slow->next;
            slow->next = prev;
            prev = slow;
            slow = nextNode;
        }
        //比较前半部分和反转后的后半部分
        ListNode *left = head;
        ListNode *right = prev;//prev现在是反转后的链表头
        while(right){//只需比较后半部分的节点数
            if(left->val != right->val) return false;

            left = left->next;
            right = right->next;

        }

        return true;//如果没有发现不相等的节点,则回文链表
    }
};

4.环形列表


思路分析1(哈希表):我们可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。

具体实现代码(详解版):

cpp 复制代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> visited;

        while (head != nullptr) {
            // 检查当前节点是否已访问过
            if (visited.count(head)) {
                return true; // 存在环
            }
            visited.insert(head); // 将当前节点标记为已访问
            head = head->next;
        }

        return false; // 没有环
    }
};

思路分析2(快慢指针):

  • 快慢指针:slow 每次前进一步,而 fast 每次前进两步。如果 fast 和 slow 在某一时刻相遇,说明链表有环。
  • 退出条件:如果 fast 或 fast->next 为 nullptr,则链表没有环。
  • 这样实现的时间复杂度为 O(n),空间复杂度为 O(1),是判断链表是否有环的标准解法。
cpp 复制代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if (!head || !head->next) return false;

        ListNode *slow = head;
        ListNode *fast = head->next;

        while (fast != slow) {
            if (!fast || !fast->next) return false;
            slow = slow->next;
            fast = fast->next->next;
        }

        return true;  // fast和slow相遇表示有环
    }
};
相关推荐
A懿轩A19 分钟前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
古希腊掌管学习的神20 分钟前
[搜广推]王树森推荐系统——矩阵补充&最近邻查找
python·算法·机器学习·矩阵
云边有个稻草人24 分钟前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
半盏茶香24 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
忘梓.1 小时前
解锁动态规划的奥秘:从零到精通的创新思维解析(3)
算法·动态规划
tinker在coding3 小时前
Coding Caprice - Linked-List 1
算法·leetcode
XH华8 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生8 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_8 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子9 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘