众所周知,C语言有一个非常让人抓狂的玩意那就是指针。
先前我们创造数组的时候,一直使用了vector来代替数组,有了迭代器也不需要指针了,可以说总算在数组层面把这烦人的玩意给抛之脑后了。
但是现在马上要刷链表和二叉树了,它来了它来了,我们最讨厌的指针它又回来了。
(链表和二叉树机考大概率是不会考的,面试有概率会面)
为啥链表和二叉树需要用指针
得益于链表和二叉树在C++中的独特结构(next,left和right指向的是地址),一旦设计到地址相关了,那么指针就必须得出现了。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
}
};
对于这么一个函数,传进来的是一个链表的指针,而使用->就可以直接访问指针结构体里面的值,非常方便(所以推荐大家使用本地IDE编辑器,因为在leetcode上,你输入一个“.”的时候,它不会自动帮你变成->!)
head->val;
head->next;
怎样创建一个链表节点或者二叉树节点
方法1:虽然题目用的是指针,但我就要自己选择不用指针,使用值
// 这里的2和3是初始给节点赋值,不重要
ListNode node1(2);
ListNode node2(3);
node1.next = &node2;
这种写法其实并不是很推荐,因为一会是用值的,一会是用指针的,格式看起来会非常混乱,所以还是推荐统一使用指针。
ListNode *node1;
但创建指针的时候我们又遇到了一个问题,这玩意毕竟只是一个指针,声明完了之后也只是一个野指针,并没有实体的内存让我们指过去。你要是先创造一个值然后再用指针指过去当我没说。
这时候就会想到C语言之中有一个叫malloc的玩意,是用来给指针申请地址的。对malloc没印象也没关系,不影响。
现在我们需要给创造好的指针一个内存,在C++中,我们可以使用new来申请一个地址,使用例如下:
ListNode *node1 = new ListNode(2);
ListNode *node2 = new ListNode(3);
node1->next = node2;
释放内存?
如果学过C语言会知道,再malloc完一个堆区地址之后,需要使用free函数将这个地址给释放。同理,在C++的new中有一个类似的,使用完new之后也需要使用delete来释放内存,不然这个内存就会被永久占用。
但是。
你现在是在刷题,只要能实现功能就行,释放不释放内存不重要,所以请无视“使用new申请完内存必须使用delete释放掉”这条规矩。
链表题附加
好了,现在你可以快乐去刷链表中的题目了。
为了防止代码随想录中链表的题目不够,我在这边附上一些:
82. 删除排序链表中的重复元素 II - 力扣(LeetCode)
83. 删除排序链表中的重复元素 - 力扣(LeetCode)
链表总结
链表题的核心难点在于:
1.做着做着可能会搞蒙这个next是啥,下一个next是啥,谁指向了我我又指向了谁
2.做着做着就把某一个节点给丢了再也找不到它了,访问一个next就莫名其妙变成了空指针然后报错
而链表题也使用了很多双指针法,双指针法的主要用处是:
1.找中点,设置快慢指针,快指针每次移动两位,慢指针每次移动一位
2.找倒数第n个点,快指针先移动n次,然后和慢指针一起移动,等快快指针移动到nullptr了说明慢指针也移动到倒数第n个点了。
链表题鸡贼做法
之前我个人认为链表在机考中大概率不会考,是因为链表题有个很鸡贼的做法,这个鸡贼法能做大部分链表题(除了环形链表、相交链表之类的)
那就是拿一个vector<ListNode*>数组去把链表的所有节点给存起来,然后当数组做。
这样就直接解决了“做着做着就把某一个节点给丢了再也找不到它了”这个问题。
vector<ListNode *> vec;
for (ListNode *ptr = head; ptr; ptr = ptr->next) {
vec.push_back(ptr);
}
比如说我要做206翻转链表。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head) return nullptr;
vector<ListNode *> nodes;
for (ListNode *ptr = head; ptr != nullptr; ptr = ptr->next) {
nodes.push_back(ptr);
}
nodes[0]->next = nullptr;
for (int i = 1; i < nodes.size(); i++) {
nodes[i]->next = nodes[i - 1];
}
return nodes.back();
}
};
再比如说我要做148排序链表,正常做法需要先完成:有序链表合并、然后归并排序。
但是鸡贼一点就自己定义一个sort把它给完成了。
class Solution {
public:
class cmp{
public:
bool operator()(ListNode *a, ListNode *b) {
return a->val < b->val;
}
};
ListNode* sortList(ListNode* head) {
if (!head) return nullptr;
vector<ListNode*> nodes;
for (ListNode *ptr = head; ptr != nullptr; ptr = ptr->next) {
nodes.push_back(ptr);
}
sort(nodes.begin(), nodes.end(), cmp());
for (int i = 1; i < nodes.size(); i++) {
nodes[i - 1]->next = nodes[i];
}
nodes[nodes.size() - 1]->next = nullptr;
return nodes[0];
}
};
当然,这里并不提倡大家使用这种方法来做链表的题目,因为这样就违背了链表题的初衷