代码随想录第3天 | 链表 203.移除链表元素,707.设计链表,206.反转链表

题目:203.移除链表元素

思路:

主要是头节点的删除问题,一种是直接在原链表上后移头节点
设置虚拟头结点,指向原链表的头结点,在设置一个cur指针指向当前节点,
虚拟头节点初始化后就不移动了,使用cur进行移动
不要忘记释放删除节点的内存,自行设置的虚拟头节点也要释放
时间复杂度: 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) {
        ListNode* Dummy=new ListNode(0);//设置虚拟头结点
        Dummy->next=head;               //
        ListNode* cur=Dummy;  
        while(cur->next!=NULL){
            if(cur->next->val==val){   //
                ListNode* tmp=cur->next;       //c++需要手动释放内存,释放被删除节点的内存空间
                cur->next=cur->next->next;
                delete tmp;
            }else{
                cur=cur->next;
            }
        }
        /*对于cur指针和Dummy指针的关系--> B站 bili_guest42 的解释如下:
          “指针之间的赋值是值传递的过程,cur和dummy开始时指向同一块节点内存,
            在cur没被重新赋值之前,cur的next变化就是dummy的next的变化, 所以dummy的next会指向最新的头节点。”    
        */
        head=Dummy->next;
        delete Dummy;
        return head;
    }
};

补充:

链表基本知识
一种线性结构,依靠指针进行连接,分为单链表、双向链表(可向前向后查询)和循环链表(首尾相连)
单链表,每个节点由一个数据域和指针域构成,指针指向下一个节点,尾节点指向null(空指针),第一个节点称为head 头节点。
双向链表,每个节点由一个数据域和两个指针域构成,两个指针分别指向前一个节点和后一个节点。
链表在内存中不是连续存储的,是散乱分布在内存中(具体分配机制取决于系统内存管理)
★链表的定义★
面试会考,要会

struct ListNode{
    int val;
    ListNode *next;  //指针是存储地址的变量,声明的类型是告诉编译器该如何解析所存储的地址的
    ListNode(int x) :val(x),next(NULL){}  /*节点的构造函数,c++系统有一个构默认造函数,
                                             但是初始化的时候不能给变量赋值了,所以自定义构造函数。*/
}

链表初始化
ListNode *phead=new ListNode(0);
ListNode *Dummy=phead->next;
ListNode *tmp=Dummy;

题目:707.设计链表

思路:

设计五个链表的基本操作

  1. index查找,获取链表中下标为 index 的节点的值
  2. 头节点前插入,将一个值为 val 的节点插入到链表中第一个元素之前。
  3. 尾节点后插入,将一个值为 val 的节点追加到链表中作为链表的最后一个元素
  4. index插入,将一个值为 val 的节点插入到链表中下标为 index 的节点之前。
  5. 删除index, 如果下标有效,则删除链表中下标为 index 的节点

为了实现 MyLinkedList 类,首先MyLinkedList() 初始化 MyLinkedList 对象
根据要求思考,因为涉及插入删除,所以 设置虚拟头节点 来方便操作
时间复杂度: 涉及 index 的相关操作为 O(index), 其余为 O(1)
空间复杂度: O(n)

坑:

分清cur指向的是 虚拟头结点 or 头结点

我要炸了,试图访问空指针 啊啊啊啊 就硬报错,进行本地调试
addAtTail 有问题!!!!!! 循环判断,cur->next=nullptr 就是到尾部节点了!! 不用for循环遍历len次
成功!!!!吗喽欢呼.gif

class MyLinkedList {
/*编译报错,先定义结构体ListNode,在定义dummyhead
	private:
		int len;//记录链表的长度
		ListNode *dummyhead;//链表虚拟头结点
	*/
private:
	struct ListNode {
		int val;
		ListNode* next;
		ListNode() :val(0), next(nullptr) {};//参数为空
		ListNode(int x) :val(x), next(nullptr) {}
	};
	ListNode* dummyhead;//链表虚拟头结点
	int len;//记录链表的长度

public:
	MyLinkedList() {
		dummyhead = new ListNode(-1); //创建链表虚拟头结点
		len = 0;   //链表初始长度为0  值不算虚拟头节点
	}

	int get(int index) {
		if (index<0 || index>len - 1)//错误 不包含虚拟头结点,所以还是len-1
			return -1;
		ListNode* cur = dummyhead->next; //错误  dummyhead是虚拟头结点,而dummyhead->next是头结点
		while (index--) {   //任意非零值时都为真
			cur = cur->next;
		}
		return cur->val;   //☆☆报错,试图使用空指针,
	}
	void addAtHead(int val) {
		ListNode* newNode = new ListNode(val); //构造函数含参,简化为new ListNode(val)
		newNode->next = dummyhead->next;
		dummyhead->next = newNode;
		len++;  //错误 忘记更新len
	}

	void addAtTail(int val) {
		ListNode* newNode = new ListNode(val);
		ListNode* cur = dummyhead;
		while(cur->next!=nullptr){ // ☆☆☆☆问题在这里!之前用的for循环<len-1次,当len为1时,
              cur=cur->next;                            //导致头结点直接被覆盖掉了
        }
		cur->next = newNode;
		len++;
	}

	void addAtIndex(int index, int val) {
		if (index > len) //错误  索引大于长度直接返回,不是len-1
			return; //错误,函数返回类型为void,不用返回-1
		if (index == len)
			addAtTail(val);
		else if (index <= 0)  //小于0 在头节点插入
			addAtHead(val);
		else {
			ListNode* newNode = new ListNode(val);
			ListNode* cur = dummyhead;
			while (index--) {
				cur = cur->next;
			}
			newNode->next = cur->next;
			cur->next = newNode;
			len++;
		}
	}

	void deleteAtIndex(int index) {
		if (index < 0 || index >= len)
			return;
		ListNode* cur = dummyhead;
		ListNode* tmp;  //保存删除的结点,便于释放
		while (index--) {
			cur = cur->next;
		}
		tmp = cur->next;
		cur->next = tmp->next;
		delete tmp;
		tmp = nullptr;
		//错误 被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
		//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
		len--;
	}
};

/**
 * 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);
 */

补充:

NULL和nullptr
编程指北关于NULL和nullptr的区别
C语言中NULL表示空指针,c++中NULL表示整数0,则在c++语言中使用NULL会产生二义性问题
c++11特性引入了nullptr表示空指针,来解决NULL的问题

在 C++11 及以后的代码中,建议使用 nullptr 代替 NULL 表示空指针。

题目:206.反转链表

思路:

设置虚拟头结点,采用头插法 ,white循环读取每个节点,在从头插入新链表,好的这种方法是对内存的浪费呜呜呜
随想录说直接翻转next
1.双指针法+一个临时指针
时间复杂度: O(n)
空间复杂度: O(1)
2.递归法
根据双指针 写递归法,更容易理解,
先明白递归的结束条件,在找循环部分
时间复杂度: 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* reverseList(ListNode* head) {
        ListNode* tmp;
        //ListNode* pre=head->next;  报错,试图访问空指针,
        ListNode* pre=head;     //指向需要反转的结点
        ListNode* cur=nullptr;  //指向新链表的头结点
        while(pre!=nullptr){
            tmp=pre->next;   //临时保存
            pre->next=cur;
            cur=pre;
            pre=tmp;
        }
        return cur;
    }
};

递归法

/**
 * 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(pre==nullptr) 
            return cur;
        else{    //这里没写else 不严谨
            ListNode* tmp;
            tmp=pre->next;
            pre->next=cur;
            return reverse(tmp,pre); //报错 没有写 return 没有返回值
        }
    }
    ListNode* reverseList(ListNode* head) {
        return reverse(head,nullptr);
    }
};

补充:

暂无

今日总结

学习了链表,设计链表,反转链表
理解链表的操作,
分清指向,虚拟头结点还是头结点
尝试写出递归,递归真简洁啊,当然前提是有这个脑子能想明白

posted @   跳圈  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示