B.D.S.代码随想录训练营day_3

今日任务:

链表理论基础 ;203.移除链表元素 ;707.设计链表 ;206.反转链表

Part_1 链表理论基础

1.链表节点的定义:

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) {}
};

        根据卡哥提示,由于力扣中已经给出如上节点定义,所以平时刷题时确实可能会忽略这部分。下面引用卡哥关于此话题的原文(感谢卡哥):

        “有同学说了,我不定义构造函数行不行,答案是可以的,C++默认生成一个构造函数。

但是这个构造函数不会初始化任何成员变量,下面我来举两个例子:

通过自己定义构造函数初始化节点:

ListNode* head = new ListNode(5);

使用默认构造函数初始化节点:

ListNode* head = new ListNode();
head->val = 5;

所以如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!”

        由于涉及本人还没有掌握的构造函数,这里将卡哥的原文直接搬过来以供学习。近期也在开始准备写一些C++_Primer第五版的学习笔记,掌握构造函数后再来还愿。

2.链表的基本操作(见part_3拓展)(后续遇到继续补充)

Part_2 力扣203.移除链表元素

题目描述:

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/remove-linked-list-elements

个人解答:

class Solution {
public:
	ListNode* removeElements(ListNode* head, int val) {
		while (head && head->val == val) {
			ListNode* tmp = head;
			head = head->next;
			delete tmp;
		}
		if (!head) return head;
		ListNode* p = head, * q = head->next;
		while (q) {
			if (q->val != val) {
				p = p->next;
				q = q->next;
			}
			else {
				ListNode* tmp = q;
				p->next = q->next;
				q = q->next;
				delete tmp;
			}
		}
		return head;
	}
};

(1)双指针法比较典型

(2)C/C++需要delete,根据卡哥提示,“使用C++来做leetcode,如果移除一个节点之后,没有手动在内存中删除这个节点,leetcode依然也是可以通过的,只不过,内存使用的空间大一些而已,但建议依然要养成手动清理内存的习惯。”

(3)此外,稍微值得一提的是前面的特殊情况分析。

while (head && head -> val == val) head = head->next;
if (!head) return head;

        正常思维顺序应该会先写出第二行,即判断空链表。然后由于双指针对于头元素的特殊性,判断并迭代删除头元素可能会造成链表为空,继而呈现如上两行代码。

(4)链表的解答大多包括是否使用虚拟头结点_dummyhead两种,个人解答为不使用,下面引用卡哥的使用虚拟节点的解答作为补充。

卡哥解答:(代码引用自力扣评论,感谢卡哥)

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
        dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
        ListNode* cur = dummyHead;
        while (cur->next != NULL) {
            if(cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

Part_3 力扣707.设计链表

题目描述:

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/design-linked-list
个人解答(包含拓展的函数,自行提取力扣所需):

#include<iostream>
using namespace std;

class MyLinkedList {
public:
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int x) : val(x), next(nullptr) {}
    };
    MyLinkedList() {
        _dummyHead = new LinkedNode(0); 
        _size = 0;
    }
    int get(int index) {
        if (index > (_size - 1) || index < 0) return -1;
        LinkedNode* p = _dummyHead->next;
        while (index--) p = p->next;
        return p->val;
    }

    void addAtHead(int val) {
        LinkedNode* p = new LinkedNode(val);
        p->next= _dummyHead->next;
        _dummyHead->next = p;
        ++_size;
    }

    void addAtTail(int val) {
        LinkedNode* q = new LinkedNode(val);
        LinkedNode* p = _dummyHead;    
        while (p->next)p = p->next;
        p->next = q;
        ++_size;
    }

    void addAtIndex(int index, int val) {
        if (index > _size || index < 0) return;
        LinkedNode* q = new LinkedNode(val);
        LinkedNode* p = _dummyHead;
        while (index--)p = p->next;
        q->next = p->next;
        p->next = q;
        ++_size;
    }

    void deleteAtIndex(int index) {
        if (index > (_size - 1) || index < 0) return ;
        LinkedNode* p = _dummyHead;
        while (index--)  p = p->next;
        LinkedNode* q = p->next;
        p->next = p->next->next;
        delete q;
        --_size;
    }

    void printList() {
        LinkedNode* p = _dummyHead->next;
        while (p) {
            cout << p->val<<' ';
            p = p->next;
        }
        cout << endl;
    }

    void reverseList() {
        LinkedNode* head = _dummyHead->next;
        if (!head || !head->next) printList();
        else if (!head->next->next) {
            LinkedNode* p = _dummyHead->next->next;
            p->next = _dummyHead->next;
            _dummyHead->next->next = NULL;
            _dummyHead->next = p;
            printList();
        }
        else {
            LinkedNode* p = head, * q = p->next, * r = q->next;
            p->next = NULL;
            while (r) {
                q->next = p;
                p = q;
                q = r;
                r = r->next;
            }
            _dummyHead->next = q;
            _dummyHead->next->next = p;
            printList();
        }
    }
    
private:
    int _size;
    LinkedNode* _dummyHead;
};

int main() {
    MyLinkedList* obj = new MyLinkedList();
    obj->addAtTail(1);
    obj->addAtTail(2);
    obj->addAtTail(3);
    obj->addAtTail(4);
    obj->addAtTail(5);
    obj->printList();
    obj->reverseList();
}

//Your MyLinkedList object will be instantiated and called as such:
//MyLinkedList* obj = new MyLinkedList();
//int param_1 = obj->get(index);
//obj->addAtHead(val);
//obj->addAtTail(val);
//obj->addAtIndex(index,val);
//obj->deleteAtIndex(index);
 

代码结尾main()部分为使用测试。

此外,比较值得注意的是开始的节点位置,观察结果如下:

LinkedNode* p = _dummyHead;//一般用于双指针的slow指针
LinkedNode* p = _dummyHead->next;//一般用于单指针

        本题很大程度上满足了链表学习的实际应用,打破以往纯理论的算法学习方式,同时也可以了解C++面向对象的类模板,很适合学习。

Part_4 力扣206.反转链表

题目描述:

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/reverse-linked-list

个人解答:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
		if (!head||!head->next) return head;
		else if (!head->next->next) {
			head->next->next = head;
			head = head->next;
			head->next->next = NULL;
			return head;
		}
		else {
			ListNode* p = head, * q = p->next, * r = q->next;
            p->next=NULL;
			while (r) {
				q->next = p;
				p = q;
				q = r;
				r = r->next;
			}
			head = q;
			head->next = p;
			return head;
		}
    }
};

        为了致敬双指针法,这种方法暂且简称为三指针法。

(1)三指针法每次调换p与q之间的箭头指向,借用r指针实现位置移动的迭代。

(2)与双指针法可能需要单独讨论首尾类似,三指针法需要讨论链表总长不足三个节点的情况,结束条件需要选取r,即最快的指针,以免出现NULL->next的情况。

(3)结束while循环时,p对于倒数第二节点,q对应最后一个节点,r对应NULL,因此手动再将head对应q,head->next对应p

(4)细心的各位可能已经注意到翻转链表在part_3设计链表中已经作为拓展函数出现过,不过由于_dummyhead和head的定义差异,在拓展函数中做出了一些变量定义调整以适配_dummyhead,实际并无差异,且在part_3中实测可用。

(5)三指针法称不上是高效,在解答过程中本人也感觉到似乎可用省去一个指针,不过其思路易于理解,虽非首创,但却是自己独立构思实现的,而且确实有可取之处,已经可以满足本人刚刚入门以及初刷随想录和力扣的预期。

下面还是欣赏下卡哥的解答:

(1)双指针法(保存、翻转、更新):

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

(2)正向递归:

class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
            // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
            // pre = cur;
            // cur = temp;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
            // 和双指针法初始化是一样的逻辑
            // ListNode* cur = head;
            // ListNode* pre = NULL;
        return reverse(NULL, head);
    }
};

        其中,保存、翻转同上双指针法,移位通过cur和tmp=cur->next实现

(3)反向递归:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 边缘条件判断
        if(head == NULL) return NULL;
        if (head->next == NULL) return head;
        
        // 递归调用,翻转第二个节点开始往后的链表
        ListNode *last = reverseList(head->next);
        // 翻转头节点与第二个节点的指向
        head->next->next = head;
        // 此时的 head 节点为尾节点,next 需要指向 NULL
        head->next = NULL;
        return last;
    }
}; 

        通过函数自己调用自己实现递归翻转

今日感悟:

        今天的三道链表题更多偏向对于理论的实现,并未涉及过多实际场景的应用背景,并且本人曾有过理论学习算法的经历(以前是完全理论学习,不会代码实现),因此今天完成时间终于稍早一些啦,而且今天的收获和完成质量也基本达到预期,很充实的一次打卡,继续加油!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

B.D.S.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值