C 语言,Indirect pointer (pointer to pointer)

前言

受到 你所不知道的 C 语言: linked list 和非连续记忆体 的启发,记录对于 Indirect Pointer 的学习历程,并尝试使用 Rust 实现此概念。

测试程式码: https://github.com/hank20010209/good-taste-linked-list

直观的 Linked List 实作

首先定义 Linked List 资料结构,定义节点本身以及 Linked List 本身

typedef struct Node {    int value;    struct Node *next;}Node;
typedef struct List {    Node *head;}List;

init_list

To init the Linked List

List *init_list(){    List *l = (List *)malloc(sizeof(List));    l->head = NULL;    return l;}

初始化 Linked List,并将初始化完成的 Linked List 回传

find_node

Find Node in Linked List
Return the pointer point to Node if in Linked List
Return NULL pointer if not in Linked List

Node *find_node(List *list, int target){    Node *temp = list->head;    while(temp && temp->value != target) {        temp = temp->next;    }    return temp;}

insert_tail

insert Node to the tail of Linked List

void insert_tail(List *list, int value){    Node *curr = list->head;    Node *prev = NULL;    Node* new = (Node *)malloc(sizeof(Node));    new->next = NULL;    new->value = value;    while(curr) {        prev = curr;        curr = curr->next;    }    if(!prev)        list->head = new;    else        prev->next = new;}

在 Linked List 的尾端插入节点,使用 prevcurr 去走访节点,prevNULL 的情况,表示 Linked List 没有任何节点或是为 NULL,否则就向 prev->next 插入节点,图像化如下

prev 不为 NULL,也就是 Linked List 不为空的情况下,存在节点的情况下

prevNULL,也就是 Linked List 为空,没有节点的情况下

remove_node

Remove the Node in Linked List
If target not in Linked List, return

void remove_node(List *list, Node *target){    if(!target) return;    Node *prev = NULL;    Node *curr = list->head;    while(curr != target) {        prev = curr;        curr = curr->next;    }        if(!prev)        list->head = target->next;    else        prev->next = target->next;}

使用 prev 以及 curr 走访 Linked List 找到目标节点,接下来分成两种情况进行讨论,分别为删除的为头节点或是其他节点

删除其他节点的情况

删除头节点的情况

观察 remove_node 以及 insert_tail

可以发现到在 remove_node 以及 insert_tail 都会出现特定情况需要进行处理,通过 if-else 判断是否为头节点,而以下将使用间接指标 (或称为指标的指标) 对程式码进行优化,除去对于特例的判断

Indirect Pointer 概念

前面 remove_node 的想法为将节点 Node 的记忆体地址储存到 currprev 中,通过 currprev 走访 Linked List,找到 target 时便停止。

而 Indirect Pointer 的想法为运用指向到节点记忆体地址的指标进行操作,原先的 currprev 中储存的是节点本身的记忆体地址,反参考 currprev 我们能够得到节点本身,而 Indirect Pointer 中储存的是指向到节点记忆体地址的指标,如果我们对其进行反参考,我们会得到的是该节点的记忆体地址,概念说明如下

#include <stdio.h>#include <stdlib.h>void foo(int *a_ptr){    a_ptr = NULL;}int main(void){    int a = 3;    int *a_ptr = &a;    printf("%d\n", *a_ptr);    foo(a_ptr);    printf("%d\n", *a_ptr);}

上面这一段程式码,我们有一个指向到整数的指标变数 a_ptr,接着我们将指标变数传入 foo() 函式,让 a_ptr 由原先的指向变数 a 变为指向到 NULL

我们可以通过第 15 行查看对 a_ptr 的修改是否成功,如果修改成功,则第 15 行会出现对空指标进行反参考的错误,造成记忆体区段错误。

以下为执行结果

33

可以发现我们并没有成功修改 a_ptr 所指向的物件,原因为 C 语言的函式传递是以传值进行的,传入函式的参数会产生一份複本到函式中,而我们在函式中修改的参数,实际上修改的是刚刚产生的複本。举一个简单的例子

#include <stdio.h>#include <stdlib.h>void foo(int a){    a = 10;}int main(void){    int a = 3;    printf("%d\n", a);    foo(a);    printf("%d\n", a);}

上面这个例子十分容易理解,我们在 foo() 中修改的 a,实际上是在函式中产生的複本,不会影响到 main 中的 a

如果要针对 a 进行修改,我们需要传入指向到 a 的指标变数,修改 a 记忆体地址中储存的资料,範例如下

#include <stdio.h>#include <stdlib.h>void foo(int *a){    *a = 10;}int main(void){    int a = 3;    printf("%d\n", a);    foo(&a);    printf("%d\n", a);}

图解如下

这里可以注意到,对于 call by value,如果我们修改传入函式中的值,实际上我们是修改到他的複本,但是我们可以修改他背后的资料,以 *a 而言,指标变数中储存的是记忆体地址,而我们对其反参考可以得到记忆体地址储存的资料,对于 call by value,我们无法修改变数中储存的值,也就是我们无法修改指标中储存的记忆体地址,但是我们可以通过反参考指标修改背后的资料。

对应到上面的範例,我们是传入指标变数到 foo() 中,在 foo() 中我们得到 *a 的複本,称为 copy *a,之后我们反参考 copy *a,修改记忆体地址指向的资料。

接着回到我们在一开始看到的例子

#include <stdio.h>#include <stdlib.h>void foo(int *a_ptr){    a_ptr = NULL;}int main(void){    int a = 3;    int *a_ptr = &a;    printf("%d\n", *a_ptr);    foo(a_ptr);    printf("%d\n", *a_ptr);}

这里我们尝试修改变数的值,也就是修改指标变数储存的记忆体地址,将其修改为 NULL,我们修改到的是变数複製出来的複本,而不是在 main()a_ptr 本身,因此在第 15 行不会发生反参考空指标所导致的记忆体区段错误。

这里整理一下,如果我们要对 a 进行修改,我们需要传入 *a。而如果我们要修改 *a,我们会想到我们可能要传入 **a*a 为指标,而 **a 称为指标的指标,又称为间接指标。我们可以通过间接指标去修改指标变数中储存的值,只需要反参考间接指标,修改其值即可,以下範例

#include <stdio.h>#include <stdlib.h>void foo(int **a_ptr){    *a_ptr = NULL;}int main(void){    int a = 3;    int *a_ptr = &a;    printf("%d\n", *a_ptr);    foo(&a_ptr);    printf("%d\n", *a_ptr);}

output:

3Segmentation fault (core dumped)

从以上範例可以看到,我们通过间接指标,去修改指标内储存的值。

而接下来我们将使用间接指标的手法,对 Linked List 的实作进行优化。

应用 Indirect Pointer 的 Linked List

remove_node

前面 remove_node 的想法为我们使用一个指标变数,储存该节点的记忆体地址,反参考后就可以得到节点本身。

而这边使用间接指标的想法,我们储存的指标,是指向到该节点的记忆体地址,如果我们反参考指标,得到的是节点的记忆体地址。

以下为实作部分

void remove_list_node(List *list, Node *target){    Node **indirect = &list->head;        while (*indirect != target)        indirect = &(*indirect)->next;    *indirect = target->next;}

我们使用指向到 Node 指标的指标变数 indirect,里面储存指向到 list->head 的指标,也就是说 indirect 是一个指标的指标,也称为间接指标,indirect is a pointer to pointer, which mean a pointer to your pointer value,your pointer value point to Node, so the type of pointer value is Node *, and the type of the pointer to your pointer value is Node **.

之后我们开始通过 indirect 去走访 Linked List,*indirect 这边为对 indirect 进行反参考,得到的会是节点的记忆体地址,型别为 Node *,通过节点的记忆体地址和 target 进行比较,target 同样也是记忆体地址,意义为节点的记忆体地址。

第 6 行,首先我们对 indirect 进行反参考,得到 Node *,也就是 Node 的记忆体地址,通过箭头运算子取得成员 next,成员 next 指向到下一个节点,型别为 Node *,意义为指向到节点的记忆体地址,接着我们对他进行取址,得到 Node **,意义为指向到指向到节点的记忆体地址,这边听起来有些绕口,概念上就是储存的是指向到节点的记忆体地址。

举一个简单的例子

#include <stdio.h>#include <stdlib.h>typedef struct Node {    int a;    int b;}Node;int main(void){    Node a = {.a = 1, .b = 2};    Node *a_ptr = &a;    Node **a_ptr_ptr = &a_ptr;    printf("%d", (*a_ptr_ptr)->a);}

图解为以下

指标变数本身也是一个变数,有自己的记忆体地址,只是该变数里面储存的值为记忆体地址

#include <stdio.h>#include <stdlib.h>typedef struct Node {    int a;    int b;}Node;int main(void){    Node a = {.a = 1, .b = 2};    Node *a_ptr = &a;    Node **a_ptr_ptr = &a_ptr;    printf("Address of Node a: %-10x\n\n", &(a));        printf("Context of a_ptr: %-10x\n", a_ptr);    printf("Dereference of a_ptr: %-10x\n", *a_ptr);    printf("Address of a_ptr: %-10x\n\n", &(a_ptr));    printf("Context of a_ptr_ptr: %-10x\n", a_ptr_ptr);    printf("Dereference of a_ptr_ptr: %-10x\n", *a_ptr_ptr);    printf("Address of a_ptr_ptr: %-10x\n", &(a_ptr_ptr));}

output

Address of Node a: 61ff18    Context of a_ptr: 61ff18Dereference of a_ptr: 1Address of a_ptr: 61ff14Context of a_ptr_ptr: 61ff14        Dereference of a_ptr_ptr: 61ff18    Address of a_ptr_ptr: 61ff10

Node a 的记忆体地址为 0x61ff18

Node *a_ptr 为指标变数,记忆体地址为 0x61ff14,内容为 0x61ff18,为一个指向到 Node 的指标,对 a_ptr 进行反参考,意味着存取 a_ptr 指标变数内容所指向的资料,也就是 0x61ff18 所指向的资料,存取到 Node a 的第一个成员,得到 1。

Node **a_ptr_ptr 为指标变数,记忆体地址为 0x61ff10,内容为 0x61ff14,为一个指向到 Node * 的指标,对 a_ptr_ptr 进行反参考,意味着存取 a_ptr_ptr 指标变数内容所指向的资料,也就是 0x61ff14 所指向的资料,0x61ff14 中的资料为 0x61ff18

将这个概念应用在 remove_list_node 中,图解如下

这是初始情况,indirect 为一个指标的指标,也就是 indirect 为一个指标变数,储存指向到节点的指标。(加上记忆体地址方便理解)

不同于原先的实作,指向到节点本身,这边是储存指向到节点的指标,概念上为储存要更新的位置,而不是要更新的节点本身。反参考 indirect 得到的是指向到节点的记忆体地址。

接着看到走访,假设目前情况如下

首先先对 indirect 进行反参考,会得到节点的下一个节点的指标,概念上为接下来我们要更新的地址

接着我们通过箭头运算子存取成员 next,也就是 (*indirect)->next

接着我们取出指向到下一个节点的指标,也就是取出指向 Node * 的指标
&(*indirect)->next,型别为 Node **

:::info
由走访节点的过程中我们可以观察到一件事情,indirect 所维护的,是我们要更新的位置,我们接下来要更新的位置是储存在 next 中的,型别为 Node *,而我们要存取要更新的位置,因此需要使用 Node ** 进行操作。

在上面的实作中,我们可以通过 *indirect 来得到 Node *,进而去存取下一个节点的资讯。

:::

如果 *indirect 就是我们要删除的节点,也就是 *indirect == target,则图解为以下

我们可以通过 *indirect 得到要删除的节点本身,也就是 target,型别为 Node *,目前 *indirect 的内容为前一个节点指向的下一个节点,于是,我们可以直接通过修改 *indirect 去改变前一个节点所指向的下一个节点,概念上可以理解为修改前一个节点中,next 的内容。

*indirect = target->next 的意义为将前一个节点的下一个节点改变为 target 的下一个节点。

这时候的 *indirect 为以下

可以注意到,实际上 target 并没有被我们移除。

remove_node 头节点情况

在最一开始的 while 迴圈判断时,会先判断 *indirect != target,而我们最一开始条件便不成立了,因此不会进入到 while 迴圈,目前图解如下

接着我们通过 *indirect 获得第一个节点本身,型别为 Node *,第一个节点本身也就是 list_head,接着我们将 *indirect 设为头节点的下一个节点,也就是将 *indirect 设为 target->next,图解如下

indirect pointer vs direct pointer

我们在最一开始直觉上的实作,是使用 direct pointer 的方式,直接针对节点本身进行操作,但是会有一些例外状况需要处理,如头节点为特殊情况。

而 indirect pointer 的想法为我们针对接下来要更新的位置进行判断,好处是不会有例外情况进行处理,因为我们并没有直接针对头节点进行操作。

Leetcode 21. Merge Two Sorted Lists

给定两个已经完成排序的单向链结串列,请将这两个链结串列进行合併,并回传合併完成的链结串列头节点。

直觉上的做法,是建立一个头节点,接着我们开始走访 list1list2,将 val 较小的节点串到我们建立的节点上,完成后回传我们建立头节点的下一个节点 (我们建立的头节点概念上是一个 dummy node)

以下为该实作程式码

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){    struct ListNode *head = (struct ListNode *)malloc(sizeof(struct ListNode));    struct ListNode *ptr = head;    while(list1 && list2) {        if(list1->val < list2->val) {            ptr->next = list1;            list1 = list1->next;        }        else {            ptr->next = list2;            list2 = list2->next;        }        ptr = ptr->next;    }    ptr->next = list1 ? list1 : list2;     return head->next;}

这里我们的 head 使用 malloc 配置了一块记忆体空间,而我们可以使用间接指标的方法优化这一段空间,让我们不用使用 malloc 去对 head 进行记忆体空间的分配

前面的想法为 head 为一个实际存在的节点,而我们通过 ptr 去维护 head 后面串接的节点,但实际上 head 为一个 dummy node,内容没有意义,只是为了让我们串接后面的节点。

这边使用间接指标的想法,概念上是改变 head 所指向的节点,直接修改 head 的内容,我们直接宣告一个 head pointer,指向到节点,并初始化为 NULL,之后我们使用 **ptr = &headptr 里面储存的,是 head 这个指标变数本身的指标,反参考 ptr 会得到 head 指标变数所在的记忆体地址。

我们可以直接通过 *ptr = some address 直接改变 head 的内容,也就是 head 所储存的指向到节点的记忆体地址。

接着我们可以通过 &(*ptr)->next 得到指向 head 下一个节点的指标的指标。

以下为初始情况

加入第一个节点 (直接修改 head 的内容,也就是让 head 直接指向某一个节点),(*ptr) = list?

通过 (*ptr) 存取到节点本身的指标,通过箭头运算子存取成员

这里的概念如同前面提及的 remove_node 一样,我们是维护记忆体地址本身,而非节点本身。

实作如下

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){    struct ListNode *head;    struct ListNode **ptr = &head;    while(list1 && list2) {        if(list1->val < list2->val) {            *ptr = list1;            list1 = list1->next;        }        else {            *ptr = list2;            list2 = list2->next;        }        ptr = &(*ptr)->next;    }    (*ptr) = list1 ? list1 : list2;     return head;}

接着观察到 if-else 的部分,发现要做的操作都是反参考 ptr,指向到下一个要接上的节点,接着更新到下一个节点,我们可以使用一个指标变数去指向 L1 或是 L2

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){    struct ListNode *head;    struct ListNode **ptr = &head;    struct ListNode **node;    while(list1 && list2) {        node = (list1->val < list2->val) ? (&list1) : (&list2);        *ptr = *node;        ptr = &(*ptr)->next;        *node = (*node)->next;    }    (*ptr) = list1 ? list1 : list2;     return head;}

使用 for 迴圈可以减少一些行数

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){    struct ListNode *head = NULL, **ptr = &head, **node = NULL;        for(; list1 && list2; *node = (*node)->next, ptr = &(*ptr)->next) {        node = (list1->val < list2->val) ? (&list1) : (&list2);        *ptr = *node;    }    (*ptr) = list1 ? list1 : list2;     return head;}

虽然可以将操作写入 for 迴圈中,但程式码将不易阅读

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){    struct ListNode *head = NULL, **ptr = &head, **node = NULL;    for(; list1 && list2; node = (list1->val < list2->val) ? (&list1) : (&list2), *ptr = *node, *node = (*node)->next, ptr = &(*ptr)->next);    return (*ptr) = list1 ? list1 : list2, head;}

Leetcode 2095. Delete the Middle Node of a Linked List

给定一单向链结串列,删除中间的节点后回传

直觉做法

struct ListNode* deleteMiddle(struct ListNode* head){    struct ListNode* slow_prev;    struct ListNode* slow = head;    struct ListNode* fast = head;        if(!head->next) return NULL;    while(fast && fast->next){        slow_prev = slow;        slow = slow->next;        fast = fast->next->next;    }    slow_prev->next = slow->next;    return head;}

我们可以尝试使用间接指标,上面的作法次我们使用一个指标去指向到节点,如果对该指标进行反参考,我们会得到节点本身,而使用间接指标,我们要操作的对象是针对节点中 next 这个成员,要修改 next,我们需要使用 Node ** (指向到 next) 来对 next 进行操作,也就是间接指标,以下範例

struct ListNode* deleteMiddle(struct ListNode* head){    struct ListNode **indirect = &head;    for(struct ListNode *fast = head; fast && fast->next; fast = fast->next->next)        indirect = &(*indirect)->next;    *indirect = (*indirect)->next;    return head;}

这边走访时概念和上方删除节点的概念相同,都是针对接下来要操作的位置进行操作。

这边同样可以发现,实际上我们没有将节点移除,这边我们尝试将节点占用的记忆进行释放。

我们需要维护一个指标,指向到 *indirect 的前一个节点

struct ListNode* deleteMiddle(struct ListNode* head){    struct ListNode **indirect = &head;    struct ListNode *prev = NULL;    for(struct ListNode *fast = head; fast && fast->next; fast = fast->next->next) {        prev = *indirect;        indirect = &(*indirect)->next;    }    prev->next = (*indirect)->next;    free(*indirect);    return head;}

反参考 indirect 得到的是下一个节点,而在 indirect 更新之前,我们先使用一个指标储存 indirect,达到储存前一个节点的目标。

迴圈结束后,*indirect 为中间节点,因此只要我们将 prev->next = (*indirect)->next 便完成移除中间节点的操作了,之后我们再将 *indirect 使用 free() 进行释放便完成操作,以给定单向链结串列 [1,3,4,7,1,2,6] 为例,在迴圈走访结束后,prev 指向节点的值为 4,*indirect 指向节点的值为 7。

接着我们把 prev 的下一个节点设为 *indirect 的下一个节点,这边其实 prev->next = (*indirect)->next 的意义等同于 (*indirect) = (*indirect)->next,相当于把指向到 7 的节点设为指向到 1 的节点,也就是,这边 *indirect 的内容为 1

接着我们会将 *indirect 进行释放,可以发现到我们将指向到 1 的节点进行释放了,这会导致我们在后续走访的时候存取到空指标。

我们试着执行后,会发现 leetcode 中的 AddressSanitizer 会发出 heap-use-after-free 的错误。

要解决这个问题,我们可以使用一个指标去存放 (*indirect)->next,实作如下

struct ListNode* deleteMiddle(struct ListNode* head){    struct ListNode **indirect = &head;    struct ListNode *prev = NULL;        if(!head->next) return NULL;    for(struct ListNode *fast = head; fast && fast->next; fast = fast->next->next) {        prev = *indirect;        indirect = &(*indirect)->next;    }    struct ListNode *next = (*indirect)->next;    free(*indirect);    prev->next = next;    return head;}

图解如下,首先是 *next = (*indirect)->next 的概念如下

这边没有直接让 next 画在 *indirect 节点的 next 下方,是为了不和 Node ** 混淆。

接着 prev->next = next

如此我们便完成了释放中间节点的操作。

也可以使用以下作法

struct ListNode* deleteMiddle(struct ListNode* head){    struct ListNode **indirect = &head;    struct ListNode *prev = NULL;        if(!head->next) return NULL;    for(struct ListNode *fast = head; fast && fast->next; fast = fast->next->next) {        prev = *indirect;        indirect = &(*indirect)->next;    }    struct ListNode *del = (*indirect);    prev->next = (*indirect)->next;    free(del);    return head;}

Rust 版本

在 Rust 版本中如果我们想要使用快慢指标,则会发现到一些问题,最一开始 slowfast 都需要指向到 head,接着 slowfast 都需要走访 Linked List,因此 slowfast 都需要是可变的。

impl Solution {    pub fn delete_middle(mut head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {            }}

delete_middle 函式传入了一个 Option<Box<ListNode>>Option 如同其他的 enum 型别,我们可以使用 match 去匹配其所有的结果,在 Linked List 的实作中,会有空节点的情况发生,也就是 nullptr,但是在 rust 中并没有 nullptr 存在,而是通过 Option,在 Option 中有两个成员,一个为 None,另外一个为 Some(val),我们可以使用 matchOption 进行解构,或是使用更加简单的方式,使用 ? 进行解构,以下举例

struct Person {    job: Option<Job>,}#[derive(Clone, Copy)]struct Job {    phone_number: Option<PhoneNumber>,}#[derive(Clone, Copy)]struct PhoneNumber {    area_code: Option<u8>,    number: u32,}impl Person {    fn work_phone_area_code(&self) -> Option<u8> {        match self.job {            Some(job) => {                match job.phonstruct Person {    job: Option<Job>,}#[derive(Clone, Copy)]struct Job {    phone_number: Option<PhoneNumber>,}#[derive(Clone, Copy)]struct PhoneNumber {    area_code: Option<u8>,    number: u32,}impl Person {    fn work_phone_area_code(&self) -> Option<u8> {        match self.job {            Some(job) => {                match job.phone_number {                    Some(phone_number) =>                         match phone_number.area_code {                          Some(area_code) => Some(area_code),                            number => number,                            None => None,                        },                    None => None,                }            }            None => None,        }    }}fn main() {    let p = Person {        job: Some(Job {            phone_number: Some(PhoneNumber {                area_code: Some(61),                number: 439222222,            }),        }),    };        println!("{}", p.work_phone_area_code().unwrap());}

可以看到 work_phone_area_code 的部分,如果使用 match 进行解构,将会看起来十分的杂乱,这边可以使用 ?Option 进行解构

impl Person {    fn work_phone_area_code(&self) -> Option<u8> {        self.job?.phone_number?.area_code    }}

x? 为表达式,能够对他进行取值,self.job?phone_number 意义为对 Option<job> 取值,当 jobSome(value) 时,取出 phone_number

回到题目本身,我们需要 slowfast 对 Linked List 进行走访,slow 需要为 head 的可变引用,因为我们需要修改他所指向的下一个节点,而 fast 只是用来走访 Linked List 的,因此只需要不可变引用即可,实作如下

impl Solution {    pub fn delete_middle(mut head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {        let mut slow = &mut head;        let mut fast = &head;    }}

但上面这个写法有一个问题,我们无法同时持有对于某一个物件的可变引用以及不可变引用,为此,我们需要再创造一个新的物件 head,他的内容需要和 head 相同,但是是两个独立的物件

impl Solution {    pub fn delete_middle(mut head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {        let mut slow = &mut head;        let mut fast = &(head.clone());    }}

概念如下

#[derive(Clone)]struct Obj {    a:i32}fn main() {    let mut a: Obj = Obj {        a: 1,    };    let mut b = &a;    let mut c = &(a.clone());        println!("{:p}", &a);    println!("{:p}", b);    println!("{:p}", c);}

output

0x7ffe4eb9ed580x7ffe4eb9ed580x7ffe4eb9ed5c

可以看到 ac 是不同的物件,如此我们便可以利用 fast 以及 slow 对 Linked List 完成走访了

impl Solution {    pub fn delete_middle(mut head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {        let mut fast = &(head.clone());        let mut slow = &mut head;                while fast.is_some() && fast.as_ref()?.next.is_some() {            slow = &mut (slow.as_mut()?.next);            fast = &(fast.as_ref()?.next.as_ref()?.next);        }                *slow = (*slow).as_mut()?.next.take();                head    }}

source
同样的,我们也可以使用 indirect pointer 的概念进行操作

impl Solution {    pub fn delete_middle(mut head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {        let mut fast = &(head.clone());        let mut indirect = &mut head;                 while fast.is_some() && fast.as_ref()?.next.is_some() {            fast = &(fast.as_ref()?.next.as_ref()?.next);            indirect = &mut indirect.as_mut().unwrap().next;        }        *indirect = indirect.as_mut().unwrap().next.take();        head    }}

参考资料

你所不知道的 C 语言: linked list 和非连续记忆体

Rust-lang

Rust. Fast&Slow pointers Solution


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章