Leetcode算法训练日记 | day03

一、设计链表

1.题目

Leetcode:第 707 题
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList 类:
MyLinkedList() 初始化 MyLinkedList 对象。
int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:
输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

2.解题思路

常规链表设计

3.实现代码

#include <iostream>
using namespace std;

class MyLinkedList {
public:
    // 定义链表节点结构体LinkedNode
    struct LinkedNode {
        int val; // 节点存储的值
        LinkedNode* next; // 指向下一个节点的指针
        // 构造函数,初始化节点的值和下一个节点指针
        LinkedNode(int val) : val(val), next(nullptr) {}
    };

    // 初始化链表的构造函数
    MyLinkedList() {
        _dummyHead = new LinkedNode(0); // 创建一个虚拟头节点,其值初始化为0
        _size = 0; // 初始化链表大小为0
    }

    // 获取链表中指定索引位置的值
    int get(int index) {
        if (index < 0 || index >(_size - 1)) { // 检查索引是否越界
            return -1; // 索引越界时返回-1
        }
        LinkedNode* cur = _dummyHead->next; // 从头节点开始遍历链表
        while (index--) { // 遍历到指定索引位置
            cur = cur->next;
        }
        return cur->val; // 返回找到的节点的值
    }

    // 在链表头部添加一个新节点
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val); // 创建新节点
        newNode->next = _dummyHead->next; // 新节点的下一个节点指向当前头节点的下一个节点
        _dummyHead->next = newNode; // 更新头节点的下一个节点为新节点
        _size++; // 链表大小加1
    }

    // 在链表尾部添加一个新节点
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val); // 创建新节点
        LinkedNode* cur = _dummyHead; // 从头节点开始遍历
        while (cur->next != nullptr) { // 遍历到链表的最后一个节点
            cur = cur->next;
        }
        cur->next = newNode; // 将最后一个节点的下一个节点指向新节点
        _size++; // 链表大小加1
    }

    // 在链表中指定索引位置插入一个新节点
    void addAtIndex(int index, int val) {
        if (index > _size) { // 索引大于链表大小时不插入
            return;
        }
        if (index < 0) { // 索引小于0时,插入到链表头部
            index = 0;
        }
        LinkedNode* newNode = new LinkedNode(val); // 创建新节点
        LinkedNode* cur = _dummyHead; // 从头节点开始遍历
        while (index--) { // 遍历到指定索引位置的前一个节点
            cur = cur->next;
        }
        newNode->next = cur->next; // 新节点的下一个节点指向当前节点的下一个节点
        cur->next = newNode; // 当前节点的下一个节点更新为新节点
        _size++; // 链表大小加1
    }

    // 从链表中删除指定索引位置的节点
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) { // 索引越界时不进行删除
            return;
        }
        LinkedNode* cur = _dummyHead; // 从头节点开始遍历
        while (index--) { // 遍历到指定索引位置的前一个节点
            cur = cur->next;
        }
        LinkedNode* temp = cur->next; // 保存需要删除的节点
        cur->next = cur->next->next; // 跳过需要删除的节点,实现删除操作
        delete temp; // 释放被删除节点的内存
        temp = nullptr; // 将指针置空,避免悬挂指针
        _size--; // 链表大小减1
    }

    // 打印链表中的所有节点值
    void printLinkedList() {
        LinkedNode* cur = _dummyHead; // 从头节点开始遍历
        while (cur->next != nullptr) { // 遍历链表
            cout << cur->next->val << "--->"; // 打印节点值和连接符
            cur = cur->next;
        }
        cout << endl; 
    }

private:
    int _size; // 链表的大小
    LinkedNode* _dummyHead; // 虚拟头节点指针
};

//测试
int main()
{
    MyLinkedList s;
    s.addAtTail(1);
    s.addAtTail(2);
    s.addAtTail(3);
    s.addAtHead(4);
    s.addAtHead(5);
    s.addAtHead(6);
    s.addAtIndex(3,99);
    s.deleteAtIndex(2);
    cout << "链表:";
    s.printLinkedList();
    cout<<"请输入需要获的结点的下标:";
    int val;
    cin >> val;
    cout << "结点下标为"<<val<<"的数值是";
    cout <<s.get(val)<<endl;
    return 0;
}

二、移除链表元素

1.题目

Leetcode:第 209 题

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

2.解题思路

用cur遍历链表,如果找到要删除的节点(值为val),临时保存要删除的节点,更新cur的下一个节点,跳过当前要删除的节点,释放要删除节点的内存,即可完成节点的删除。

3.实现代码
class Solution {
public:
    // 定义链表节点结构体ListNode
    struct ListNode {
        int val; // 节点存储的值
        ListNode* next; // 指向下一个节点的指针
        // 构造函数,初始化节点的值和下一个节点指针
        ListNode(int val) : val(val), next(nullptr) {}
    };

    // removeElements函数用于移除链表中所有值为val的节点
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0); // 创建一个虚拟头节点,其值初始化为0
        dummyHead->next = head; // 虚拟头节点的下一个节点指向原链表的头节点
        ListNode* cur = dummyHead; // 定义当前节点cur,初值为虚拟头节点
        while (cur->next != nullptr) { // 遍历链表,直到遇到nullptr
            if (cur->next->val == val) { // 如果找到要删除的节点(值为val)
                ListNode* temp = cur->next; // 临时保存要删除的节点
                cur->next = cur->next->next; // 更新cur的下一个节点,跳过当前要删除的节点
                delete temp; // 释放要删除节点的内存
            }
            else { // 如果当前节点的下一个节点不是要删除的节点
                cur = cur->next; // 将cur往后移动,继续遍历链表
            }
        }
        // 更新头节点,因为链表的头节点可能已经发生变化
        head = dummyHead->next;
        // 释放虚拟头节点的内存,因为它不再被使用
        delete dummyHead;
        return head; // 返回更新后的链表头节点
    }
};

三、反转链表

1.题目

Leetcode:第 206 题

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

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]
2.解题思路

通过三个指针 prevcurrtemp 来完成链表的反转操作。prev 用于跟踪已反转部分的链表的尾部,curr 用于遍历原始链表,而 temp 用于临时保存下一个节点,以便在反转 currnext 指针后继续遍历。

3.实现代码
class Solution {
public:
    // reverseList函数用于反转一个链表
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = NULL; // 初始化prev为nullptr,它将记录待翻转的节点的前驱节点
        ListNode* curr = head; // 初始化curr为head,它将记录当前需要翻转的链表节点
        while (curr != NULL) { // 遍历整条链表,直到curr为nullptr
            // 临时保存当前节点的下一个节点,因为即将改变curr的next指针
            ListNode* temp = curr->next;
            // 将当前节点的下一节点指向它的前驱节点,完成翻转
            curr->next = prev;
            // 移动prev到当前节点,因为prev现在指向了新的上一节点
            prev = curr;
            // 同理,移动curr到下一个节点(即原来的下一个节点temp),即可继续下一轮翻转
            curr = temp;
        }
        // 循环结束时,curr是nullptr,prev到达了原链表的尾节点,它现在是新的头节点
        return prev; // 返回新的头节点
    }
};

ps:以上皆是本人在探索算法世界旅途中的浅薄见解,诚挚地希望得到各位的宝贵意见与悉心指导,若有不足或谬误之处,还请多多指教。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值