首页 > 其他分享 >代码随想录打卡 Day 2

代码随想录打卡 Day 2

时间:2025-01-01 17:18:29浏览次数:6  
标签:ListNode cur 随想录 fast Day 链表 打卡 next 节点

代码随想录打卡 Day 2

1.链表的定义与操作

链表作为基本的数据结构类型,其主要形式有三种:

  • 单链表

  • 双链表

  • 循环链表

由于刷代码题平时在OJ上默认定义已经给出,可以直接使用。而在面试/机试时,一般需要需要自己手写链表。因此,正确写出链表的定义是非常有必要的。一个单链表的定义如下所示:

struct ListNode{
	int val;
	ListNode *next;
	ListNode(int x):val(x),next(nullptr){} //节点的构造函数
};

其中,注意到,第三行给出了一个构造函数,通过这个构造函数,我们可以对一个新节点的val变量直接赋值。

链表的基本操作包括了:

  • 删除节点
  • 添加节点
  • 查询链表内元素(包括按照val值和索引值)

相比较数组,链表的长度可以是不固定的,并且可以动态增删,适合数据量不固定,频繁增删,较少查询的场景。

2.链表常见的6个操作

leetcode题号:707.设计链表

【题目描述】

设计一个链表类,实现六个接口:

  1. 获取链表第index个节点的值(按索引查询)
  2. 在链表的最前面插入一个节点(头插法)
  3. 在链表的最后面插入一个节点(尾插法)
  4. 在链表的第index个节点插入一个节点(按索引插入)
  5. 删除链表的第index个节点(按索引删除)
  6. 打印当前链表

【题目分析】

该题是练习链表操作的一道非常好的题目,六个结构覆盖了链表的常见操作,其代码如下:

class MyLinkedList {
public:
    struct Node {
            int val;
            Node* next;
            Node(int x) : val(x), next(NULL) {}
        };

private:
    Node* _dummyhead; //设置一个伪头指针,方便后续操作
    int _size;        // _size表示链表元素个数

    MyLinkedList() { //构造函数
        _dummyhead = new Node(0);
        _size = 0;
    }

    int get(int index) {   //按索引查询元素
            if(index > _size -1 || index < 0)
                return -1;

            Node* cur=_dummyhead->next;

            while (index--){
                cur=cur->next;
            }
             return cur->val;
        }

    void addAtHead(int val){  //头插法
            Node* newnode = new Node(val);
            newnode->next = _dummyhead->next;
            _dummyhead->next = newnode;
            _size++;
        }

    void addAtTail(int val) {  //尾插法
        Node* newnode =new Node(val);
        Node* cur=_dummyhead;
        while (cur->next != NULL){
            cur=cur->next;
            }
        cur->next=newnode;
        _size++;
        }
	
    void addAtIndex(int index, int val) {  //按索引插入
        if(index>_size) return;

        if(index<0) index=0;

        Node* newnode = new Node(val);
        Node* cur=_dummyhead;

        while(index--){
            cur=cur->next;

        }
        newnode->next=cur->next;
        cur->next=newnode;
        _size++;
    }
    
    void deleteAtIndex(int index) {  //按索引删除
        if(index>=_size || index<0) return;

        Node* cur=_dummyhead;
        while(index--){
            cur=cur->next;
        }
        Node* tmp=cur->next;
        cur->next=cur->next->next;
        delete tmp;
        tmp=nullptr;
        _size--;
    }
	
        void printList(){	//打印链表
        Node* cur=_dummyhead;
        while (cur->next != NULL){
            cout<<cur->next->val<<" ";
            cur=cur->next;
        }
        cout<<endl;
    }
};

3.利用伪头节点(dummy_Head)来简化操作

在单链表中,删除头节点与删除其他节点的操作不一样,除了共同的释放内存和指针移动之外,还需要移动头指针。通过设置一个伪头节点,最后将dummy_Head->next设置为新的头节点即可。

leetcode203题移除链表元素为例,设置伪头节点可以方便处理待处理元素为头节点的情况。

本题的代码如下:

ListNode* removeElements(ListNode* head, int val) {
    ListNode* dummy = new ListNode(-1);  //伪头节点的设置
    dummy->next = head;
    ListNode* cur = dummy;
    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 = dummy->next; //设置新的头节点
    delete dummy;  //删除伪头节点
    return head;   
}

4.链表的反转

leetcode题号:206.反转列表

【题目描述】

反转一个单链表,要求不能申请额外的内存空间

【题目分析】

反转链表是链表操作中的一个重要思路,这里使用双指针法和递归法来实现。对于双指针发,只需要改变next指针的指向,不需要重新定义一个新的链表。

双指针法

双指针法的思想是定义一个cur指针和prev指针,分别进行遍历,并且使用nxt指针保存下一个指针,便于遍历。

双指针法的代码如下所示:

ListNode* reverseList(ListNode *head) {
    if (head == NULL || head->next == NULL) {  //对链表只有至多一个元素的边界条件处理
        return head;
    }
    ListNode *p = head->next;
    ListNode *prev = head;
    ListNode *nxt= NULL; //nct指针保存p所指的下一个结点
    while(p!=NULL){ //遍历
        nxt = p->next;
        p->next = prev;
        prev = p;
        p = nxt;
    }

    return  prev;
}
递归法

递归法的思路实质上与双指针法的思路一致,但是递归法的代码较为抽象,比较难写。代码如下所示:

ListNode* reverse(ListNode * pre, ListNode * cur) {
    if (cur == NULL) return pre;
    ListNode * nxt = cur->next;
    cur->next = pre;
    //如下递归的算法,实质上完成了:
    //pre=cur;
    //cur=temp;
    return reverse(cur, nxt);
}

ListNode* reverseList(ListNode *head) {
    //与双指针法的初始化逻辑一致
    return reverse(NULL, head);
}

5.双指针法在链表中的应用

1)删除倒数第N个节点

【基本思路】

如果要删除倒数第N个节点,可以先让快指针fast移动N步,然后让fastslow同时移动,删除slow所在的节点即可。

删除链表倒数第N个节点又应该使用伪头节点,让slow节点指向待删除节点前一个节点。因此,可以让fast先动一步,然后让fast移动N步,最后让fastslow同时移动一步。

基于以上思路,本题的代码如下所示:

ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* dummyHead = new ListNode(0, head);
    dummyHead->next = head; //伪头节点设置

    ListNode* fast = dummyHead;
    ListNode* slow = dummyHead;
    while(n-- && fast->next != nullptr) { //fast移动N步
        fast = fast->next;
    }
    fast = fast->next; //需要让slow指向要删除节点的前一个节点

    while(fast!=NULL){ //fast与slow同时移动一步

        fast = fast->next;
        slow = slow->next;
    }
    slow->next = slow->next->next;

    return dummyHead->next;
}

对快慢指针进行移动方法调整,就可以获得单链表的中间节点。

2)判断链表是否有环

【基本思路】

通过快慢指针法,分别定义fastslow指针,从头节点出发,fast移动两步,slow移动一步,若fastslow相遇,则说明有环,否则则无环。

但是,如果要寻找到环的入口,则是一个比较困难的问题。在这里,我们假设从头节点到环的入口的节点数为$x$,环形入口节点到fastslow节点相遇节点数为$y$。从相遇节点再到环形入口节点书为$z$.

那么,相遇时,slow走过了$(x+y)$个节点,fast走过了$2(x+y)=x+y+n*(y+z)$个节点,其中$n$为fast指针与slow相遇时已经走过的圈数。

通过化简,得到

$$x=(n-1)(y+z)+z$$,

$$x+y=n(y+z)$$

此时,可以在相遇节点处定义一个指针index1,在头节点处定义index2,让两个指针同时移动一步,两者相遇的位置即为环形入口的起点。

根据以上的思路,可以整理出本题的解

ListNode *detectCycle(ListNode *head) {
    ListNode *slow = head, *fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) { //找到环后的处理
            slow = head;
            while (slow != fast) {
                slow = slow->next;
                fast = fast->next;
            }
            return slow;
        }
    }
   return NULL;
}

3)判断两个链表的共同后缀

【基本思路】

两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。题目数据保证整个链式结构中不存在环。

本题可以通过先遍历A,B链表的长度,再使用双指针法进行操作。需要注意的是,交错链表并非数值相等,而是指针相等。

基于以上思考,可以给出如下的算法:

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if (headA == NULL || headB == NULL) {
        return NULL;
    }
    ListNode *pA = headA, *pB = headB;
    int lenA = 0, lenB = 0;
    while(pA!=NULL){
        lenA++;
        pA = pA->next;
    }
    
    while(pB!=NULL){
        lenB++;
        pB = pB->next;
    }
    pA = headA;pB = headB;

    if (lenB > lenA) {
        swap(lenA, lenB);
        swap(pA, pB);
    }//保证headA是长的,便于算法的求解

    int gap = lenA - lenB;//计算两者长度插值,为接下来使用快慢指针法做准备

    while (gap--){
        pA = pA->next;
    }

    while (pA != NULL) {
        if(pA == pB) return pA;
        pA = pA->next;
        pB = pB->next;
    }
    return NULL;


}

6. 总结

对于以单链表为基本数据结构的算法题,主要的技巧就是伪头节点,双指针,以及反转链表。

  • 伪头节点:其主要作用时简化链表的边界条件,简化操作,并保持链表结构的一致性。
  • 双指针法:通过快慢指针的方法,可以快速查找中间节点,倒数K个节点等特殊节点,以及探测链表中的环。
  • 反转链表:通过设置prev,curr,next三个指针,可以将链表进行反转操作,便于判断回文等操作的实现。

标签:ListNode,cur,随想录,fast,Day,链表,打卡,next,节点
From: https://www.cnblogs.com/kotoriinsky/p/18644833

相关文章

  • 【Java教程】Day15-16 多线程:线程同步——Java的原子操作类
    在Java中,除了常见的底层锁和并发集合类,java.util.concurrent 包还提供了一组专门用于原子操作的封装类,位于 java.util.concurrent.atomic 包。通过这些类,我们可以在多线程环境下安全地进行无锁操作,避免了传统锁的性能开销。今天我们就来详细了解其中一个常用的类:AtomicInt......
  • 【Java教程】Day14-01 加密与安全:从ASCII到Base64
    ​1.什么是编码?在计算机科学中,编码(Encoding)是将信息从一种格式转换成另一种格式的过程。在我们日常生活中,编码算法广泛应用于文本、文件和网络传输等领域。了解编码的基础知识是学习计算机编程与算法的第一步。1.1ASCII编码ASCII(AmericanStandardCodeforInformationI......
  • 【Java教程】Day11-07 时间与日期:日期与时间API的转换与数据库存储
    Java提供了两个日期与时间处理API:旧的 java.util.Date 和 java.util.Calendar,以及新的 java.time 包。新的API以 Instant、LocalDateTime 等为核心,具有更清晰的设计和更强大的功能。除非你需要与遗留代码进行交互,否则建议使用新的API。在需要将新旧API进行转换时,......
  • 打卡信奥刷题(523)用C++信奥P6861[普及组/提高] [RC-03] 难题
    [RC-03]难题题目描述求两个整数a,ba,ba,b(......
  • Linux第一课:c语言 学习记录day01
    0、大纲1、Linux命令2、基础内容:进制转换、词法符号、变量常量、输入输出3、语句:分支语句、循环语句、循环控制语句4、数组:一维数组、字符数组、排序、二维数组5、指针:一级指针、二级指针、指针和数组、指针数组、数组指针6、函数:函数基本用法、string函数族、开辟堆区空......
  • 八股day1——HashMap
    HashMap回答重点数组+链表+红黑树超过负载因子会*2扩容,扩容操作比较耗时尾插法,头插法在多线程中可能会形成回路,可以参考BV1n541177Ea红黑树优化当链表长度超过8时,链表会转变为红黑树,查找复杂度从O(n)降到O(logn),如果树中元素低于6,则转换回链表,减少不表的树操作开销hashC......
  • Day1算法刷题(数组板块)
    二分查找注意事项left<=right,等于不能漏掉,定位到同一个元素的时候也要判断和target是否相等left=mid+1,right=mid-1而不能是mid,否则会死循环,比如nums={1,2},target=2,此时mid就会一直指向1。补充:数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分......
  • [CEOI2010 day2] pin
    思路看到「恰好」触发被动了考虑套路转化,令\(f(k)\)表示「至少」有\(k\)个对应位置的字符不同的字符串对数套路的,令\(g(k)\)表示「恰好」有\(k\)个对应位置的字符不同的字符串对数\[f(k)=\sum_{i=k}^{n}{n\choosei}g(i)\iffg(k)=\sum_{i=k}^{n}(-1......
  • 苍穹外卖day06、07
    bug记录知识点记录HttpClientHttpClient是ApacheJakartaCommon下的子项目,可以用来提供高效的、最新的、高智能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。微信小程序开发缓存菜品问题:用户端小程序展示的菜品数据都是通过查询数据......
  • LeetCode算法题 (比较含退格的字符串)Day9!!!C/C++
    https://leetcode.cn/problems/backspace-string-compare/description/一、题目描述给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。注意:如果对空文本输入退格字符,文本继续为空。二、相关知识点了解   ......