都说指针是 C 语言的灵魂,其实这是由几个重量级的数据结构决定的,如最基础却又最重要的:链表与二叉树两位元老,所有操作几乎都依赖指针。
可谓是:无指针者,无链表与二叉树也
想象一下,没有链表与二叉树,计算机世界将如何存在?
当然,数组的本质也是指针,但藏得较深,大家用脚标得过且过,倒也怡然自得。
若只论链表与二叉树,链表又更容易将指针指的出神入化,二叉树稍逊,一个 left, 一个 right 的二次元世界,弄不出什么花来。
所以想要把握指针的灵魂,练就一身弹"指"神通的俊功夫,还得多练练链表。
下面,我就随意截取几道经典的链表问题,陪诸君练练手。(为简化问题,凸显实质,皆为单链表)
cpp
struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} };
链表的逆
1->2->3->4->5
^
root
想要逆序,最直接的想法,就是希望上图中的链表指向反过来。我们借用一个空指针 node 指向一个空节点:
1->2->3->4->5 | ListNode* reverse(ListNode *root) {
^ | ListNode *node = nullptr;
root | }
|
null |
^ |
node |
第一步,我们希望节点1从单链表中剥离,于是让其指向 node, 但我们不能因此而找不到链表索引,故需要一个额外的指针 next, 指向后续节点:
2->3->4->5 | ListNode* reverse(ListNode *root) {
^ | ListNode *node = nullptr;
root | ListNode *next = root->next; // next refer to 2
| root->next = node; // root point to node
1->null | node = root; // node refer to root(1)
^ | root = next; // root refer to next(2)
node | }
几个简单的指针转移,便将节点1反向的去指向了 node 节点。如法炮制的话,节点2, 节点3, 节点4, 节点5 都调转枪头,我们的目的便达到了。
cpp
ListNode* reverse(ListNode *root) { ListNode *node = nullptr; while (root) { ListNode *next = root->next; root->next = node; node = root; root = next; } return node; }
链表除重
1->1->2->2->3->4
^
head
cur
如果用一个指针 cur 来指向当前节点的话,出现重复的条件即为:cur->value == cur->next->value
,如上图中,1 与 1 是重复的。我们只要想办法去掉重复的那个 1 即可。
1->1->2->2->3->4 | if (cur->val == cur->next->val) {
^ ^ ^ | ListNode *next = cur->next->next;
cur next | delete cur->next;
| ^ | cur->next = next;
|_____| | }
这个思路简单,易懂,但这个问题却又是很多复杂问题的基础。还是需要注意的。
cpp
ListNode *removeDuplicates(ListNode *head) { if (head == nullptr) return head; for (ListNode *cur=head; cur->next; ) if (cur->val == cur->next->val) { ListNode *next = cur->next->next; delete cur->next; cur->next = next; } else { cur = cur->next; } return head; }
未完待续