链表的定义
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
不定义构造函数行不行?答案是可以的,C++默认生成一个构造函数,但是这个构造函数不会初始化任何成员变量。
通过自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);
使用默认构造函数初始化节点:
ListNode* head = new ListNode();
head->val = 5;
所以如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!
链表的存储方式
数组是在内存中是连续分布的,但是链表在内存中并不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
性能分析
把链表的特性和数组的特性进行一个对比,如图所示:
203.移除链表元素
解法一: 一般解法,头节点和非头节点分开操作
注意:
1、移除头节点也可能是持续移除,因此用while循环,不用if
2、临时指针cur要从head开始,才可能把head的下一个删了
3、第二个while循环的循环条件不需要判断next->next是否为空,因为为空是可以的,链表的最后一个结点的next本来就是空(画两个节点就知道了)
4、最后return head即可得到整个链表
5、要学会清理内存
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
/*
1、采用一般解法,头节点和非头节点分开操作
2、头节点可能需要连续删除,因此用while循环,不用if
3、非头节点的删除则创建临时指针cur操作
要学会清理内存!!!
*/
while(head != NULL && head->val == val){
ListNode* tmp = head;
head = head->next;
delete tmp;
}
ListNode* cur = head;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
return head;
}
};
解法二: 虚拟头节点(统一操作头节点和非头节点)
注意:
1、链表中节点的创建方式:ListNode* dummy_head = new ListNode(0);
2、只有开辟新节点的时候才需要new!cur啊tmp啊只是指针而已,所以不用new
3、虚拟头节点也可以在最后释放掉
自己写的时候出现的问题:
没有new节点,而是直接写的ListNode* dummy_head;
导致了以下错误
正确代码如下:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
/*
1、采用虚拟头节点法,统一操作
2、new一个虚拟头节点(咋new?
3、
*/
ListNode* dummy_head = new ListNode(0);
dummy_head->next = head;
ListNode* cur = dummy_head;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
//return dummy_head->next;
head = dummy_head->next;
delete dummy_head;
return head;
}
};
707.设计链表
思路: 采用虚拟头节点进行设计
注意:
1、不要忽视了链表节点的创建定义!记得写上,原题的模板没有给!
2、用类初始化链表,新建虚拟头节点以及初始化链表大小size
3、要对index
进行有效性判断,以防止是负数和超出链表size
的情况
自己写的时候出现的问题:
1、忽视了链表节点的创建定义
2、不会初始化链表
3、对类的使用不熟悉(在private里设置属性dummy_head
和size
)
4、缺少对index的有效判断
5、addAtTail()
函数中错用size--
,导致其他地方逻辑错误,用cur->next != nullptr
即可
收获:
真正尝试了用本地IDE去cout打日志,找出问题,有时候光看是真看不出来,在此感谢@卡哥助手_围城,没有直接丢给我答案,而是和我说去cout打印日志就明白了,不然我绝不可能自己动手hhh
class MyLinkedList {
public:
// 定义链表节点结构体
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int val) : val(val), next(nullptr) {}
};
// 初始化链表
MyLinkedList() {
dummy_head = new LinkedNode(0);
size = 0; //虚拟头节点不算在size里
}
int get(int index) {
/*
1、先判断index是否有效
2、有效后再根据index进行索引
*/
if(index < 0 || index > (size - 1)){
return -1;
}
LinkedNode* cur = dummy_head->next;
while(index--){
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* node = new LinkedNode(val);
node->next = dummy_head->next;
dummy_head->next = node;
size++;
}
void addAtTail(int val) {
LinkedNode* node = new LinkedNode(val);
LinkedNode* cur = dummy_head;
/* 此处用size--会把size的值改变,其他用到size的地方也就乱了(比如if(index <= size)这种),全部也就不合逻辑了
while(size--){
cout << "size = " << size << endl;
cur = cur->next;
}
*/
while(cur->next != nullptr){
//cout << "size = " << size << endl; 这句打开直接给你超时
cur = cur->next;
}
cur->next = node;
size++;
}
void addAtIndex(int index, int val) {
if(index < 0) index = 0;
if(index <= size){
LinkedNode* node = new LinkedNode(val);
LinkedNode* cur = dummy_head;
while(index--){
cur = cur->next;
}
node->next = cur->next;
cur->next = node;
size++;
}else if(index > size){
return;
}
}
void deleteAtIndex(int index) {
if(index < size && index >= 0){
LinkedNode* cur = dummy_head;
while(index--){
cur = cur->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
size--;
}else{
return;
}
}
//用于在本地IDE测试(测试了addAtTail函数里size--的问题)
void printLinkedList() {
LinkedNode* cur = dummy_head;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int size;
LinkedNode* dummy_head;
};
206.反转链表
解法一: 双指针法(pre 和 cur)以及用tmp暂存cur->next,防止失去位置信息
class Solution {
public:
ListNode* reverseList(ListNode* head) {
/*
1、双指针解法,cur和pre
2、边翻转边移动,while循环
3、利用tmp暂存cur->next,以防止丢失位置
*/
ListNode* cur = head;
ListNode* pre = NULL;
while(cur != NULL){
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
解法二: 递归法,和双指针法一一对应,建议先理解透双指针法再照着写
自己写的时候出现的问题: 对return的用法比较疑惑(虽然从最里面可以return到pre,但之后就没有return可用了,所以会停在倒数第二层,应该是这样,递归不会用打日志的方法验证…)
class Solution {
public:
ListNode* reverse(ListNode* cur, ListNode* pre){
if(cur == NULL) return pre;
ListNode* tmp = cur->next;
cur->next = pre;
//上面一次翻转已完成,于是进入下一次
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(tmp, cur);
}
ListNode* reverseList(ListNode* head) {
return reverse(head, NULL);
}
};