日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

设计链表(Leetcode第707题)

發布時間:2025/3/15 编程问答 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 设计链表(Leetcode第707题) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

此題涵蓋了鏈表的常見操作,是練習鏈表操作非常好的一道題目

此題涵蓋了鏈表的常見操作,是練習鏈表操作非常好的一道題目

此題涵蓋了鏈表的常見操作,是練習鏈表操作非常好的一道題目

題目描述:

設計鏈表的實現。您可以選擇使用單鏈表或雙鏈表。單鏈表中的節點應該具有兩個屬性:val?和?next。val?是當前節點的值,next?是指向下一個節點的指針/引用。如果要使用雙向鏈表,則還需要一個屬性?prev?以指示鏈表中的上一個節點。假設鏈表中的所有節點都是 0-index 的。

在鏈表類中實現這些功能:

get(index):獲取鏈表中第?index?個節點的值。如果索引無效,則返回-1。
addAtHead(val):在鏈表的第一個元素之前添加一個值為?val?的節點。插入后,新節點將成為鏈表的第一個節點。
addAtTail(val):將值為?val 的節點追加到鏈表的最后一個元素。
addAtIndex(index,val):在鏈表中的第?index?個節點之前添加值為?val??的節點。如果?index?等于鏈表的長度,則該節點將附加到鏈表的末尾。如果 index 大于鏈表長度,則不會插入節點。如果index小于0,則在頭部插入節點。
deleteAtIndex(index):如果索引?index 有效,則刪除鏈表中的第?index 個節點。

參考Leetcode博主(題解里搜索c語言的題解,第一個就是)

超詳細

雖然博主講的很詳細了,而且看也看的懂,但是要是你僅僅只是停留在這表面的話,那遠遠是不夠的,實踐也是很重要的,特別是我們軟件行業,要的就是動手能力。

看懂了之后要跟著把代碼再敲一遍,敲了之后再加上自己的理解寫一篇文章是最好的,這樣就能加深你的理解,就算文章的內容和博主差不多,但是是你自己思考和動手寫的,結果肯定不一樣。

下面就來說說這題的思路和解法:

首先是要創建一個鏈表,先設置這個鏈表的頭節點,此節點是空虛的、虛無的,不代表節點意義的鏈表頭,我們不關注它的val只關注它的Next,掛的是真正鏈表的節點。

以下是創建鏈表的內容,詳細注解:

MyLinkedList* myLinkedListCreate() {/*1. 把我們的虛假鏈表頭malloc出來,之后它就是我們訪問、管理鏈表的入口*/MyLinkedList *obj = (MyLinkedList *)malloc(sizeof(MyLinkedList));/*2. 對虛假鏈表頭初始化,val隨便是多少都可以,我們方便而取0,以后也用不到;next為NULL,此時真正的鏈表是空的*/obj->val = 0;obj->next = NULL;/*3. 返回我們的鏈表入口*/return obj; }

鏈表創建好以后,現在就來實現第一個接口——增加頭節點。

此時就可能出現兩種情況:1.鏈表是空的;2.鏈表不是空的

第一種情況:如果鏈表是空的,那么處理起來就比較簡單,直接把Node掛在obj后面即可

第二種情況:鏈表不是空的,那就把現有的鏈表掛在Node后面,這樣就形成了一個新鏈表,最后再把新鏈表掛在鏈表頭的后面,相當于還是有個虛假鏈表頭,接下來的過程就和原來一樣了。

具體代碼實現:

void myLinkedListAddAtHead(MyLinkedList* obj, int val) {/*1. 初始化節點*/MyLinkedList *Node = (MyLinkedList *)malloc(sizeof(MyLinkedList));Node->val = val;Node->next = NULL;/*2. 分情況對待*/if (obj->next == NULL) {/*3. 如果原鏈表是空的,就直接把obj的next指向新節點*/obj->next = Node;return;} else {/*4. 如果原鏈表不是空的,我們先把原鏈表掛在Node節點屁股后面,形成新鏈表*/Node->next = obj->next;/*5. 再把新鏈表更新到obj這個入口上,也就是新鏈表掛在obj虛擬節點的屁股后面*/obj->next = Node;} }

這樣第一個接口就完成了,接下來完成第二個接口——獲取鏈表中第?index?個節點的值。如果索引無效,則返回-1。

1.首先分析無效的情況:①:index < 0 ②鏈表是空的任何索引都無效 ③Index超過了鏈表的長度

因為鏈表不想數組可以隨機存取,鏈表要進行遍歷才能到達指定的地方

此時問題來了,遍歷的終止條件是什么?

有兩種情況:

①:

while(list->next != NULL) {list = list->next; }

假設鏈表為 1->2->3->4->5
那么以這種方式進行遍歷,最后list會停在5這個節點。
5->next 是NULL,循環終止。

②:

while(list != NULL) {list = list->next; } 直接這么寫會不會在list->next這里取到空指針? 理論上是不會的,因為list節點本身我們是malloc出來的,取next是ok的, 但是next指向的內容如果是NULL,等于我們將list賦值成NULL,然后會在下一個循環條件退出。

假設鏈表為 1->2->3->4->5
那么以這種方式進行遍歷,最后list會停在5這個節點的后面。
也就是最終,list停在了5后面的NULL上。

所以這兩種方式怎么選擇?
對我們來說,根據應用又有不同的情況:

1. 要遍歷鏈表,對其中的每一個節點都訪問到,并進行操作。
這種場景的意思是:
假設鏈表為 1->2->3->4->5
要對每個節點,1,2,3,4,5都進行某個操作后,再退出循環。
這種情況下,我們可以采用第二種方式,也就是最后list停在5后面的NULL節點上。
這樣,節點5也會在循環的里面,被操作到。

2. 遍歷鏈表的目的是找到尾節點,對尾節點進行操作。
這種場景的意思是:
假設鏈表為 1->2->3->4->5
要找到尾節點5,并對它進行操作。
要對一個節點進行操作,那我們必須停在它的上面。
這種情況要選擇第一種,最終list停在5節點上面,我們就可以對它進行操作。

分析到這里另一個接口——尾插就很容易寫出來了,用的就是第一種方式

具體代碼:

void myLinkedListAddAtTail(MyLinkedList* obj, int val) {/*1. 初始化新節點*/MyLinkedList *Node = (MyLinkedList *)malloc(sizeof(MyLinkedList));Node->val = val;Node->next = NULL;/*2. 不能改變obj本身的位置,所以我們創建一個新指針標記鏈表移動的當前臨時位置*//* 因為真實鏈表也可能是空的,所以我們從obj開始,如果是空的就相當于直接在obj后面加一個節點*/MyLinkedList *nowList = obj;/*3. 用第一種方式遍歷鏈表,使得臨時位置停在最后一個節點本身*/while (nowList->next != NULL) {nowList = nowList->next;}/*4. 對尾節點操作,把它的next指向新節點*/nowList->next = Node; }

?

另一個接口——獲取index的值

這時用的就是第二種方式了,停在節點后面

那么終止循環的條件又應該是什么呢?

兩種情況:

1. 鏈表當前的Node已經為NULL

2. Node還不是NULL,但是當前的位置已經達到了index

那么就應該有兩種寫法:

第一種寫法:

一。以當前Node為NULL作為外面的循環終止條件;以now位置為index作為break條件終止循環 int now = 0; while(list != NULL) {if (now == index) {return list->val;}list = list->next;now++; }循環結束后考慮,鏈表已經遍歷完了,now都沒有達到index,說明index無效,超過了鏈表的長度: return -1; (這里不需要判斷,因為循環處理,list要么是NULL,要么index無效,肯定是返回-1)

第二種寫法:

二。以當前位置now達到index位置作為循環終止條件,以鏈表當前Node為NULL作為break條件終止循環 int now = 0; while(now < index) {if (list == NULL) {return -1;}list = list->next;now++; }/*注意這里為什么要判斷。* 1. 考慮index == 0時,循環不會進入* 2. now++后,實際當前的list我們并不知道是不是NULL,必須判斷一下,也就是當前的now位置的list,沒有經過判斷* 我們只知道now位置現在就是index位置,但是不知道這個Node到底是不是NULL。*/ if (list != NULL) {return list->val; } return -1;

整個接口:

題目要求:get(index):獲取鏈表中第 index 個節點的值。如果索引無效,則返回-1。int myLinkedListGet(MyLinkedList* obj, int index) {/*1. 對明顯的異常情況進行攔截。index < 0,以及真實鏈表是空的情況,直接返回-1*/if (index < 0 || obj->next == NULL) {return -1;}/*2. 定義一個now位置,從0開始; 取出真實鏈表作為0位置*/int now = 0;MyLinkedList *listNow = obj->next;/*3. 進行鏈表遍歷,我們采用的遍歷方式是取到節點本身* 循環終止條件: now位置達到index位置* break條件: 當前位置節點為空,直接返回-1* 遍歷過程: 每次取next節點,然后now位置后移* 循環結束后的判斷: 循環結束后,即取到了now位置為index位置的節點* 但是該節點是否是NULL沒有經過判斷,所以需要判斷* 如果該節點不為空,就是我們要找的,返回它的val* 如果為空,證明該節點雖然位置到了index,但是它剛好是空的,返回-1。*/while (now < index) {if (listNow == NULL) {return -1;}listNow = listNow->next;now++;}if (listNow != NULL) {return listNow->val;}return -1; }

實現增加一個節點接口:

基本過程:

1. 先初始化一個新節點

2. 找到index的前一個節點

3. 新節點的next指向index位置

4. index的前一個節點的next指向新節點

要思考的問題:

1. 異常情況: index < 0,題目要求負索引直接頭插,我們可以直接調用已經寫好的頭插函數
2. index等于鏈表長度,也就是index位置剛好是尾節點,這時候要尾插
3. index大于鏈表長度,也就是index位置是空的,不操作
4. 正常情況,新節點要在index位置插入。也就是我們要找到index - 1位置,完成上述基本插入操作。

關鍵問題就變成了找上一個節點

回憶上面分析的遍歷的問題,此時應該用第一種遍歷

此時的循環終止也有兩種情況:

1. list是最后一個節點,它的next是NULL。 now位置和index位置關系不明,但一定沒有超過index - 1

2. now位置是index的前一個位置。 在循環里跳出的,所以滿足list->next != NULL, list一定不是尾節點

完整實現:

void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {/*1. 如果是負索引,直接頭插*/if (index <= 0) {myLinkedListAddAtHead(obj, val);}/*2. now位置從0開始,鏈表遍歷從真實鏈表開始*/int now = 0;MyLinkedList *nowList = obj->next;/*3. 鏈表遍歷,停在每個節點本身,最后一個節點不為空。* break 條件: now位置達到index - 1位置*/while (nowList->next != NULL) {if (now == index - 1) {break;}nowList = nowList->next;now++;}/*如果不滿足now 位置是index - 1位置,一定是通過節點->next == NULL跳出循環的,又不滿足位置條件,index無效,不操作*/if (index - 1 != now) {return;}/*4. 初始化一個新節點,新節點的next指向index位置節點,即是當前節點(index - 1位置)的next* 然后將index - 1位置節點的next,指向新節點*/MyLinkedList *Node = (MyLinkedList *)malloc(sizeof(MyLinkedList));Node->val = val;Node->next = nowList->next;nowList->next = Node; }

最后一個接口——刪除節點

其實同插入是一樣的道理,只是操作上不一樣

完整實現:

void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {/*1. 異常情況攔截:index < 0和真實鏈表為空的情況,直接退出*/if (index < 0 || obj->next == NULL) {return;}/*如果index 是 0,此時真實鏈表至少有一個節點,所以可以直接刪除第一個節點*/if (index == 0) {obj->next = obj->next->next;return;}/*2. now從0位置開始,取出真實鏈表,進行遍歷*/MyLinkedList *nowList = obj->next;int now = 0;/*3. 鏈表遍歷:* 循環終止條件: 當前節點已經是最后一個節點,它的next是NULL * break條件: 當前節點是index位置的前一個節點: now == index - 1* 循環結束后: 需滿足當前節點既不是最后一個節點,也是index位置的前一個位置,才進行刪除操作*/while (nowList->next != NULL) {if (now == index - 1) {break;}nowList = nowList->next;now++;}if (now == index - 1 && nowList->next != NULL) {nowList->next = nowList->next->next;} }

最后就是要考慮釋放空間的問題了。

如果直接從第一個節點釋放,那么后面的next就無法取到了,鏈斷掉以后,剩下的內存就再也無法訪問。

所以鏈表釋放內存必須從最后一個開始釋放,再釋放倒數第二個。
但是問題又來了,如果我們遍歷,取到最后一個節點釋放,但是鏈表是單向的,我們無法通過鏈表取到它上一個節點。

那怎么辦呢?
想到一個方法: 遞歸。

每次只要檢查到當前節點還有下一個節點,就先去釋放下一個節點。
如果沒有下一個節點了,就真正完成釋放。
然后逐層往回走,最后釋放整個鏈表。

遞歸需要確定終止條件,和每次的操作
終止條件:下一個節點是空,即Node->next == NULL
每次操作:Node->next = NULL;
free(Node);

完整實現:

void myNodeFree(MyLinkedList* Node) {if (Node->next != NULL) {myNodeFree(Node->next);Node->next = NULL;}free(Node);}void myLinkedListFree(MyLinkedList* obj) {myNodeFree(obj); }

想看更詳細的請移步Leetcode,707題c語言題解第一個。

總結

以上是生活随笔為你收集整理的设计链表(Leetcode第707题)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。