掌握链表:数据结构手撕面经(简单入门版 适合新手学习)

7 篇文章 1 订阅
3 篇文章 0 订阅

题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台海量技术面试题库,拥有算法、数据结构、系统设计等 1000+题目,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problemset/?topicSlugs=linked-list&page=1

 2.两数相加

2. 两数相加 - 力扣(LeetCode)https://leetcode.cn/problems/add-two-numbers/description/icon-default.png?t=N7T8https://leetcode.cn/problems/add-two-numbers/description/

思路:

  1. 初始化: 创建一个新的链表用于存储结果,使用一个变量carry来跟踪进位。
  2. 遍历两个链表: 同时遍历两个链表,将对应位置的数值相加,加上前一位置的进位。
  3. 处理进位: 计算每次相加后的和以及新的进位值。
  4. 处理多余的进位: 如果最后还有进位,需要在新链表的末尾添加一个节点。
  5. 返回结果链表的头部
#include<iostream>
#include<vector>
using namespace std;

struct ListNode
{
    //数据域
    int val;
    //指针域
    ListNode* next;
    //结点
    ListNode(int val)
    {
        this->val = val;
        next = nullptr;
    }
};

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    ListNode* dummyHead = new ListNode(0); // 创建一个虚结点,简化边界情况的处理
    ListNode* current = dummyHead; // 用来构建结果链表
    int carry = 0; // 进位初始化为0
    while (l1 != nullptr || l2 != nullptr) { // 当任一链表还有节点未处理时循环
        int x = (l1 != nullptr) ? l1->val : 0; // 获取l1当前节点的值,如果已经为空,则用0替代
        int y = (l2 != nullptr) ? l2->val : 0; // 获取l2当前节点的值,如果已经为空,则用0替代
        int sum = carry + x + y; // 将当前位的两个数字与进位相加
        carry = sum / 10; // 更新进位
        current->next = new ListNode(sum % 10); // 创建新节点存放当前位的结果(sum的个位数)
        current = current->next; // 移动指针到新创建的节点

        if (l1 != nullptr) l1 = l1->next; // 移动l1
        if (l2 != nullptr) l2 = l2->next; // 移动l2
    }
    if (carry > 0) { // 如果最后还有进位,添加一个新节点
        current->next = new ListNode(carry);
    }
    return dummyHead->next; // 返回哑结点的下一个节点,即结果链表的头节点
}

void printList(ListNode* head) {
    while (head != nullptr) {
        cout << head->val << " ";
        head = head->next;
    }
    cout << endl;
}

int main() {
    // 从键盘输入两个链表
    ListNode* l1 = nullptr;
    ListNode* l2 = nullptr;
    int val;
    cout << "输入第一个链表的节点值(输入-1结束):" << endl;
    while (true) {
        cin >> val;
        if (val == -1) break;
        if (l1 == nullptr) {
            l1 = new ListNode(val);
        } else {
            ListNode* temp = l1;
            while (temp->next != nullptr) {
                temp = temp->next;
            }
            temp->next = new ListNode(val);
        }
    }
    cout << "输入第二个链表的节点值(输入-1结束):" << endl;
    while (true) {
        cin >> val;
        if (val == -1) break;
        if (l2 == nullptr) {
            l2 = new ListNode(val);
        } else {
            ListNode* temp = l2;
            while (temp->next != nullptr) {
                temp = temp->next;
            }
            temp->next = new ListNode(val);
        }
    }

    // 调用 addTwoNumbers 函数计算结果链表
    ListNode* result = addTwoNumbers(l1, l2);
    // 输出结果链表的值
    cout << "结果链表的值为: ";
    printList(result);

    return 0;
}

删除中间节点

 思路:

  1. 复制下一节点的数据:将node->next(即下一个节点)的值复制到当前的节点中。这样,当前节点就拥有了下一个节点的数据。

  2. 更新指针:将当前节点的next指针指向下下个节点(node->next->next),这样当前节点就绕过了下一个节点。

  3. 删除下一节点:由于当前节点已经取代了下一个节点的数据,我们现在可以安全地删除下一个节点。

struct ListNode {
  int val;
  ListNode *next;
  ListNode(int x) : val(x), next(nullptr) {}
};

void deleteNode(ListNode* node) {
  ListNode* next_node = node->next;
  node->val = next_node->val;  // 将下一个节点的值复制到当前节点。
  node->next = next_node->next;  // 将当前节点的指针指向下下个节点。
  delete next_node;  // 删除下一个节点。
}

合并两个有序链表

思路:

  1. 创建虚拟头节点:一个虚拟(哑)节点可以作为结果链表的起始点,这样可以避免单独处理头节点为空的特殊情况。

  2. 比较节点值:比较两个链表的当前节点的值,将较小的节点连接到结果链表的末尾,并移动该链表的指针到下一节点。

  3. 遍历链表:重复这一过程,直到达到其中一个链表的末尾。

  4. 连接剩余节点:如果一个链表已经完全合并,而另一个链表还有剩余的节点,直接将这些节点连接到结果链表的末尾。

  5. 返回结果:返回虚拟头节点的下一个节点,即合并后的链表的头节点。

class Solution {
public:
    ListNode* trainningPlan(ListNode* l1, ListNode* l2) {
        ListNode*head=new ListNode();//创建虚头
        ListNode*tail=head;     
        while(l1&&l2)
        {
            if (l1->val < l2->val) 
            {
                tail->next = l1;
                l1 = l1->next;
            } 
            else 
            {
                tail->next = l2;
                l2 = l2->next;
            }
            tail = tail->next;
        }
        if (l1) {// 如果l1还有剩余节点,直接连接到合并链表的末尾
            tail->next = l1;
        }
        if (l2) { // 如果l2还有剩余节点,直接连接到合并链表的末尾
            tail->next = l2;
        }
          return head->next;
    }
};

链表中倒数第k个节点

LCR 140. 训练计划 IIicon-default.png?t=N7T8https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/

思路:

  1. 定义快慢双指针

    fast 指针:开始时指向链表的头节点 headslow 指针:同样开始时指向链表的头节点 head
  2. 初始化 fast 指针

    fast 指针向前移动 cnt 步。这样做的目的是创建一个从 fastslow 指针之间固定为 cnt 个节点的窗口。这一步是关键,因为它将允许我们定位到链表的倒数第 cnt 个节点。
  3. 同时移动 fastslow 指针

    fast 指针没有到达链表尾部(即 fast 不为 nullptr)时,同时将 fastslow 指针向链表的下一个节点移动。这样,当 fast 指针到达链表尾部时,slow 指针刚好位于倒数第 cnt 个节点。
  4. 返回结果

    函数返回 slow 指针,此时 slow 指向的就是链表的倒数第 cnt 个节点。
class Solution {
public:
    ListNode* trainingPlan(ListNode* head, int cnt) {
        ListNode*fast=head;
        ListNode*slow=head;
        for(int i=1;i<=cnt;i++)
            fast=fast->next;
        while(fast)
        {
            fast=fast->next;
            slow=slow->next;
        }
        return slow;
    }
};

链表的中间节点

思路:

  1. 边界条件处理

    如果链表为空(headnullptr),直接返回 nullptr
  2. 初始化指针

    fast 指针和 slow 指针都初始化为指向链表的头节点 head
  3. 遍历链表

    使用 while 循环来遍历链表,循环条件是 fastfast->next 均不为 nullptr。这个条件确保了快指针可以安全地前进两步而不会越界。在每次循环中,fast 指针向前移动两步(fast = fast->next->next),而 slow 指针向前移动一步(slow = slow->next)。
  4. 寻找中间节点

    fast 指针到达链表末尾(或超出末尾)时,循环结束,此时 slow 指针指向的就是链表的中间节点。如果链表长度是偶数,则返回的是中间两个节点中的第二个。
  5. 返回结果

    函数返回 slow 指针,即链表的中间节点。
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode*fast=head;
        ListNode*slow=head;
        while(fast&&fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
        }
       return slow;
    }
};

回文链表

思路:

  1. 快慢指针fast 指针每次移动两步,而 slow 指针每次移动一步。这样,当 fast 指针到达链表尾部时,slow 指针正好在链表中间。
  2. 反转链表:在寻找中点的同时,逐步将链表的前半部分反转,这样可以在后续步骤中直接进行前后部分的比较。
  3. 奇数长度链表处理:如果链表长度是奇数,fast 指针不会指向 NULL,此时 slow 指针需要向前移动一步,以跳过中间的节点。
  4. 比较前后部分:最后比较反转后的前半部分和原链表的后半部分是否相等。
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (!head || !head->next) return true;
        ListNode*fast=head,*slow=head;
        ListNode*temp=nullptr,*pre=nullptr;
        while(fast&&fast->next)
        {
            fast=fast->next->next;
            //反转前半部分
            temp=slow->next;
            slow->next=pre;
            pre=slow;
            slow=temp;
        }
        // 如果链表长度为奇数,跳过中间节点
        if(fast)slow=slow->next;
         // 比较前半部分和后半部分
        while (pre && slow) {
            if (pre->val != slow->val) return false;
            pre = pre->next;
            slow = slow->next;
        }
        return true;
    }
};

反转链表

206. 反转链表icon-default.png?t=N7T8https://leetcode.cn/problems/reverse-linked-list/

思路:

  1. 初始化三个指针

    • prev(前一个节点),初始值为 nullptr
    • curr(当前节点),初始值为链表的头节点 head
    • next(下一个节点),在迭代中动态更新。
  2. 遍历链表

    • 在每次迭代中,首先保存 curr->nextnext,因为修改 curr->next 之后会丢失对原链表中下一个节点的引用。
    • curr->next 指向 prev,完成对当前节点的反转。
    • 移动 prevcurr 指针:prev 移动到 currcurr 移动到 next
  3. 完成反转

    • curr 变为 nullptr,即达到链表末尾时,结束迭代。此时 prev 指针指向新链表的头节点。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head) return head;
        ListNode* prev = nullptr;
        ListNode* curr = head;
        ListNode* next = nullptr;

        while (curr != nullptr) {
            next = curr->next;  // 保存当前节点的下一个节点
            curr->next = prev;  // 反转当前节点的指向
            prev = curr;        // 移动prev和curr指针
            curr = next;
        }
        return prev;  // prev现在指向新的头节点
    }
};

移除链表元素

203. 移除链表元素icon-default.png?t=N7T8https://leetcode.cn/problems/remove-linked-list-elements/

思路:

  1. 创建虚拟头节点

    首先创建一个虚头节点 HEAD,并将其指向原链表的头节点 head 前面。这样做的目的是为了处理删除链表头节点的情况,同时简化删除节点时的操作。
  2. 初始化指针

    初始化两个指针 fastslow,分别指向原链表的头节点 head 和虚拟头节点 HEAD
  3. 遍历链表

    使用 fast 指针遍历整个链表,直到 fast 指向 nullptr,即遍历完整个链表。在遍历过程中,判断当前节点的值是否等于目标值 val
  4. 删除节点

    如果当前节点的值等于目标值,则将 slow 指针的 next 指向当前节点的下一个节点,相当于删除了当前节点。否则,将 slow 指针移动到下一个节点。
  5. 返回结果

    最后返回虚拟头节点 HEAD 的下一个节点,即删除节点后的链表头节点。
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if (!head) return head;
        ListNode* HEAD = new  ListNode(0, head);
        ListNode* fast = head, * slow = HEAD;
        while (fast)
        {
            if (fast->val == val)
                slow->next = fast->next;
            else
                slow = slow->next;
            fast = fast->next;
        }
        return HEAD->next;
    }
};

相交链表

160. 相交链表icon-default.png?t=N7T8https://leetcode.cn/problems/intersection-of-two-linked-lists/

思路:

  1. 初始化指针

    初始化两个指针 lalb,分别指向两个链表的头节点 headAheadB
  2. 遍历链表

    使用一个循环来遍历链表,循环条件是 la != lb,即当两个指针相遇时结束循环。在循环中,判断 la 是否为空,如果不为空,则将 la 指针移动到下一个节点;如果为空,则将 la 指针重新指向另一个链表的头节点 headB。同样地,判断 lb 是否为空,如果不为空,则将 lb 指针移动到下一个节点;如果为空,则将 lb 指针重新指向另一个链表的头节点 headA
  3. 返回结果

    循环结束后,返回 la 指针指向的节点,即为两个链表的交点。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode*la=headA,*lb=headB;
        while(la!=lb)
        {
            if(la)
                la=la->next;
            else
                la=headB;
            if(lb)
                lb=lb->next;
            else
                lb=headA;
        }
        return la;
    }
};

环形链表

141. 环形链表icon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle/

思路:

  1. 初始化快慢指针

    初始化两个指针 fastslow,初始都指向链表的头节点 head
  2. 遍历链表

    使用一个循环来遍历链表,循环条件是 fastfast->next 都不为空,即快指针能够走到链表尾部。在循环中,快指针 fast 每次走两步,慢指针 slow 每次走一步,这样它们之间的距离会逐渐拉近。
  3. 检测环

    在每次循环中,检查快指针 fast 是否等于慢指针 slow,如果相等,说明链表中存在环,返回 true。如果快指针 fast 走到了链表尾部(即 fastfast->next 为空),则说明链表中不存在环,返回 false

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode*fast=head,*slow=head;
        while(fast&&fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow)
                return true;
        }
        return false;
    }
};

删除链表中的重复元素

83. 删除排序链表中的重复元素icon-default.png?t=N7T8https://leetcode.cn/problems/remove-duplicates-from-sorted-list/

思路:

  1. 初始化指针

    初始化两个指针 slowfast,分别指向链表的头节点 head 和头节点的下一个节点。
  2. 遍历链表

    使用一个循环来遍历链表,循环条件是 fast 不为空。
    • 在循环中,比较 fast 指针指向的节点的值与 slow 指针指向的节点的值是否相等。如果相等,则说明出现了重复元素,将 slownext 指针指向 fast 的下一个节点,从而删除了重复元素。如果不相等,则将 slow 指针移动到下一个节点。
    • 不论是否删除了重复元素,都需要将 fast 指针移动到下一个节点。
  3. 返回结果

    循环结束后,返回原链表的头节点 head。因为链表已经被修改,所以头节点可能已经改变了,但是头节点不会重复。
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(!head) return head;
        ListNode*slow=head,*fast=head->next;
        while(fast)
        {
            if(fast->val==slow->val)
                slow->next=fast->next;
            else
            
                slow=slow->next;
            fast=fast->next;
        }
        return head;
    }
};

合并两个有序链表 

21. 合并两个有序链表icon-default.png?t=N7T8https://leetcode.cn/problems/merge-two-sorted-lists/

思路:

  1. 初始化指针:

    • 创建一个新的节点 Head 作为合并后链表的虚拟头节点。
    • 创建指针 Tail 并指向虚拟头节点 Head
  2. 遍历链表:

    • 使用 while 循环遍历两个链表 l1l2,只要它们都还有节点。
    • 在循环中,比较 l1l2 当前节点的值。
  3. 拼接节点:

    • 如果 l1 的值小于等于 l2 的值,将 Tailnext 指针指向 l1,并移动 l1 指针到下一个节点。
    • 否则,将 Tailnext 指针指向 l2,并移动 l2 指针到下一个节点。
  4. 移动指针:

    无论拼接了 l1 还是 l2 的节点,都将 Tail 移动到新添加的节点上。
  5. 拼接剩余节点:

    循环结束后,可能 l1l2 中还有剩余的节点,直接将剩余节点接在合并后链表的末尾。
  6. 返回结果:

    返回虚拟头节点 Head 的下一个节点,即合并后链表的头节点。
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode*Head=new ListNode();//创建虚头
        ListNode*Tail=Head;
        while(l1&&l2)
        {
            if(l1->val<=l2->val)
            {
                Tail->next=l1;
                l1=l1->next;
            }
            else
            {
                Tail->next=l2;
                l2=l2->next;
            }
            Tail=Tail->next;//无论拼谁都移动
        }
        //拼接剩余的
        if(l1) Tail->next=l1;
        if(l2) Tail->next=l2;
        return Head->next;
    }
};

剑指offer06.从头到尾打印

LCR 123. 图书整理 Iicon-default.png?t=N7T8https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/

思路:

        与反转链表思路相同只不过是把逆序得到的链表存在动态数组中返回。

class Solution {
public:
    vector<int> reverseBookList(ListNode* head) {
        if (!head) // 检查头指针是否为空
            return {};
        ListNode*cur=head,*pre=nullptr,*next=head->next;
        vector<int>res;// 用于存储逆序后的链表节点值
        // 将链表逆序
        while (cur) {
            next = cur->next; 
            cur->next = pre; 
            pre = cur; 
            cur = next; 
        }
        // 将逆序后的链表节点值添加到vector中
        cur = pre; 
        while (cur) {
            res.push_back(cur->val); // 将节点值添加到vector中
            cur = cur->next; // 移动到下一个节点
        }
        return res; // 返回包含逆序后的链表节点值的vector
    }
};

二进制链表转整数 

1290. 二进制链表转整数icon-default.png?t=N7T8https://leetcode.cn/problems/convert-binary-number-in-a-linked-list-to-integer/

思路:

  1. 链表逆序

    遍历链表,将链表逆序,即将每个节点的指针指向它的前一个节点。在遍历过程中,记录链表的前一个节点、当前节点和下一个节点。
  2. 二进制转十进制

    遍历逆序后的链表,将每个节点表示的二进制位转换为十进制值,并累加到一个变量中。在循环中,利用指数函数 pow 计算每个节点的值对应的十进制权重,然后乘以当前节点的值,累加到总和中。
class Solution {
public:
    int getDecimalValue(ListNode* head) {
        ListNode*cur=head,*pre=nullptr,*next=head->next;
        while(cur)
        {
            cur->next=pre;
            pre=cur;
            cur=next;
            if(next) next=next->next;
        }
        cur=pre;
        int sum=0;
        int i=0;
        while(cur)
        {
            sum+=cur->val*pow(2,i);
            i++;
            cur=cur->next;
        }
        return sum;
    }
};

         如果你对探索机器学习的无限可能性、掌握Python编程的技巧、以及玩转各种框架的技能充满了好奇心,那么恭喜你,你来对地方了!赶紧扫描下方二维码,加入我们的微信公众号吧!这里有最新的技术趋势、独家教程、精彩案例等着你,让我们一起探索未知的领域,开启编程之旅吧!🚀🌟

  • 37
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

了一li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值