203.移除链表元素
思路:
因为列表为单向列表,在做删除操作时需要向后看,否则删除该节点后无法访问前一个节点进行链表的重新连接。遍历到a时判断是否产出b, 如果产出b:
a->b->c : a->c
a->next = b->next
如果不删除b,则进行简单遍历
a = a->next
问题:
自己写的话要注意删除节点内存空间的释放。刚开始的时候以为head是一个指向第一个节点的不包含数据的节点指针,后面才发现题目中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) {
//先确定头节点
while(head != nullptr && head->val == val){
//delete head;
head = head->next;
}
ListNode* ptr = head;
//遍历所有有效结点
//当前ptr决定是否删除后一个节点
while(ptr != nullptr && ptr->next != nullptr) {
if (ptr->next->val == val){
ListNode* remove = ptr->next;
ptr->next = remove->next;
//delete remove;
}
else {
ptr = ptr->next;
}
}
return head;
}
};
#707.设计链表
单链表版本
思路:
单链表只有后驱方向,所以将head设计为Node0的前驱节点(head->next 指向Node0,而不是head指向Node0)比较方便插入和删除,尾部插入时将新节点置于最后一个节点,所以tail指向最后一个实体节点即可。此外,需要注意哪些操作会改变head和tail。每次遍历都是从head开始,所以每次head都会被考虑到,只需要考虑哪些情况需要改变tail:
- add_at_head时候链表为空,此时需要更新tail为插入的Node0
- add_at_tail
- add_at_index or delete_at_index 操作最后一个Node
问题:
1.如何对head这种不指向实体节点的指针进行初始化?
2. 在类定义中定义了NodeList* head,又在类的初始化中定义了NodeList* head = new NodeList()导致内存不对齐
3.Add_at_tail 中只记得将新节点接入链表:
tail->next = newNode; 忘记了tail自身的更新:
tail = tail->next;
易忘知识点总结:
结构体初始化格式:
struct node{
int data;
string str;
char x;
//注意构造函数最后这里没有分号!
node() :x(), str(), data(){} //无参数的构造函数数组初始化时调用
node(int a, string b, char c) :data(a), str(b), x(c){}
}N[10];
class MyLinkedList {
private:
struct NodeList{
int val;
NodeList* next;
// NodeList(NodeList* ptr): next(ptr){}
NodeList(int v, NodeList* ptr): val(v), next(ptr) {}
};
NodeList* head;
NodeList* tail;
int size;
public:
MyLinkedList() {
//head:头节点的前驱指针
//head->next : Node0
//tail: 指向最后一个节点
//add at tail 新节点插入tail->next位置
//已经初始化过不要再初始化
// NodeList* head = new NodeList(-1, nullptr);
// NodeList* tail = head;
head = new NodeList(-1, nullptr);
tail = head;
size = 0;
}
int get(int index) {
if (index < 0 || index >= size) return -1;
NodeList* ptr = head->next;
for(int i = 0; i < index; ++i){
//if (ptr == nullptr) return -1;
ptr = ptr->next;
}
return ptr->val;
}
void addAtHead(int val) {
NodeList* newNode = new NodeList(val,head->next);
head->next = newNode;
//链表为空时既影响head又影响tail
if (size == 0) {
tail = newNode;
}
++size;
}
void addAtTail(int val) {
NodeList* newNode = new NodeList(val,nullptr);
tail->next = newNode;
//tail的移动给忘了
tail = tail->next;
++size;
}
void addAtIndex(int index, int val) {
if (index < 0 || index > size) return;
NodeList* ptr = head;
for (int i = 0; i < index; ++i){
ptr = ptr->next;
}
NodeList* newNode = new NodeList(val, ptr->next);
ptr->next = newNode;
//影响head,为第一个节点,ptr从head遍历起包括该情况
//影响tail,尾部插入,当index == size时
if(index == size){
tail = newNode;
}
++ size;
}
void deleteAtIndex(int index) {
if (index< 0 || index >= size) return;
NodeList* ptr = head;
for (int i = 0; i < index; ++i){
ptr = ptr->next;
}
NodeList* remove = ptr->next;
ptr->next = remove->next;
// delete remove;
if (size == index + 1) {
tail = ptr;
}
--size;
}
};
/**
* 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);
*/
双向链表
思路
跟单链表类似,不过链接链表时要同时考虑前驱和后驱的关系,只需要注意连接顺序,比如在先赋值head->next->prev再赋值head->next
问题
head,tail初始化过程中:
head = new NodeList(-1, nullptr, tail);
tail = new NodeList(-1, head, nullptr);
head初始化时用了初始化的变量tail,这种“小错误”很容易被忽略,而且很难在自己debug过程发现,需要多加注意。
class MyLinkedList {
private:
struct NodeList{
int val;
NodeList* prev;
NodeList* next;
NodeList(NodeList* ptr_prev, NodeList* ptr_next): prev(ptr_prev), next(ptr_next){}
NodeList(int v, NodeList* ptr_prev, NodeList* ptr_next): val(v), prev(ptr_prev), next(ptr_next) {}
};
NodeList* head;
NodeList* tail;
int size;
public:
MyLinkedList() {
//head:头节点的前驱指针
//tail: 尾节点的后驱节点
// tail 还没初始化!!!!!!!!
// head = new NodeList(-1, nullptr, tail);
// tail = new NodeList(-1, head, nullptr);
// head = new NodeList(-1, nullptr, nullptr);
// tail = new NodeList(-1, nullptr, nullptr);
head = new NodeList(nullptr, nullptr);
tail = new NodeList(nullptr, nullptr);
head->next = tail;
tail->prev = head;
size = 0;
}
int get(int index) {
if (index < 0 || index >= size) return -1;
NodeList* ptr = head->next;
for(int i = 0; i < index; ++i){
//if (ptr == nullptr) return -1;
ptr = ptr->next;
}
return ptr->val;
}
void addAtHead(int val) {
NodeList* newNode = new NodeList(val, head, head->next);
head->next->prev = newNode;
head->next = newNode;
++size;
}
void addAtTail(int val) {
NodeList* newNode = new NodeList(val, tail->prev, tail);
tail->prev->next = newNode;
tail->prev = newNode;
//tail的移动给忘了
++size;
}
void addAtIndex(int index, int val) {
if (index < 0 || index > size) return;
NodeList* ptr = head;
for (int i = 0; i < index; ++i){
ptr = ptr->next;
}
NodeList* newNode = new NodeList(val, ptr, ptr->next);
ptr->next->prev = newNode;
ptr->next = newNode;
++ size;
}
void deleteAtIndex(int index) {
if (index< 0 || index >= size) return;
NodeList* ptr = head;
for (int i = 0; i < index; ++i){
ptr = ptr->next;
}
NodeList* remove = ptr->next;
remove->prev->next = remove->next;
remove->next->prev = remove->prev;
// delete remove;
--size;
}
};
206. 反转链表
思路
思路非常简单,反转链表的过程其实就是模拟了一个入栈出栈的过程,直接调用STL的stack即可完成
问题
- 反转完之后的链表的头节点发生了变化,我们需要重新赋值,这里我们用counter来判断哪个哪个是头节点。
- stack.pop()是个void返回类型的函数,需要栈顶元素要用stack.top()
class Solution {
public:
//可以用stack实现
//是一个栈的过程,后进先出
ListNode* reverseList(ListNode* head) {
stack<ListNode*> s;
ListNode* ptr = head;
int count = 0;
while (ptr != nullptr){
s.push(ptr);
ptr = ptr->next;
}
while(!s.empty()){
ListNode* current = s.top();
//头节点
if (++count == 1) head = s.top();
s.pop();
//尾节点指向nullptr
current->next = s.empty() ? nullptr : s.top();
}
return head;
}
};