幾道常見的鏈表算法題【筆試干貨】

1. 兩數(shù)相加

題目描述

Leetcode:給定兩個非空鏈表來表示兩個非負整數(shù)。位數(shù)按照逆序方式存儲祭示,它們的每個節(jié)點只存儲單個數(shù)字肄满。將兩數(shù)相加返回一個新的鏈表。

你可以假設(shè)除了數(shù)字 0 之外,這兩個數(shù)字都不會以零開頭稠歉。

示例:

輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807

問題分析

Leetcode官方詳細解答地址:

https://leetcode-cn.com/problems/add-two-numbers/solution/

要對頭結(jié)點進行操作時掰担,考慮創(chuàng)建啞節(jié)點dummy,使用dummy->next表示真正的頭節(jié)點怒炸。這樣可以避免處理頭節(jié)點為空的邊界問題带饱。

我們使用變量來跟蹤進位,并從包含最低有效位的表頭開始模擬逐
位相加的過程阅羹。

圖1勺疼,對兩數(shù)相加方法的可視化: 342 + 465 = 807342+465=807, 每個結(jié)點都包含一個數(shù)字捏鱼,并且數(shù)字按位逆序存儲执庐。

Solution

我們首先從最低有效位也就是列表 l1和 l2 的表頭開始相加。注意需要考慮到進位的情況导梆!

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
 //https://leetcode-cn.com/problems/add-two-numbers/description/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode dummyHead = new ListNode(0);
    ListNode p = l1, q = l2, curr = dummyHead;
    //carry 表示進位數(shù)
    int carry = 0;
    while (p != null || q != null) {
        int x = (p != null) ? p.val : 0;
        int y = (q != null) ? q.val : 0;
        int sum = carry + x + y;
        //進位數(shù)
        carry = sum / 10;
        //新節(jié)點的數(shù)值為sum % 10
        curr.next = new ListNode(sum % 10);
        curr = curr.next;
        if (p != null) p = p.next;
        if (q != null) q = q.next;
    }
    if (carry > 0) {
        curr.next = new ListNode(carry);
    }
    return dummyHead.next;
}
}

2. 翻轉(zhuǎn)鏈表

題目描述

劍指 offer:輸入一個鏈表轨淌,反轉(zhuǎn)鏈表后,輸出鏈表的所有元素看尼。

翻轉(zhuǎn)鏈表

問題分析

這道算法題猿诸,說直白點就是:如何讓后一個節(jié)點指向前一個節(jié)點!在下面的代碼中定義了一個 next 節(jié)點狡忙,該節(jié)點主要是保存要反轉(zhuǎn)到頭的那個節(jié)點,防止鏈表 “斷裂”址芯。

Solution

public class ListNode {
  int val;
  ListNode next = null;

  ListNode(int val) {
    this.val = val;
  }
}
/**
 * 
 * @author Snailclimb
 * @date 2018年9月19日
 * @Description: TODO
 */
public class Solution {

  public ListNode ReverseList(ListNode head) {

    ListNode next = null;
    ListNode pre = null;

    while (head != null) {
      // 保存要反轉(zhuǎn)到頭的那個節(jié)點
      next = head.next;
      // 要反轉(zhuǎn)的那個節(jié)點指向已經(jīng)反轉(zhuǎn)的上一個節(jié)點(備注:第一次反轉(zhuǎn)的時候會指向null)
      head.next = pre;
      // 上一個已經(jīng)反轉(zhuǎn)到頭部的節(jié)點
      pre = head;
      // 一直向鏈表尾走
      head = next;
    }
    return pre;
  }

}

測試方法:

  public static void main(String[] args) {

    ListNode a = new ListNode(1);
    ListNode b = new ListNode(2);
    ListNode c = new ListNode(3);
    ListNode d = new ListNode(4);
    ListNode e = new ListNode(5);
    a.next = b;
    b.next = c;
    c.next = d;
    d.next = e;
    new Solution().ReverseList(a);
    while (e != null) {
      System.out.println(e.val);
      e = e.next;
    }
  }

輸出:

5
4
3
2
1

3. 鏈表中倒數(shù)第k個節(jié)點

題目描述

劍指offer: 輸入一個鏈表灾茁,輸出該鏈表中倒數(shù)第k個結(jié)點。

問題分析

鏈表中倒數(shù)第k個節(jié)點也就是正數(shù)第(L-K+1)個節(jié)點谷炸,知道了只一點北专,這一題基本就沒問題!

首先兩個節(jié)點/指針旬陡,一個節(jié)點 node1 先開始跑拓颓,指針 node1 跑到 k-1 個節(jié)點后,另一個節(jié)點 node2 開始跑描孟,當 node1 跑到最后時驶睦,node2 所指的節(jié)點就是倒數(shù)第k個節(jié)點也就是正數(shù)第(L-K+1)個節(jié)點。

Solution

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/

// 時間復(fù)雜度O(n),一次遍歷即可
// https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
public class Solution {
  public ListNode FindKthToTail(ListNode head, int k) {
    // 如果鏈表為空或者k小于等于0
    if (head == null || k <= 0) {
      return null;
    }
    // 聲明兩個指向頭結(jié)點的節(jié)點
    ListNode node1 = head, node2 = head;
    // 記錄節(jié)點的個數(shù)
    int count = 0;
    // 記錄k值匿醒,后面要使用
    int index = k;
    // p指針先跑场航,并且記錄節(jié)點數(shù),當node1節(jié)點跑了k-1個節(jié)點后廉羔,node2節(jié)點開始跑溉痢,
    // 當node1節(jié)點跑到最后時,node2節(jié)點所指的節(jié)點就是倒數(shù)第k個節(jié)點
    while (node1 != null) {
      node1 = node1.next;
      count++;
      if (k < 1) {
        node2 = node2.next;
      }
      k--;
    }
    // 如果節(jié)點個數(shù)小于所求的倒數(shù)第k個節(jié)點,則返回空
    if (count < index)
      return null;
    return node2;

  }
}

4. 刪除鏈表的倒數(shù)第N個節(jié)點

Leetcode:給定一個鏈表孩饼,刪除鏈表的倒數(shù)第 n 個節(jié)點髓削,并且返回鏈表的頭結(jié)點。

示例:

給定一個鏈表: 1->2->3->4->5, 和 n = 2.

當刪除了倒數(shù)第二個節(jié)點后镀娶,鏈表變?yōu)?1->2->3->5.

說明:

給定的 n 保證是有效的立膛。

進階:

你能嘗試使用一趟掃描實現(xiàn)嗎?

該題在 leetcode 上有詳細解答汽畴,具體可參考 Leetcode.

問題分析

我們注意到這個問題可以容易地簡化成另一個問題:刪除從列表開頭數(shù)起的第 (L - n + 1)個結(jié)點旧巾,其中 L是列表的長度。只要我們找到列表的長度 L忍些,這個問題就很容易解決鲁猩。

圖 1. 刪除列表中的第 L - n + 1 個元素

Solution

兩次遍歷法

首先我們將添加一個 啞結(jié)點 作為輔助,該結(jié)點位于列表頭部罢坝。啞結(jié)點用來簡化某些極端情況廓握,例如列表中只含有一個結(jié)點,或需要刪除列表的頭部嘁酿。在第一次遍歷中隙券,我們找出列表的長度 L。然后設(shè)置一個指向啞結(jié)點的指針闹司,并移動它遍歷列表娱仔,直至它到達第 (L - n) 個結(jié)點那里。我們把第 (L - n)個結(jié)點的 next 指針重新鏈接至第 (L - n + 2)個結(jié)點游桩,完成這個算法牲迫。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
// https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/
public class Solution {
  public ListNode removeNthFromEnd(ListNode head, int n) {
    // 啞結(jié)點,啞結(jié)點用來簡化某些極端情況借卧,例如列表中只含有一個結(jié)點盹憎,或需要刪除列表的頭部
    ListNode dummy = new ListNode(0);
    // 啞結(jié)點指向頭結(jié)點
    dummy.next = head;
    // 保存鏈表長度
    int length = 0;
    ListNode len = head;
    while (len != null) {
      length++;
      len = len.next;
    }
    length = length - n;
    ListNode target = dummy;
    // 找到 L-n 位置的節(jié)點
    while (length > 0) {
      target = target.next;
      length--;
    }
    // 把第 (L - n)個結(jié)點的 next 指針重新鏈接至第 (L - n + 2)個結(jié)點
    target.next = target.next.next;
    return dummy.next;
  }
}

復(fù)雜度分析:

  • 時間復(fù)雜度 O(L) :該算法對列表進行了兩次遍歷,首先計算了列表的長度 LL 其次找到第 (L - n)(L?n) 個結(jié)點铐刘。 操作執(zhí)行了 2L-n2L?n 步陪每,時間復(fù)雜度為 O(L)O(L)。
  • 空間復(fù)雜度 O(1) :我們只用了常量級的額外空間镰吵。

進階——一次遍歷法:

**鏈表中倒數(shù)第N個節(jié)點也就是正數(shù)第(L-N+1)個節(jié)點檩禾。

其實這種方法就和我們上面第四題找“鏈表中倒數(shù)第k個節(jié)點”所用的思想是一樣的。基本思路就是: 定義兩個節(jié)點 node1疤祭、node2;node1 節(jié)點先跑锌订,node1節(jié)點 跑到第 n+1 個節(jié)點的時候,node2 節(jié)點開始跑.當node1 節(jié)點跑到最后一個節(jié)點時,node2 節(jié)點所在的位置就是第 (L-n ) 個節(jié)點(L代表總鏈表長度画株,也就是倒數(shù)第 n+1 個節(jié)點)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
  public ListNode removeNthFromEnd(ListNode head, int n) {

    ListNode dummy = new ListNode(0);
    dummy.next = head;
    // 聲明兩個指向頭結(jié)點的節(jié)點
    ListNode node1 = dummy, node2 = dummy;

    // node1 節(jié)點先跑辆飘,node1節(jié)點 跑到第 n 個節(jié)點的時候,node2 節(jié)點開始跑
    // 當node1 節(jié)點跑到最后一個節(jié)點時啦辐,node2 節(jié)點所在的位置就是第 (L-n ) 個節(jié)點,也就是倒數(shù)第 n+1(L代表總鏈表長度)
    while (node1 != null) {
      node1 = node1.next;
      if (n < 1 && node1 != null) {
        node2 = node2.next;
      }
      n--;
    }

    node2.next = node2.next.next;

    return dummy.next;

  }
}

5. 合并兩個排序的鏈表

題目描述

劍指offer:輸入兩個單調(diào)遞增的鏈表蜈项,輸出兩個鏈表合成后的鏈表芹关,當然我們需要合成后的鏈表滿足單調(diào)不減規(guī)則。

問題分析

我們可以這樣分析:

  1. 假設(shè)我們有兩個鏈表 A,B紧卒;
  2. A的頭節(jié)點A1的值與B的頭結(jié)點B1的值比較侥衬,假設(shè)A1小,則A1為頭節(jié)點跑芳;
  3. A2再和B1比較轴总,假設(shè)B1小,則,A1指向B1博个;
  4. A2再和B2比較
    就這樣循環(huán)往復(fù)就行了怀樟,應(yīng)該還算好理解。

考慮通過遞歸的方式實現(xiàn)盆佣!

Solution

遞歸版本:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
//https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
       if(list1 == null){
           return list2;
       }
       if(list2 == null){
           return list1;
       }
       if(list1.val <= list2.val){
           list1.next = Merge(list1.next, list2);
           return list1;
       }else{
           list2.next = Merge(list1, list2.next);
           return list2;
       }       
   }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末往堡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子共耍,更是在濱河造成了極大的恐慌虑灰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痹兜,死亡現(xiàn)場離奇詭異穆咐,居然都是意外死亡,警方通過查閱死者的電腦和手機字旭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門庸娱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谐算,你說我怎么就攤上這事」槁叮” “怎么了洲脂?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剧包。 經(jīng)常有香客問我恐锦,道長,這世上最難降的妖魔是什么疆液? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任一铅,我火速辦了婚禮,結(jié)果婚禮上堕油,老公的妹妹穿的比我還像新娘潘飘。我一直安慰自己肮之,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布卜录。 她就那樣靜靜地躺著戈擒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艰毒。 梳的紋絲不亂的頭發(fā)上筐高,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音丑瞧,去河邊找鬼柑土。 笑死,一個胖子當著我的面吹牛绊汹,可吹牛的內(nèi)容都是我干的稽屏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼灸促,長吁一口氣:“原來是場噩夢啊……” “哼诫欠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起浴栽,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤荒叼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后典鸡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體被廓,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年萝玷,在試婚紗的時候發(fā)現(xiàn)自己被綠了嫁乘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡球碉,死狀恐怖蜓斧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睁冬,我是刑警寧澤挎春,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站豆拨,受9級特大地震影響直奋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜施禾,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一创淡、第九天 我趴在偏房一處隱蔽的房頂上張望拷沸。 院中可真熱鬧,春花似錦锈遥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至一死,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間傻唾,已是汗流浹背投慈。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冠骄,地道東北人伪煤。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像凛辣,于是被迫代替她去往敵國和親抱既。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容