代码随想录算法训练营第2天|LeetCode707.设计链表、LeetCode203.移除链表元素、LeetCode206.翻转链表
1、链表理论基础
定义
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
类型
- 单链表
指针域只能指向节点的下一个节点。 - 双链表
每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
既可以向前查询也可以向后查询。 - 循环链表
链首尾相连。
可以用来解决约瑟夫问题。
存储方式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
下面的链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。
手写定义
//单链表
struct ListNode{
int val;
ListNode *next;
ListNode(int v):val(v), next(NULL){}//节点的构造函数
};
//创建
ListNode* head = new ListNode(5);
链表的操作
- 删除节点
复杂度O(1)。
但是删除前需要先查找到这个元素,查找复杂度是O(n)。
- 添加节点
O(1)。
性能
2、LeetCode203.移除链表元素
题目链接:203. 移除链表元素 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:手把手带你学会操作链表 | LeetCode:203.移除链表元素_哔哩哔哩_bilibili
第一想法
定义一个虚拟头结点,查找元素,比较,若相等则删除。
代码
犯错:
- do-while的最后要有分号(因为不怎么写这个结构所以忘了);
- 释放内存
ListNode* removeElements(ListNode* head, int val) {
if(head==nullptr)
return nullptr;
ListNode* ans = new ListNode();//虚拟头节点,因为可能返回是空链表
ans->next = head;
ListNode* before = ans;
ListNode* current = head;
//遍历链表
do{
// cout<<"do:current->val="<<current->val<<endl;
if(current->val == val){
// cout<<"删除"<<endl;
//删除
ListNode* dele = current;
current = before->next = current->next;
delete dele;
}
else{
cout<<"else"<<endl;
before = before->next;
current = current->next;
}
}while(current);
return ans->next;
}
3、LeetCode707.设计链表
题目链接:707. 设计链表 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:帮你把链表操作学个通透!LeetCode:707.设计链表_哔哩哔哩_bilibili
实现功能
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
代码
犯错:
- 删除元素时考虑不周全:删除索引0的元素没被包含进去。
- print()的话会超时
技巧/总结:
- 遍历链表时,若要到达所给index前一个,
LinkedNode* cur = _dummy_head;
。
若要到达index,LinkedNode* cur = _dummy_head->next;
- 遍历链表时,
while(index--)
的写法能够更简洁,而不用额外使用变量cur_idx记录。
class MyLinkedList {
public:
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int v):val(v), next(nullptr) {}
};
MyLinkedList() {
//创建虚拟头节点
_dummy_head = new LinkedNode(0);
_size = 0;
// print();
}
int get(int index) {
print();
//不合法索引
if(index<0 || index >= _size)
return -1;
LinkedNode* current = _dummy_head->next;
int cur_idx=0;
//找到索引位置
while(cur_idx != index){
current = current->next;
cur_idx++;
}
return current->val;
}
void addAtHead(int val) {
LinkedNode* target = new LinkedNode(val);
target->next = _dummy_head->next;
_dummy_head->next = target;
_size++;
// print();
}
void addAtTail(int val) {
LinkedNode* target = new LinkedNode(val);
LinkedNode* cur = _dummy_head;
//找到最后一个位置
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = target;
_size++;
// print();
}
void addAtIndex(int index, int val) {
if(index == _size){
addAtTail(val);
return ;
}
if(index > _size)
return ;
if(index <=0 ){ //当index<0时,也插入到开头?为啥
addAtHead(val);
return ;
}
LinkedNode* target = new LinkedNode(val);
LinkedNode* cur = _dummy_head;
int cur_idx = -1;
//找到索引的前一位置
while(cur->next != nullptr){
cur = cur->next;
cur_idx++;
if(cur_idx == index-1){
target->next = cur->next;
cur->next = target;
_size++;
return ;
}
}
// print();
}
void deleteAtIndex(int index) {
if(index<0 || index >= _size )
return ;
LinkedNode* cur = _dummy_head;
int cur_idx = -1;
//找到索引的前一位置。注意删除第一个元素的情况
while(index --){
cur = cur->next;
}
LinkedNode* dele = cur->next;
cur->next = (cur->next)->next;
delete dele;
_size--;
return ;
// print();
}
void print(){
LinkedNode* cur = _dummy_head;
while(cur -> next != nullptr){
cout<<cur->next->val;
cur = cur->next;
}
cout<<endl;
}
private:
LinkedNode* _dummy_head;
int _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);
*/
4、LeetCode206.翻转链表
题目链接:206. 反转链表 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili
法1 双指针法pre, cur
思路
初始化pre为nullptr,cur为head,每次遍历保存好临时变量,并做好翻转cur->next = pre
。
代码
犯错:
- while中,一开始写的是cur->next != nullptr,这样当cur来到最后一个元素的时候,无法进入循环,这个元素就无法指到上一个元素
- temp保存的应该是cur的next
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = nullptr;
while(cur != nullptr){//注意这里不是cur->next
ListNode* temp = cur->next;//注意这里temp是保存cur->next
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
法2 递归法
与双指针的逻辑相同,代码更简洁。
对照着双指针的代码写
/**
* 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* cur, ListNode* pre){
if(cur == nullptr)//对应于while条件
return pre;
ListNode* temp = cur->next;
cur->next = pre;
return reverse(temp, cur);//对应于pre,cur往后移
}
ListNode* reverseList(ListNode* head) {
//法2 递归
return reverse(head, nullptr);//对应于pre,cur的初始化
}
};
法3 从后往前翻转(不是很理解)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 边缘条件判断
if(head == NULL) return NULL;
if (head->next == NULL) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode *last = reverseList(head->next);
// 翻转头节点与第二个节点的指向
head->next->next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head->next = NULL;
return last;
}
};
总结
- 使用虚拟头节点–代码会简便很多
- do-while结构
- 释放内存
- C++内置的链表用法——双向链表
- while(index–)
#include <iostream>
#include <list>
int main() {
std::list<int> myList;
// 插入元素
myList.push_back(1);
myList.push_back(2);
myList.push_back(3);
// 遍历链表
for (int value : myList) {
std::cout << value << " ";
}
std::cout << std::endl;
// 删除元素
myList.remove(2);
// 遍历链表
for (int value : myList) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}