您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何使用debug調(diào)試”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“如何使用debug調(diào)試”吧!
反轉(zhuǎn)鏈表是經(jīng)典的題目,題中信息描述很清晰,給定一個(gè)單鏈表,將其反轉(zhuǎn)。
先說(shuō)說(shuō)有什么思路呢?從題中給的案例輸出結(jié)果看,是不是只需要將輸入的鏈表的指針改成相反方向,就可以得到要輸出的結(jié)果。
就好比如下圖所示:
但是問(wèn)題來(lái)了,我們是單鏈表,是沒(méi)辦法將下個(gè)節(jié)點(diǎn)直接指向該節(jié)點(diǎn)的上個(gè)節(jié)點(diǎn)。
因此就需要定義一個(gè)輔助指針,用來(lái)指向該節(jié)點(diǎn)的上個(gè)節(jié)點(diǎn),這樣就能完成,如下圖所示。
那按照我們上面分析也就是將cur指針指向pre節(jié)點(diǎn)就可以了。
注意:此處有坑
當(dāng)我們將當(dāng)前節(jié)點(diǎn)【cur】指向上一個(gè)節(jié)點(diǎn)【pre】的時(shí)候,如何將指針向下移動(dòng)呢?
此時(shí)的節(jié)點(diǎn)【cur】已經(jīng)指向了上一個(gè)節(jié)點(diǎn)【pre】了,所以我們還需要一個(gè)臨時(shí)變量去保存當(dāng)前節(jié)點(diǎn)的下個(gè)節(jié)點(diǎn),具體為什么這么做,我們?cè)谙旅娲a演示的時(shí)候debug看下過(guò)程。
接著我們上面的步驟,將指針向下移動(dòng),如圖所示。
移動(dòng)指針后,再將當(dāng)前節(jié)點(diǎn)的next指針指向上一個(gè)節(jié)點(diǎn)。
最后當(dāng)前節(jié)點(diǎn)沒(méi)有下個(gè)節(jié)點(diǎn)的時(shí)候,就結(jié)束遍歷,如圖所示。
按照套路,先初始化節(jié)點(diǎn)對(duì)象。
class ListNode { int val; ListNode next; ListNode() { } ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; } @Override public String toString() { return "ListNode{" + "val=" + val + '}'; } }
創(chuàng)建單鏈表結(jié)構(gòu)。
// 創(chuàng)建單鏈表 ListNode l1 = new ListNode(1); ListNode l2 = new ListNode(2); ListNode l3 = new ListNode(3); ListNode l4 = new ListNode(4); ListNode l5 = new ListNode(5); NodeFun nodeFun = new NodeFun(); nodeFun.add(l1); nodeFun.add(l2); nodeFun.add(l3); nodeFun.add(l4); // 返回創(chuàng)建的鏈表 ListNode node = nodeFun.add(l5);
反轉(zhuǎn)鏈表的代碼。
public ListNode reverseListIteration(ListNode head) { // 定義上節(jié)點(diǎn)輔助指針 ListNode pre = null; // 定義當(dāng)前節(jié)點(diǎn)輔助指針 ListNode cur = head; // 循環(huán)當(dāng)前節(jié)點(diǎn)不為空 while (null != cur) { // 臨時(shí)變量保存當(dāng)前節(jié)點(diǎn)的下個(gè)節(jié)點(diǎn) ListNode temp = cur.next; // 當(dāng)前節(jié)點(diǎn)的next指向上節(jié)點(diǎn) cur.next = pre; // 上節(jié)點(diǎn)向下移動(dòng) pre = cur; // 當(dāng)前節(jié)點(diǎn)指向下個(gè)節(jié)點(diǎn) cur = cur.next; } return pre; }
節(jié)點(diǎn)初始化完成了,按照分析我們定義了2個(gè)節(jié)點(diǎn),如上圖第一次遍歷【pre】節(jié)點(diǎn)是null,【cur】從第一個(gè)節(jié)點(diǎn)開始。
下一步debug調(diào)試我們先不急,回顧之前說(shuō)的一個(gè)問(wèn)題,為什么要將當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)用臨時(shí)變量保存,那我們直接看debug調(diào)試。
第一次遍歷的時(shí)候,修改完指針后當(dāng)前節(jié)點(diǎn)已經(jīng)指向上一個(gè)節(jié)點(diǎn)了,再看上述題目分析的圖解。
這就是為啥要先把當(dāng)前節(jié)點(diǎn)的下個(gè)節(jié)點(diǎn)緩存起來(lái)。
上圖debug我們看出,【cur】當(dāng)前節(jié)點(diǎn)的指針已經(jīng)指向null,下一步就是移動(dòng)指針指向下一個(gè)節(jié)點(diǎn)。
我們?cè)俳又M(jìn)行debug調(diào)試,按照上述分析,第二步循環(huán)就是將節(jié)點(diǎn)【2】指向上一個(gè)節(jié)點(diǎn)【1】,如下圖所示。
現(xiàn)在當(dāng)前節(jié)點(diǎn)【cur】已經(jīng)指向【2】,那它的下個(gè)節(jié)點(diǎn)就是【1】,如下圖所示。
經(jīng)過(guò)上面的兩步循環(huán),成功的將指針進(jìn)行了反轉(zhuǎn),剩下的節(jié)點(diǎn)循環(huán)也就如出一轍了。
當(dāng)循環(huán)到最后節(jié)點(diǎn)【5】時(shí),下個(gè)節(jié)點(diǎn)為null,此時(shí)結(jié)束while循環(huán),而節(jié)點(diǎn)【5】也是指向了上一個(gè)節(jié)點(diǎn)【4】。
最后我們?cè)倏聪逻\(yùn)行結(jié)果。
如果做過(guò)字符串的算法題,里面有個(gè)回文字符串的題目。沒(méi)錯(cuò),它倆的意思是一樣的。
看題目描述得知一個(gè)鏈表是不是回文鏈表,就是看鏈表就是看鏈表正讀和反讀是不是一樣的。
假如說(shuō),我們拿到了后半部分鏈表,再將其反轉(zhuǎn)。去和鏈表的前半部分比較,值相等就是回文鏈表了。
注意:
這種方式會(huì)破壞原鏈表的結(jié)構(gòu),為保證題目的一致性,最后再將鏈表再重新拼接
另外一種解題方式為:將整個(gè)鏈表節(jié)點(diǎn)遍歷保存到數(shù)組中,而數(shù)組是有下標(biāo),并可以直接獲取數(shù)組的大小,那么只需從數(shù)組的首尾去判斷即可
反轉(zhuǎn)鏈表上一道題我們已經(jīng)分享了,現(xiàn)在重點(diǎn)是如何獲取后半部分的鏈表。
我們?cè)僬f(shuō)說(shuō)快慢指針的思想,通常我們定義2個(gè)指針,一個(gè)移動(dòng)快,一個(gè)移動(dòng)慢。詳細(xì)的案例可以參考本人上一篇文章《開啟算法之路,還原題目,用debug調(diào)試搞懂每一道題》,有一道關(guān)于快慢指針的題目。
定義慢指針每次移動(dòng)1個(gè)節(jié)點(diǎn),快指針每次移動(dòng)2個(gè)節(jié)點(diǎn),當(dāng)然我們是需要保證快節(jié)點(diǎn)的下下個(gè)
個(gè)節(jié)點(diǎn)不為空。
slow = slow.next; fast = fast.next.next;
其實(shí)快慢指針的思想就是,假設(shè)鏈表是一個(gè)回文鏈表,快指針比慢指針是多走一步,當(dāng)快指針走完的時(shí)候,慢指針也就剛好走到該鏈表的一半。
上圖中slow指針正好走到鏈表的一半,此時(shí)也就得到鏈表的后半部分了,即slow.next
。
老套路,先創(chuàng)建一個(gè)回文鏈表。
ListNode l1 = new ListNode(1); ListNode l2 = new ListNode(2); ListNode l3 = new ListNode(2); ListNode l4 = new ListNode(1); NodeFun nodeFun = new NodeFun(); nodeFun.add(l1); nodeFun.add(l2); nodeFun.add(l3); ListNode head = nodeFun.add(l4);
獲取后半部分鏈表代碼。
private ListNode endOfFirstHalf(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast.next != null && fast.next.next != null) { fast = fast.next.next; slow = slow.next; } return slow; }
反轉(zhuǎn)鏈表的代碼與上題目是一樣的。
最后將兩個(gè)鏈表進(jìn)行判斷是否是一樣的。
// 判斷是否回文 ListNode p1 = head; ListNode p2 = secondHalfStart; boolean flag = true; while (flag && p2 != null) { if (p1.val != p2.val) { flag = false; } p1 = p1.next; p2 = p2.next; }
先獲取鏈表的后半部分。
debug開始循環(huán)后,fast直接走到鏈表的第3個(gè)節(jié)點(diǎn)【2】
slow.next就是鏈表的后半部分,再將后半部分進(jìn)行鏈表反轉(zhuǎn)
最后我們也就得到如下2個(gè)鏈表。
最后將這2個(gè)鏈表進(jìn)行比較是否相等,相等則是回文鏈表。
獲取鏈表的中間節(jié)點(diǎn)乍一看和回文鏈表中使用快慢指針獲取后半鏈表有點(diǎn)類似呢?
沒(méi)錯(cuò),這波操作是類似的,但也并不是完全一樣,其主要思想還是快慢指針。
換句話說(shuō),如果你已理解了上面的題,那這道題也就不是什么事了。話不多說(shuō),先來(lái)分析一波。
同樣我們還是定義slow慢指針每次移動(dòng)一個(gè)節(jié)點(diǎn),fast快指針每次移動(dòng)2個(gè)節(jié)點(diǎn)。
那么fast快指針移動(dòng)到最后節(jié)點(diǎn)時(shí),slow慢指針也就是要返回的鏈表。
我想,你是不是有個(gè)疑問(wèn)。就是為什么慢指針是移動(dòng)一個(gè)節(jié)點(diǎn),快節(jié)點(diǎn)移動(dòng)2個(gè)節(jié)點(diǎn)?如果是偶數(shù)個(gè)節(jié)點(diǎn),這個(gè)規(guī)則還正確嗎!那就驗(yàn)證下。
為了方便,就繼續(xù)上面節(jié)點(diǎn)的遍歷。
題目中描述,如果有2個(gè)中間節(jié)點(diǎn),返回第二個(gè)節(jié)點(diǎn)
,所以返回節(jié)點(diǎn)【4,5,6】也就符合要求了
創(chuàng)建鏈表結(jié)構(gòu)。
ListNode l1 = new ListNode(1); ListNode l2 = new ListNode(2); ListNode l3 = new ListNode(3); ListNode l4 = new ListNode(4); ListNode l5 = new ListNode(5); NodeFun nodeFun = new NodeFun(); nodeFun.add(l1); nodeFun.add(l2); nodeFun.add(l3); nodeFun.add(l4); ListNode head = nodeFun.add(l5);
獲取后半部分鏈表代碼。
// 快慢指針 ListNode slow = head; ListNode fast = head; while(fast != null && fast.next != null){ //移動(dòng)指針 fast = fast.next.next; slow = slow.next; } return slow;
快指針移動(dòng)到節(jié)點(diǎn)【3】,慢指針移動(dòng)到節(jié)點(diǎn)【2】
接著再走一步,快指針移動(dòng)到節(jié)點(diǎn)【5】,慢節(jié)點(diǎn)移動(dòng)到節(jié)點(diǎn)【3】,到此也就滿足題意的要求了。
這道題要求就是返回倒數(shù)K個(gè)節(jié)點(diǎn),最笨的辦法就是參考上面鏈表反轉(zhuǎn),先將鏈表反轉(zhuǎn)。獲取前K個(gè)節(jié)點(diǎn),將獲取的節(jié)點(diǎn)再次進(jìn)行反轉(zhuǎn)即可得到題目要求。
但是顯然這種方式只能滿足答案輸出,經(jīng)過(guò)上面的3道題目,有沒(méi)有得到什么啟發(fā)呢?
是的,這道題依然可以使用雙指針解決,是不是感覺(jué)雙指針可以解決所有的鏈表問(wèn)題了(QAQ)。
再仔細(xì)一想,是不是感覺(jué)和上一道《鏈表的中間節(jié)點(diǎn)》題目很類似?獲取鏈表的中間節(jié)點(diǎn)是返回后半部分節(jié)點(diǎn),而本道題是要求返回指定K個(gè)節(jié)點(diǎn)。
那就直接說(shuō)結(jié)論吧,同樣是定義快慢指針。只不過(guò)在上道題中快指針是每次移動(dòng)2個(gè)節(jié)點(diǎn),本道題中給定的K,就是快指針移動(dòng)的節(jié)點(diǎn)個(gè)數(shù)。
同樣初始化指針都在首節(jié)點(diǎn),如果我們先將fast指針移動(dòng)K個(gè)節(jié)點(diǎn)。
到此才算初始化節(jié)點(diǎn)完成,剩下的操作就是遍歷剩下的鏈表,直到fast指針指向最后一個(gè)節(jié)點(diǎn)。
一直遍歷到fast節(jié)點(diǎn)為null,此時(shí)返回slow指針?biāo)敢墓?jié)點(diǎn)。
初始化鏈表,由于和前幾道題的操作是一樣的,此處就不在展示。
獲取倒數(shù)第K個(gè)節(jié)點(diǎn)的代碼。
public ListNode getKthFromEnd(ListNode head, int k) { ListNode slow = head; ListNode fast = head; // 先將快指針向前移動(dòng)K while (k-- > 0) { fast = fast.next; } while (fast != null) { fast = fast.next; slow = slow.next; } return slow; }
按照上面圖解分析,fast快指針指向節(jié)點(diǎn)【3】的時(shí)候才算真正初始化快慢指針完成。
當(dāng)快指針指向節(jié)點(diǎn)【5】時(shí),slow慢節(jié)點(diǎn)指向節(jié)點(diǎn)【3】
注意:中間省略了一步,即慢指針指向節(jié)點(diǎn)【2】時(shí),快指針指向節(jié)點(diǎn)【4】
節(jié)點(diǎn)【5】是最后一個(gè)節(jié)點(diǎn),再次進(jìn)入while循環(huán)。
最后一次循環(huán)時(shí),慢指針指向了4,快指針下一個(gè)節(jié)點(diǎn)已經(jīng)為null,此時(shí)結(jié)束循環(huán)。
這道題和上一篇中的題目【刪除排序鏈表中的重復(fù)元素】是一樣的,簡(jiǎn)單的做法即利用Set集合保存未重復(fù)的節(jié)點(diǎn),再遍歷鏈表判斷是否已存在Set集合中。
因此本道題就不在多分析,直接貼上代碼。
Set<Integer> set = new HashSet<>(); ListNode temp = head; while(temp != null && temp.next != null){ set.add(temp.val); if(set.contains(temp.next.val)){ temp.next = temp.next.next; }else{ temp = temp.next; } } return head; }
感謝各位的閱讀,以上就是“如何使用debug調(diào)試”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)如何使用debug調(diào)試這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。