C++ day3 链表理论基础 移除链表元素 设计链表 反转链表

题目1  203移除链表元素

题目链接 :移除链表元素

对题目的理解

首先有一个疑问,这个链表是默认是单链表吗?是的

已知链表头节点head和一个整数val,删除链表中与val相等的节点,返回新的头节点。

自己的思路

遍历链表中的各个节点的元素,比较每个节点元素与val的大小,如果相等,则将该元素所在节点的前一个节点的指针指向该节点的下一个节点,如此遍历下去,直到所有的节点元素都比较判断完成。

自己思路存在的问题

Q1:首先就是没有考虑头节点的情况与其他节点不同,没有分类讨论

Q2:还有就是自己的思想太简单了,一直想着遍历,对于单链表而言,是无法找寻上一个节点的,因为是单向的。

看视频之后的解法

/**
 * 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* removeElements(ListNode* head, int val) {
        //分两步考虑,头节点 非头节点
        //删除头节点,首先判断头节点是否为空,若为空,相当于对空指针进行操作,编译报错
        //注意这是一个循环遍历的过程[1,1,1],val=1,持续移除的过程
        while(head!=NULL&&head->val==val)//头节点不能为空,因为要取头节点的值
        {
            head = head -> next;
        }
       //删除非头节点
        ListNode* cur = head;  //这里需要定义头节点的类型,将cur指向头节点,如果指向当前要删除 
                               //的位置的话,前一个节点无法寻找
        while(cur!=NULL&&cur->next!=NULL)//因为要对cur->next进行操作,所以事先必须判断cur 
                                         不为空,cur->next也不能为空,因为cur->next与val进行 
                                          对比,如果为空,则是对空指针进行操作
        {
            if(cur->next->val==val)//如果出现相等的情况
            {
                cur->next = cur->next->next;
            }
            else  //如果不等的话,则cur继续指向下一个元素,继续进行while循环
            {
                cur = cur->next;
            }   
        }
    return head;  //头节点一直存在,所以返回头节点
    }
};

网站上直接使用原链表进行移除操作(思想相同)

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
/**
 * 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* removeElements(ListNode* head, int val) {
        //分两步考虑,头节点 非头节点
        //删除头节点,首先判断头节点是否为空,若为空,相当于对空指针进行操作,编译报错
        //注意这是一个循环遍历的过程[1,1,1],val=1,持续移除的过程
        while(head!=NULL&&head->val==val)
        {
            ListNode* tmp = head;//设定一个中间变量,最后直接删除这个tmp即可
            head = head ->next;  
            delete tmp;
        }
       //删除非头节点
        ListNode* cur = head;  //这里需要定义头节点的类型,将cur指向头节点,如果指向当前要删除的位置的话,前一个节点无法寻找
        while(cur!=NULL&&cur->next!=NULL)//因为要对cur->next进行操作,所以事先必须判断cur-next不为空
        {
            if(cur->next->val==val)//如果出现相等的情况
            {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            }
            else  //如果不等的话,则cur继续指向下一个元素,继续进行while循环
            {
                cur = cur->next;
            }   
        }
    return head;  //头节点一直存在,所以返回头节点
    }
};

虚拟节点

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
  • 其中的dummyhead->next = head 要好好理解一下,是dummyhead指向链表的头节点head,不是赋值操作,从而将dummyhead虚拟节点与整个链表联系起来。
/**
 * 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* removeElements(ListNode* head, int val) {
        ListNode *dummyhead = new ListNode(0);
        dummyhead->next = head;//这里不是简单的赋值操作,是dummyhead指向head
        //将dummyhead当作头节点,剩下的包括head在内的所有节点一样,这样就不用分类讨论了
        ListNode* cur = dummyhead;  //因为是单向链表,所以只能向后找,所以进行这样的赋值  注意对cur进行定义
        while(cur!=NULL&&cur->next!=NULL) //因为要对cur->next进行操作,所以需保证其不为空
        {
            if(cur->next->val==val)  //如果cur的下一个值与val相等,则进行操作,
            {
                cur->next = cur->next->next;
            }
            else
            {
                cur = cur->next; //如果cur的下一个值与val不相等,则将cur推至下一个节点
            }
        }
    return dummyhead->next;//这个头节点head(整个链表)是一直都没有动的
        
    }
};

网站上给出的对应的代码

/**
 * 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* removeElements(ListNode* head, int val) {
        ListNode *dummyhead = new ListNode(0);
        dummyhead->next = head;
        //将dummyhead当作头节点,剩下的包括head在内的所有节点一样,这样就不用分类讨论了
        ListNode* cur = dummyhead;  //因为是单向链表,所以只能向后找,所以进行这样的赋值  注意对cur进行定义
        while(cur->next!=NULL) //因为要对cur->next进行操作,所以需保证其不为空
        {
            if(cur->next->val==val)  //如果cur的下一个值与val相等,则进行操作,
            {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            }
            else
            {
                cur = cur->next; //如果cur的下一个值与val不相等,则将cur推至下一个节点
            }
        }
    return dummyhead->next;//这个头节点head(整个链表)是一直都没有动的
    """
    head = dummyhead->next;
    delete dummyhead;
    return head;//这个头节点head其实就是上dummyhead这三行有点多余有一行就够了
    """
        
        
    }
};

题目2 707设计链表

题目链接:设计链表

对题目的理解

共有1个类定义和5个函数定义

类:初始化

5个函数:

获取下标对应的节点值

将节点插入到头节点之前,使之成为新的头节点

将节点放到最后一个结点之后

将节点插入到中间某一个节点之前

删除某一个节点

虚拟节点

  • 时间复杂度: 涉及 index 的相关操作为 O(index), 其余为 O(1)
  • 空间复杂度: O(n)
class MyLinkedList {
public:
//定义链表节点结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val),next(nullptr){}
    };
    MyLinkedList() {
        dummyhead = new LinkedNode(0); //定义虚拟头节点
        _size = 0;
    }

    int get(int index) {
        //LinkedNode* dummyhead = new LinkedNode(0); //定义虚拟头节点  这一行不用,前面已经定义了
        if(index>(_size-1)||index<0)
        {
            return -1;
        }//头节点下标是0,尾节点下标是size-1
        // LinkedNode* cur = dummyhead;
        // while(index)
        // {
        //     cur = cur->next;
        //     index--;
        // }
        // return cur->next->val;  //这段代码也可以正常运行
        LinkedNode* cur = dummyhead->next;//因为题目中获取的是index下标对应的值
        while(index)
        {
            cur = cur->next;
            index--;
        }
        return cur->val;
    }

    void addAtHead(int val) {
        // LinkedNode* dummyhead = new LinkedNode(0); //定义虚拟头节点 前面已经定义了
        LinkedNode* newnode = new LinkedNode(val);
        // LinkedNode* cur = dummyhead;//像这种,可以直接使用dummyhead做运算
        // newnode->next = cur->next;
        // cur->next = newnode;
        // _size++;//这个函数的类型是void所以不返回任何东西 这段代码也可以正常运行
        newnode->next = dummyhead->next;
        dummyhead->next = newnode;
        _size++;
    }
    
    void addAtTail(int val) {
        // LinkedNode* dummyhead = new LinkedNode(0); //定义虚拟头节点 前面已经定义了不需要
        
        LinkedNode* newnode = new LinkedNode(val);
        LinkedNode *cur = dummyhead;
        while(cur->next!=nullptr){
            cur = cur->next;
        }//如果cur->next==null才指向最后一个节点,那就对其进行操作,
        // while(cur->next!=NULL)/
        // {
        //     cur = cur->next;
        // }
        // //也可以使用while(cur->next->val!=Null)
        cur->next = newnode;
        _size++;
    }
    
    void addAtIndex(int index, int val) {
        // LinkedNode* dummyhead = new LinkedNode(0); //定义虚拟头节点不需要,前面已经定义了
       
        if(index>_size) return;
        if(index<0)  return;
        LinkedNode* cur = dummyhead;
        LinkedNode* newnode = new LinkedNode(val);
        while(index){
            cur = cur->next;
            index--;
        }
        newnode->next = cur->next;
        cur->next = newnode;
        _size++;
    }

    void deleteAtIndex(int index) {
        // LinkedNode* dummyhead = new LinkedNode(0); //定义虚拟头节点  不需要这一行,前面已经定义
        
        if(index>=_size||_size<0)
        {
            return;
        }
        LinkedNode* cur = dummyhead;
        while(index)
        {
            cur = cur->next;
            index--;
        }
        //cur->next = cur->next->next;
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;//delete命令指示释放了tmp指针原本所指的那部分内存,
        //被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
        //如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
        //如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
        tmp = nullptr;
        _size--;
    }
       void printLinkedList() {
        LinkedNode* cur = dummyhead;
        while (cur->next != nullptr) {
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << endl;
    }
private:
    int _size;
    LinkedNode* dummyhead;
};

/**
 * 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);
 */
 // 打印链表
 

本题有很多需要注意的点:

①获取下标对应的节点值 

将cur=dummyhead->next就可,因为是一个直接取值的操作,不涉及前面的节点。

②将节点插入到头节点之前,使之成为新的头节点

设定一个虚拟节点,令cur=dummyhead,cur->next=head,因为要在头节点前面插入一个节点,所以需要用到头节点前面的虚拟节点,所以这样设置,如果cur=head的话,无法找到前面的虚拟节点。

③将节点放到最后一个结点之后

cur=dummyhead,判断条件是,cur->next!=NULL,  若不满足,即·当cur->next为空时,说明cur到了最后一个节点,再将cur->next=newnode即可

④将节点插入到中间某一个节点之前

首先要执行①F->next=D,然后②C->next=F,这样才会成功,如果两者顺序颠倒,那么就会导致D节点先消失,找不到该节点,最终无法插入。

令cur=C,cur->next=D,因为这个操作,关系到节点的前一个节点,所以要这样设置,如果将D赋值给cur的话,最终找不到C节点,无法完成插入操作。

⑤删除某一个节点

与添加某一个节点相似,同样要知道删除的节点所在的前一个节点,那么删除的这个节点设置为cur->next,其前一个节点是cur,使得cur->next=cur->next->next即可

题目3  206反转链表

题目链接:反转链表

对题目的理解

将链表“倒置”

双指针法(★)

定义一个cur指向head,pre位于head的前面,,在反转链表中cur指向pre,即将pre赋值给cur->next,temp临时指针,代表cur之后的那个节点。

首先确定循环的范围,直到cur=NULL,整个链表才遍历完成,

一定要先c后d,如果调换顺序的话,那么cur和pre的内容一样,是无效的

首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。

然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。

为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。

接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。

最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
/**
 * 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* cur = head;
        ListNode* pre = NULL;
        ListNode* temp ;
        while(cur)
        {
            temp = cur->next;
            cur ->next = pre;
            pre = cur;
            cur = temp;
        } 
        return pre;

    }
};

递归法

  • 时间复杂度: O(n), 要递归处理链表的每个节点
  • 空间复杂度: O(n), 递归调用了 n 层栈空间
/**
 * 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* reverse(ListNode* pre,ListNode* cur)
    {
        if(cur== NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
       return reverse(NULL,head);
    }
};


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值