昨天在最後給大家留了拓展題,不知道大家有沒有思考完成,其實南塵說有巨坑是吓大家的啦,實際上也沒什麼。我們來繼續看看昨天這個拓展題。
面試題:給定單連結清單的頭結點,删除單連結清單的倒數第 k 個結點。
前面的文章見連結:面試 7:面試常見的連結清單算法捷徑(一)
這個題和前面的文章中增加了一個操作,除了找出來這個結點,我們還要删除它。删除一個結點,想必大家必定也知道:要想操作(添加、删除)單連結清單的某個結點,那我們還得知道這個節點的前一個節點。是以我們要删除倒數第 k 個結點,就必須要找到倒數第 k+1 個結點。然後把倒數第 k+1 個元素的 next 變量 p.next 指向 p.next.next。
我們找到倒數第 k 個結點的時候,先讓 fast 走了 k-1 步,然後再讓 slow 變量和 fast 同步走,它們之間就會一直保持 k-1 的距離,是以當 fast 到連結清單尾結點的時候,slow 剛剛指向的是倒數第 k 個結點。
本題由于我們要知道倒數第 k+1 個結點,是以得讓 fast 先走 k 步,待 fast 指向連結清單尾結點的時候,slow 正好指向倒數第 k+1 個結點。
我們簡單思考一下臨界值:
- 當 k = 1 的時候,删除的值是尾結點。我們讓 fast 先走 1 步,當 fast.next 為尾結點的時候,倒數第 k+1 個結點正好是我們的倒數第二個結點。我們删除 slow.next,并讓slow.next 指向 slow.next.next = null,滿足條件。
- 當 k > len 的時候,我們要找的倒數第 k 個元素不存在,直接出錯;
- 當 1 < k < len 的時候,k 最大為 len-1 的時候,fast 移動 len-1 步,直接到達尾結點,此時,snow 指向頭結點。删除倒數第 k 個元素,即删除正數第 2 個結點即可;
- 當 k = len 的時候比較特殊,當 fast 移動 len 步的時候,已經指向了 fast.next = null,此時我們其實要删除的是頭結點,直接傳回 head.next 即可。
是以我們自然能得到這樣的代碼。
public class Test07 {
public static class LinkNode {
int data;
LinkNode next;
public LinkNode(int data) {
this.data = data;
}
}
private static LinkNode delTheSpecifiedReverse(LinkNode head, int k) {
LinkNode slow = head;
LinkNode fast = head;
if (fast == null) {
throw new RuntimeException("your linkNode is null");
}
// 先讓 fast 先走 k 步
for (int i = 0; i < k; i++) {
if (fast == null) {
// 說明輸入的 k 已經超過了連結清單長度,直接報錯
throw new RuntimeException("the value k is too large.");
}
fast = fast.next;
}
// fast == null ,說明已經到了尾結點後面的空區域,說明要删除的就是頭結點。
if (fast == null) {
return head.next;
}
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return head;
}
public static void main(String[] args) {
LinkNode head = new LinkNode(1);
head.next = new LinkNode(2);
head.next.next = new LinkNode(3);
head.next.next.next = new LinkNode(4);
head.next.next.next.next = new LinkNode(5);
LinkNode node = delTheSpecifiedReverse(head, 3);
while (node != null) {
System.out.print(node.data + "->");
node = node.next;
}
}
}
好了,我們解決了昨天文章中留下的拓展題,今天我們來看看我們連結清單都還有些怎樣的考法。
面試題:定義一個單連結清單,輸入一個連結清單的頭結點,反轉該連結清單并輸出反轉後連結清單的頭結點。為了友善,我們連結清單的 data 采用整型。
這是一道反轉連結清單的經典題,我們來屢一下思路:一個結點包含下一結點的引用,反轉的意思就是要把原來指向下一結點的引用指向上一個結點。我們可以分為下面的步驟:
- 找到目前要反轉的結點的下一個結點,并用變量儲存,因為下一次要反轉的是它,如果我們不儲存的話一定會因為前面已經反轉,導緻無法通過周遊得到這個結點;
- 然後讓目前結點的 next 引用指向上一個結點,上一個結點初始 null 因為頭結點的反轉後變成尾結點;
- 目前要反轉的結點變成下一個要比較元素的上一個結點,用變量儲存;
- 目前要比較的結點指派為之前儲存的未反轉前的下一個結點;
- 目前反轉的結點為 null 的時候,儲存的上一個結點即反轉後的連結清單頭結點。
用代碼實作就是:
public class Test08 {
private static class LinkNode {
int data;
LinkNode next;
LinkNode(int data) {
this.data = data;
}
}
private static LinkNode reverseLink(LinkNode head) {
// 上一個結點
LinkNode nodePre = null;
LinkNode next = null;
LinkNode node = head;
while (node != null) {
// 先用 next 儲存下一個要反轉的結點,不然會導緻連結清單斷裂。
next = node.next;
// 再把現在結點的 next 引用指向上一個結點
node.next = nodePre;
// 把目前結點指派給 nodePre 變量,以便于下一次指派
nodePre = node;
// 向後周遊
node = next;
}
return nodePre;
}
public static void main(String[] args) {
LinkNode head = new LinkNode(1);
head.next = new LinkNode(2);
head.next.next = new LinkNode(3);
head.next.next.next = new LinkNode(4);
head.next.next.next.next = new LinkNode(5);
LinkNode node = reverseLink(head);
while (node != null) {
System.out.print(node.data + "->");
node = node.next;
}
}
}
連結清單可以考的可真多,相信不是小夥伴都和我一樣,雲裡霧裡了,那我們今天就講到這裡,後面還要繼續考算法,你,打起精神,别睡着了。