1. 程式人生 > >常見的連結串列翻轉,位元組跳動加了個條件,面試者高呼「我太難了」| 圖解演算法

常見的連結串列翻轉,位元組跳動加了個條件,面試者高呼「我太難了」| 圖解演算法

本文首發自公眾號「承香墨影(ID:cxmyDev)」,歡迎關注。

一. 序

我又來講連結串列題了,這道題據說是來自位元組跳動的面試題。

為什麼說是「據說」呢?因為我也是看來的,覺得題目還是挺有意思,但是原作者給出的方案,我想了想覺得還有優化空間,就單獨拿出來講講。

就像本文的題目說的,這是一道關於連結串列翻轉的題。連結串列的翻轉,之前的文章也講了很多,例如:連結串列翻轉、連結串列兩兩翻轉、K 個一組翻轉連結串列。這些其實都是 leetcode 上的標準題,但是通常企業給出的面試題,多半會做一些變種,也就是加一些特殊的條件。

例如今天要講的這道題。

給定單鏈表的頭結點 head,實現一個調整連結串列的函式,從連結串列尾部開始,以 K 個結點為一組進行逆序翻轉,頭部剩餘結點不足一組時,不需要翻轉。(不能使用佇列或者棧作為輔助)

仔細讀題,像不像我們之前講到的 leetcode 第 25 題:K 個一組翻轉連結串列。

leetcode-25 是從頭結點開始,以 K 個結點一組進行翻轉。而位元組跳動這道題,是從尾結點開始。

只是多了一個從尾結點開始分組翻轉的條件,這道題的難度就增加了。

二. K個一組翻轉連結串列(頭條版)

2.1 其他人的解題思路

前面也說到,這道題是我看來的,當時是以一篇文章的形式釋出出來。

文章我就不發了,不過先了解一下他的解題思路,有助於我們思考。

他的思路很清晰,雖然這道題他不會解,但是 leetcode-25 這個標準的以 K 個一組翻轉連結串列的題他很熟悉。

那麼可以先將原始連結串列,進行一次「連結串列翻轉」,再進行「K 個一組翻轉連結串列」,最後再做一次「連結串列翻轉」還原連結串列,就得出了需要的結果。

ListNode revserseKGroupPlus(ListNode head, int k) {
  // 翻轉連結串列
  head = reverseList(head);
  // K 個一組翻轉連結串列
  head = reverseKGroup(head, k);
  // 翻轉連結串列
  head = reverseList(head);
  return head;
}

把一個不熟悉的問題,經過簡單的轉換,變成熟悉的問題進行解決,這種思路是沒有錯的。

但是呢,有個問題--

在面試的場景中,通常來說,面試官的水平會高於面試者,那麼我們可以簡單的理解,面試就是一個不斷受挫的過程,這個過程總會被問到我們知識的邊界才會停止。

面試題只是起點,面試過程中深挖的哪些問題,才是觸控到我們談薪資本的核心。當然這扯遠了,繼續回到本文的內容。

此時就算面試者當場寫出瞭解題程式碼,也逃不開一個經典問題。

面試官:「還有更優的方案嗎?」

那麼這道題,有沒有更優的方案?答案當然是有的。

2.2 更優一點的方案

將連結串列先翻轉後處理,再翻轉回去,這樣並不優雅,其實只需一次以 K 個一組翻轉連結串列就可以。

再回憶一下 leetcode 第 25 題,它和這道題的差異,主要來自於,對不足一組的連結串列結點的處理。leetcode-25 是從頭結點開始處理,所以多出來的結點會在尾部,而位元組跳動這道題則正好相反,餘下的結點會在頭部。

但是它們同時也有一種特殊情況,就是 K 個一組進行分組時,這裡的 K 正好可以完整的分組,一個不多,一個不少的分成 N 組。

當連結串列結點數量正好為 K * N 時,那麼又回到了我們熟悉的 leetcode-25 題了。

如果我們先將原始結點進行處理,找出它正好可以整除 K 的起始結點 offset,將這個起始結點 offset 的子連結串列,再進行 K 個一組進行翻轉連結串列,最後把它拼接回原始連結串列,就完成了這道題。

這個過程,需要額外定義兩個結點,第一個滿足 K 個分組條件的 offset 結點,以及 offset 的前驅結點 prev 結點,prev 結點主要是用來拼接翻轉後的兩個連結串列,讓其不會出現連結串列斷裂的問題。

它們的關係如下:

這其中還涉及到一些簡單的連結串列運算,例如求連結串列的長度,這裡就不展開說了,直接上核心程式碼,邏輯都在註釋裡,我們先定義一個 reverseKGroupPlus() 方法。

public ListNode reverseKGroupPlus(ListNode head, int k) {
    if (head == null || k <= 1) return head;
    // 計算原始連結串列長度
    int length = linkedLength(head);
    if (length < k) 
        return head;
  
    // 計算 offset
    int offsetIndex = length % k;

    // 原始連結串列正好可以由 K 分為 N 組,可直接處理
    if (offsetIndex == 0) {
        return reverseKGroup(head, k);
    }

    // 定義並找到 prev 和 offset
    ListNode prev = head, offset = head;
    while (offsetIndex > 0) {
        prev = offset;
        offset = offset.next;
        offsetIndex--;
    }
    // 將 offset 結點為起始的子連結串列進行翻轉,再拼接回主連結串列
    prev.next = reverseKGroup(offset, k);
    return head;
}

注意當連結串列長度正好可以用 K 分為 N 組時,我們直接處理,否者才需要後續複雜的邏輯。

程式碼的註釋足夠清晰了,在腦子裡過一遍程式碼的執行流程應該能明白,為了幫助大家理解,我又畫了個示意圖。

假設以 head 為頭結點的連結串列長度是 10,K 為 4 時,那麼計算下來 offset Index 就是 2。

找到 prev 和 offset 結點後,就可以將以 offset 結點為頭結點的子連結串列,進行 K 個一組翻轉連結串列的操作了。

此時,head 結點為起始的連結串列,就是我們計算後的結果。

2.3 再補一些額外的程式碼

這道題,還涉及到很多其他的小演算法,本身 leetcode-25 就已經被定級為「困難」,位元組跳動在這道題的基礎上,又增加了難度。

為了保證解題的完整,這裡再補充一些相關程式碼。

1. 計算連結串列長度

private int linkedLength(ListNode head) {
    int count = 0;
    while (head != null) {
        count++;
        head = head.next;
    }
    return count;
}

沒什麼好說的,一個 while 迴圈搞定。

2. 以 K 個一組翻轉連結串列

這道題在之前的文章中詳細講解了,這裡直接貼程式碼了。

public ListNode reverseKGroup(ListNode head, int k) {
    // 增加虛擬頭結點
    ListNode dummy = new ListNode(0);
    dummy.next = head;

    // 定義 prev 和 end 結點
    ListNode prev = dummy;
    ListNode end = dummy;

    while(end.next != null) {
      // 以 k 個結點為條件,分組子連結串列
      for (int i = 0; i < k && end != null; i++)
        end = end.next;
      // 不足 K 個時不處理
      if (end == null)
        break;
      // 處理子連結串列
      ListNode start = prev.next;
      ListNode next = end.next;
      end.next = null;
      // 翻轉子連結串列
      prev.next = reverseList(start);
      // 將子連表前後串起來
      start.next = next;
      prev = start;
      end = prev;
    }
    return dummy.next;
}

// 遞迴完成單鏈表翻轉
private ListNode reverseList(ListNode head) {
    if (head == null || head.next == null)
      return head;
    ListNode p = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return p;
}

對於 leetcode-25 這道題,還不太瞭解的可以看看之前的文章《K 個一組翻轉連結串列》。

三. 小結時刻

以上就是我解這道題的思路,可能不是最高效的,但也算是比較清晰。

在面試過程中,連結串列相關的題目可以說是高頻題。雖然企業在出題時,為了增加難度也會做一些變種,但是作為面試者,無論如何都避不開多練多寫多想。

你有更好的方案嗎?你在面試中有碰到什麼奇葩的演算法題嗎?歡迎在留言區討論。

本文對你有幫助嗎?留言、轉發、收藏是最大的支援,謝謝!


公眾號後臺回覆成長『成長』,將會得到我準備的學習資料,也能回覆『加群』,一起學習進步。

相關推薦

常見連結串列翻轉位元組跳動條件面試高呼| 圖解演算法

本文首發自公眾號「承香墨影(ID:cxmyDev)」,歡迎關注。 一. 序 我又來講連結串列題了,這道題據說是來自位元組跳動的面試題。 為什麼說是「據說」呢?因為我也是看來的,覺得題目還是挺有意思,但是原作者給出的方案,我想了想覺得還有優化空間,就單獨拿出來講講。 就像本文的題目說的,這是一道關於

已知兩連結串列A和B分別表示兩集合其元素遞增排列。請設計一個演算法用於求出A與B的交集並存放在A連結串列中。

語言:C++ #include <iostream> using namespace std; typedef struct LNode { int data; LNode *next; }LNode,*LinkList; //建立連結串列 int CreateList(Li

小林求職記(三)一上來就圍繞電商系統層層提問….

前傳   面試官:什麼是大事務?小林哥:就是 很大...的...事務??   小林求職記(二):說好的問基礎,為啥我感覺一點也不基礎呢?   二面的面試官來到來我的跟前,開始對我的簡歷進行了一番打量然後就開始了技術提問。 面試官: 看了下你在簡歷上邊有寫到過關於電商系

api介面返回動態的json格式?嘗試一下 linq to json

## 一:背景 ### 1. 講故事 前段時間和一家公司聯調api介面的時候,發現一個奇葩的問題,它的api返回的json會動態改變,簡化如下: ``` json {"Code":101,"Items":[{"OrderTitle":"訂單1"}]} {"Code":102,"Items":[

leetcode+ 連結串列翻轉注意事項都註釋

點選開啟連結class Solution { public: ListNode* reverseList(ListNode* head) { if(!head) return N

連結串列翻轉【比如連結串列1→2→3→4→5→6k=2 翻轉後2→1→4→3→6→】

2.【附加題】–1、連結串列翻轉,給出一個連結串列和一個數k,比如連結串列1→2→3→4→5→6,k=2, 翻轉後2→1→4→3→6→5,若k=3,翻轉後3→2→1→6→5→4,若k=4,翻轉後4→3→2→1→5→6, 用程式實現Node* RotateLi

請編寫一個函式使其可以刪除某個連結串列中給定的(非末尾)節點你將只被給定要求被刪除的節點

今天給大家分享一個小題目,如下: 請編寫一個函式,使其可以刪除某個連結串列中給定的(非末尾)節點,你將只被給定要求被刪除的節點。 現有一個連結串列 -- head = [4,5,1,9],它可以表示為: 4 -> 5 -> 1 -> 9

證明一個環狀連結串列(首尾相連)的兩指標head1和head2 從同一個節點出發head1每次走一步 head2 每次走兩步他們第一次相遇於出發的節點

一個環狀連結串列(收尾相連),兩個指標 head1和head2 從同一個節點出發,head1每次走一步, head2 每次走兩步,請證明,兩個指標第一次相遇於出發的節點。 設兩個指標走的次數為 x,使用簡單的數學公式即可證明。難度 1 面。考察基本的數學 知識。 設連結串列有 m 個元素,head1

位元組跳動大資料中心17萬伺服器硬實力支撐今日頭條等產品線(公號回覆“位元組跳動”下載PDF典型資料歡迎轉發、讚賞支援科普)

位元組跳動大資料中心17萬伺服器硬實力支撐今日頭條等產品線(公號回覆“位元組跳動”下載PDF典型資料,歡迎轉發、讚賞支援科普) 原創: 秦隴紀 科學Sciences 昨天 科學Sciences導讀:北京位元組跳動有限公司大資料中心以17萬臺伺服器的硬實力,支撐起今日頭條、抖音、西瓜視

連結串列與遞迴/連結串列翻轉-LeetCode25-k一組翻轉連結串列

題目 給出一個連結串列,每 k 個節點一組進行翻轉,並返回翻轉後的連結串列。 k 是一個正整數,它的值小於或等於連結串列的長度。如果節點總數不是 k 的整數倍,那麼將最後剩餘節點保持原有順序。 示例 : 給定這個連結串列:1->2->3->4->5 當 k = 2

linux下執行連結串列棧(實現棧的基本功能 pushpop刪除任意結點遍歷輸出等)

一、簡要敘述設計思想和技術路線(不少於300字)(20分)。 設計思想:利用Linux GNU make C 專案管理軟體工具實現資料結構棧(Stack)。實現Push,Pop,Delete,Search,Visit through,Clear功能。節點的資料設計具有一般性(使用void *da

連結串列翻轉的圖文講解(遞迴與迭代(直接迴圈翻轉指標)兩種實現)【轉】

連結串列的翻轉是程式設計師面試中出現頻度最高的問題之一,常見的解決方法分為遞迴和迭代兩種。最近在複習的時候,發現網上的資料都只告訴了怎麼做,但是根本沒有好好介紹兩種方法的實現過程與原理。所以我覺得有必要好好的整理一篇博文,來幫忙大家一步步理解其中的實現細節。   我們知道

常見連結串列操作-刪除連結串列倒數第n節點(JAVA實現)

問題 給出一個單向連結串列,刪除該連結串列倒數第n個節點,並返回頭節點。 例如: 給出連結串列 1->2->3->4->5,n=2 返回連結串列 1->2->3->5 解題思路 最容易想到的演算法: 先遍歷一次連結串列,

常見連結串列操作-求連結串列的中間節點(JAVA實現)

問題 給出任意單向連結串列,找出並返回該連結串列的中間節點。 奇數長度的連結串列,例如:1->2->3->4->5 返回節點 3 偶長度的連結串列,例如:1->2->3->4->5->6 返回節點 4 解題思

LeetCode 連結串列翻轉相關(24 25)

思路 連結串列反轉這種題,上個星期我還是不會做的,但是自從學會了前插法,寫起來就遊刃有餘的。 下面介紹下前插法 新建一個空節點作為反轉連結串列的表頭 將待反轉連結串列的頭結點取出,用一個指標指向頭結點的下一個結點,然後將頭結點指向新連結串列的表頭 新連結串列的

C++ 單鏈表基本操作分析與實現 連結串列   連結串列是一種物理儲存單元上非連續、非順序的儲存結構資料元素的邏輯順序是通過連結串列中的指標連結次序實現的。連結串列由一系列結點(連結串列中每一個元素稱為結點)組成

連結串列   連結串列是一種物理儲存單元上非連續、非順序的儲存結構,資料元素的邏輯順序是通過連結串列中的指標連結次序實現的。連結串列由一系列結點(連結串列中每一個元素稱為結點)組成,結點可以在執行時動態生成。每個結點包括兩個部分:一個是儲存資料元素的資料域,另一個是儲存下一個結點地址的指標域。 相比於線性表

java實現單向連結串列CRUD反轉,排序查詢倒數第k元素遞迴輸出等操作

package myLink; import javax.xml.transform.Templates; public class LianBiao { static Node head=null; /** * 查詢單鏈表的中間節

以二叉連結串列的方式建立一棵二叉樹並以非遞迴演算法中序輸出;計算二叉樹的繁茂度並判斷二叉樹是否為完全二叉樹

以二叉連結串列的方式存二叉樹,輸入時要以先序方式輸入,其中,空子樹用#表示。 二叉樹的繁茂度定義為其高度乘其每層結點最大值。演算法為先用遞迴演算法求二叉樹高度:其高度為左右子樹最大值加1,所以用先序遍歷,定義ld與rd分別為左右子樹高度,最後返回其較大值加1即可。二叉樹寬度