链表
首先复习一下链表,并与数组做出对比。
- 常用的为单向链表,每个结点由两部分组成:一个是当前结点的值,另一个是指向下一个结点的指针
- 链表的操作:链表的查询的时间复杂度是O(n),而增删操作的时间复杂度为O(1),所以适合频繁增删的场景。
- C++中链表的定义:
ListNode(){
int val;
ListNode* next;
ListNode(int x):val(x) , next(NULL){}
};
LeetCode 203.移除链表元素
移除链表元素主要考虑到移除头节点时的特殊情况,所以使用虚拟头结点让处理头节点时不用单独处理,另一个需要注意的地方记得使用一个temp保存一下需要删除结点之后的结点。下面直接给出代码:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode* cur = dummyhead;
while(cur->next != NULL){
if(cur->next->val == val){
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
}else{
cur = cur->next;
}
}
return dummyhead->next;
}
LeetCode 707. 设计链表
设计链表一个是对代码的掌控能力,与之前模拟螺旋矩阵一样需要注意的细节比较多;另一个是对链表本身基础的掌控。
需要注意的点:
- 本题需要自己定义链表结构体并定义及初始化类成员变量
- 参考卡哥的代码,因为涉及删除的操作,所以在成员变量提前定义好一个虚拟头结点
- 其实想好index的值之后,本题难度不大
- 首先定义好一个链表,并初始化一个链表:
struct ListNode{
int val;
ListNode* next;
ListNode(int x):val(x),next(NULL) {}
};
MyLinkedList() {
dummyhead = new ListNode(0);
size = 0;
}
private:
ListNode* dummyhead;
int size;
- 编写单独的函数:
//获取index的值
int get(int index) {
if(index < 0 || index >= size) return -1;
ListNode* cur = dummyhead->next;
while(index--){
cur = cur->next;
}
return cur->val;
}
//在头添加
void addAtHead(int val) {
ListNode* newnode = new ListNode(val);
newnode->next = dummyhead->next;
dummyhead->next = newnode;
size++;
}
//在尾添加
void addAtTail(int val) {
ListNode* newnode = new ListNode(val);
ListNode* cur = dummyhead;
while(cur->next != NULL){
cur = cur->next;
}
cur->next = newnode;
size++;
}
//在指定index添加
void addAtIndex(int index, int val) {
if(index > size) return;
ListNode* newnode = new ListNode(val);
ListNode* cur = dummyhead;
while(index--){
cur = cur->next;
}
newnode->next = cur->next;
cur->next = newnode;
size++;
}
//在指定位置删除,包括头尾
void deleteAtIndex(int index) {
if(index < 0 || index >= size) return;
ListNode* cur = dummyhead;
while(index--){
cur = cur->next;
}
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
size--;
}
因为比较基础,所以熟练即可。
LeetCode 206. 反转链表
力扣上这题标的简单,我觉得其实不简单。两种方法:双指针和递归。
- 双指针法:使用双指针法时,定义一个
pre
和一个cur
,因为链表的特殊性,需要一个结点一个结点的处理。逻辑就是处理cur结点时,断开cur与下一个结点,并使cur的下一个结点指向pre。然后pre与cur同时向后移位。特别注意:pre与cur指向的结点只表示两者当前所在的结点,与他们指针域指向的结点无关,听起来有点绕,一开始想明白了双指针的逻辑,没想明白代码逻辑。
下面给出代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
ListNode* cur = head;
while(cur){
ListNode* temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
- 递归法:使用递归法时,一定要先确定递归函数的输入,以及在什么条件下跳出递归循环。递归的过程与双指针的过程一样,所以递归的输入就是pre和cur两个指针;当模拟过程到最后一次循环时,pre指向的是最后一个有值的结点,而cur指向的是一个空结点
NULL
,所以跳出递归的条件就是cur == NULL
并且返回pre结点。下面给出递归的函数:
ListNode* reverse(ListNode* pre , ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
return reverse(cur,temp);
}
递归函数中就不需要写像双指针法中指针移位的逻辑,直接将cur
与cur->next
的之间断开,然后递归。
下面就是主函数:
ListNode* reverseList(ListNode* head) {
return reverse(NULL, head);
}