国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

用 JavaScript 實(shí)現(xiàn)鏈表操作 - 08 Remove Duplicates

Me_Kun / 2015人閱讀

摘要:遞歸版本尾遞歸很多遞歸沒(méi)辦法自然的寫(xiě)成尾遞歸,本質(zhì)原因是無(wú)法在多次遞歸過(guò)程中維護(hù)共有的變量,這也是循環(huán)的優(yōu)勢(shì)所在。這是因?yàn)殡m然用的,但并沒(méi)有開(kāi)啟尾遞歸優(yōu)化。

TL;DR

為一個(gè)已排序的鏈表去重,考慮到很長(zhǎng)的鏈表,需要尾調(diào)用優(yōu)化。系列目錄見(jiàn) 前言和目錄 。

需求

實(shí)現(xiàn)一個(gè) removeDuplicates() 函數(shù),給定一個(gè)升序排列過(guò)的鏈表,去除鏈表中重復(fù)的元素,并返回修改后的鏈表。理想情況下鏈表只應(yīng)該被遍歷一次。

var list = 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5 -> null
removeDuplicates(list) === 1 -> 2 -> 3 -> 4 -> 5 -> null

如果傳入的鏈表為 null 就返回 null

這個(gè)解決方案需要考慮鏈表很長(zhǎng)的情況,遞歸會(huì)造成棧溢出,所以遞歸方案必須用到尾遞歸。

因?yàn)槠拗疲@里并不解釋什么是尾遞歸,想詳細(xì)了解的可以先看看 尾調(diào)用 的定義。

遞歸版本 - 非尾遞歸

對(duì)數(shù)組或者鏈表去重本身是個(gè)花樣很多的算法,但如果鏈表是已排序的,解法就單一很多了,因?yàn)橹貜?fù)的元素都是相鄰的。假定鏈表為 a -> a1 -> a2 ... aN -> b ,其中 a1aN 都是對(duì) a 的重復(fù),那么去重就是把鏈表變成 a -> b

因?yàn)檫f歸版本沒(méi)有循環(huán),所以一次遞歸操作只能減去一個(gè)重復(fù)元素,比如第一次去除 a1 ,第二次去除 a2

先看一個(gè)簡(jiǎn)單的遞歸版本,這個(gè)版本遞歸的是 removeDuplicates 自身。先取鏈表的頭結(jié)點(diǎn) head,如果發(fā)現(xiàn)它跟之后的節(jié)點(diǎn)有重復(fù),就讓 head 指向之后的節(jié)點(diǎn)(減去一個(gè)重復(fù)),然后再把 head 放入下一個(gè)遞歸里。如果沒(méi)有重復(fù),則遞歸 head 的下一個(gè)節(jié)點(diǎn),并把結(jié)果指向 head.next

function removeDuplicates(head) {
  if (!head) return null

  const nextNode = head.next
  if (nextNode && head.data === nextNode.data) {
    head.next = nextNode.next
    return removeDuplicates(head)
  }

  head.next = removeDuplicates(nextNode)
  return head
}

這個(gè)版本只有第一個(gè) return removeDuplicates(head) 處是尾遞歸,最后的 return head 并不是。所以這個(gè)解法并不算完全的尾遞歸,但性能并不算差。經(jīng)我測(cè)試可以處理 30000 個(gè)節(jié)點(diǎn)的鏈表,但 40000 個(gè)就一定會(huì)棧溢出。

遞歸版本 - 尾遞歸

很多遞歸沒(méi)辦法自然的寫(xiě)成尾遞歸,本質(zhì)原因是無(wú)法在多次遞歸過(guò)程中維護(hù)共有的變量,這也是循環(huán)的優(yōu)勢(shì)所在。上面例子中的 head.next = removeDuplicates(nextNode) 就是一個(gè)典型,我們需要保留 head 這個(gè)變量,好在遞歸結(jié)束把結(jié)果賦值給 head.next 。尾遞歸優(yōu)化的基本思路,就是把共有的變量繼續(xù)傳給下一個(gè)遞歸過(guò)程,這種做法往往需要用到額外的函數(shù)參數(shù)。下面是一個(gè)改變后的尾遞歸版本:

function removeDuplicatesV2(head, prev = null, re = null) {
  if (!head) return re

  re = re || head
  if (prev && prev.data === head.data) {
    prev.next = head.next
  } else {
    prev = head
  }

  return removeDuplicatesV2(head.next, prev, re)
}

我們加了兩個(gè)變量 prevreprev 代表 head 的前一個(gè)節(jié)點(diǎn),在遞歸過(guò)程中我們判斷的是 prevhead 是否有重復(fù)。為了最后能返回鏈表的頭我們加了 re 這個(gè)參數(shù),它是最后的返回值。re 僅僅指向最開(kāi)始的 head ,也就是第一次遞歸的鏈表的頭結(jié)點(diǎn)。因?yàn)檫@個(gè)算法是修改鏈表自身,只要鏈表非空,頭結(jié)點(diǎn)作為返回值就是確定的,即使鏈表開(kāi)頭就有重復(fù),被移除的也是頭結(jié)點(diǎn)之后的節(jié)點(diǎn)。

如何測(cè)試尾遞歸

首先我們需要一個(gè)支持尾遞歸優(yōu)化的環(huán)境。我測(cè)試的環(huán)境是 Node v7 。Node 應(yīng)該是 6.2 之后就支持尾遞歸優(yōu)化,但需要指定 harmony_tailcalls 參數(shù)開(kāi)啟,默認(rèn)并不啟動(dòng)。我用的 Mocha 寫(xiě)測(cè)試,所以把參數(shù)寫(xiě)在 mocha.opts 里,配置如下:

--use_strict
--harmony_tailcalls
--require test/support/expect.js

其次我們需要一個(gè)方法來(lái)生成很長(zhǎng)的,隨機(jī)重復(fù)的,生序排列的鏈表,我的寫(xiě)法如下:

// Usage: buildRandomSortedList(40000)
function buildRandomSortedList(len) {
  let list
  let prevNode
  let num = 1

  for (let i = 0; i < len; i++) {
    const node = new Node(randomBool() ? num++ : num)
    if (!list) {
      list = node
    } else {
      prevNode.next = node
    }
    prevNode = node
  }

  return list
}

function randomBool() {
  return Math.random() >= 0.5
}

然后就可以測(cè)試了,為了方便同時(shí)測(cè)試溢出和不溢出的情況,寫(xiě)個(gè) helper ,這個(gè) helper 簡(jiǎn)單的判斷函數(shù)是否拋出 RangeError 。因?yàn)楹瘮?shù)的邏輯已經(jīng)在之前的測(cè)試中保證了,這里就不測(cè)試結(jié)果是否正確了。

function createLargeListTests(fn, { isOverflow }) {
  describe(`${fn.name} - max stack size exceed test`, () => {
    it(`${isOverflow ? "should NOT" : "should"} be able to handle a big random list.`, () => {
      Error.stackTraceLimit = 10

      expect(() => {
        fn(buildRandomSortedList(40000))
      })[isOverflow ? "toThrow" : "toNotThrow"](RangeError, "Maximum call stack size exceeded")
    })
  })
}

createLargeListTests(removeDuplicates, { isOverflow: true })
createLargeListTests(removeDuplicatesV2, { isOverflow: false })

完整的測(cè)試見(jiàn) GitHub 。

順帶一提,以上兩個(gè)遞歸方案在 Codewars 上都會(huì)棧溢出。這是因?yàn)?Codewars 雖然用的 Node v6 ,但并沒(méi)有開(kāi)啟尾遞歸優(yōu)化。

循環(huán)版本

思路一致,就不贅述了,直接看代碼:

function removeDuplicatesV3(head) {
  for (let node = head; node; node = node.next) {
    while (node.next && node.data === node.next.data) node.next = node.next.next
  }
  return head
}

可以看到,因?yàn)檠h(huán)體外的共有變量 nodehead ,這個(gè)例子代碼比遞歸版本要簡(jiǎn)單直觀很多。

總結(jié)

循環(huán)和遞歸沒(méi)有孰優(yōu)孰劣,各有合適的場(chǎng)合。這個(gè) kata 就是一個(gè)循環(huán)比遞歸簡(jiǎn)單的例子。另外,尾遞歸因?yàn)橐獋鬟f中間變量,所以寫(xiě)起來(lái)的感覺(jué)會(huì)更類似循環(huán)而不是正常的遞歸思路,這也是為什么我對(duì)大部分 kata 沒(méi)有做尾遞歸的原因 -- 這個(gè)教程的目的是展示遞歸的思路,而尾遞歸有時(shí)候達(dá)不到這一點(diǎn)。

算法相關(guān)的代碼和測(cè)試我都放在 GitHub 上,如果對(duì)你有幫助請(qǐng)幫我點(diǎn)個(gè)贊!

參考資料

Codewars Kata
GitHub 的代碼實(shí)現(xiàn)
GitHub 的測(cè)試

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/88095.html

相關(guān)文章

  • LeetCode 攻略 - 2019 年 7 月上半月匯總(55 題攻略)

    摘要:微信公眾號(hào)記錄截圖記錄截圖目前關(guān)于這塊算法與數(shù)據(jù)結(jié)構(gòu)的安排前。已攻略返回目錄目前已攻略篇文章。會(huì)根據(jù)題解以及留言內(nèi)容,進(jìn)行補(bǔ)充,并添加上提供題解的小伙伴的昵稱和地址。本許可協(xié)議授權(quán)之外的使用權(quán)限可以從處獲得。 Create by jsliang on 2019-07-15 11:54:45 Recently revised in 2019-07-15 15:25:25 一 目錄 不...

    warmcheng 評(píng)論0 收藏0
  • LeetCode 攻略 - 2019 年 7 月下半月匯總(100 題攻略)

    摘要:月下半旬攻略道題,目前已攻略題。目前簡(jiǎn)單難度攻略已經(jīng)到題,所以后面會(huì)調(diào)整自己,在刷算法與數(shù)據(jù)結(jié)構(gòu)的同時(shí),攻略中等難度的題目。 Create by jsliang on 2019-07-30 16:15:37 Recently revised in 2019-07-30 17:04:20 7 月下半旬攻略 45 道題,目前已攻略 100 題。 一 目錄 不折騰的前端,和咸魚(yú)有什么區(qū)別...

    tain335 評(píng)論0 收藏0
  • JavaScript 實(shí)現(xiàn)鏈表操作 - 前言和目錄

    摘要:我打算寫(xiě)一個(gè)鏈表操作的系列,來(lái)自的系列,實(shí)現(xiàn)語(yǔ)言是。通過(guò)自己實(shí)現(xiàn)一個(gè)鏈表和常用操作,可以加深理解這類數(shù)據(jù)結(jié)構(gòu)的優(yōu)缺點(diǎn)。鏈表經(jīng)常用來(lái)訓(xùn)練指針操作,雖然這只對(duì)適用,但等高級(jí)語(yǔ)言中控制引用的思路其實(shí)也差不多。 TL;DR 我打算寫(xiě)一個(gè)鏈表操作的系列,來(lái)自 Codewars 的 Linked List 系列 kata ,實(shí)現(xiàn)語(yǔ)言是 JavaScript 。這篇是開(kāi)篇,簡(jiǎn)單描述了一下我寫(xiě)這個(gè)的目...

    BetaRabbit 評(píng)論0 收藏0
  • leetcode83 Remove Duplicates from Sorted List從有序鏈表

    摘要:題目要求從有序鏈表中刪除重復(fù)的數(shù)字,并且返回刪除后的頭結(jié)點(diǎn)例如輸入鏈表為返回這題和相似,只是數(shù)據(jù)結(jié)構(gòu)從數(shù)組變成了鏈表若還有更好的思路,請(qǐng)多多指教想要了解更多開(kāi)發(fā)技術(shù),面試教程以及互聯(lián)網(wǎng)公司內(nèi)推,歡迎關(guān)注我的微信公眾 題目要求: 從有序鏈表中刪除重復(fù)的數(shù)字,并且返回刪除后的頭結(jié)點(diǎn)例如輸入鏈表為1->1->2,返回1->2 這題和leetcode26相似,只是數(shù)據(jù)結(jié)構(gòu)從數(shù)組變成了鏈表 /*...

    JessYanCoding 評(píng)論0 收藏0
  • leetcode82. Remove Duplicates from Sorted List II

    摘要:題目要求翻譯將鏈表中重復(fù)的元素全部刪除,返回新的頭結(jié)點(diǎn)。相比于,這里將重復(fù)的元素全部刪除。除此以外,我們還需要知道重復(fù)元素的前一個(gè)值和重復(fù)元素的最后一個(gè)值。如果存在重復(fù)值,則跳過(guò)重復(fù)值后,前節(jié)點(diǎn)不變,否則前節(jié)點(diǎn)跟隨后節(jié)點(diǎn)同時(shí)向后移動(dòng)。 題目要求 Given a sorted linked list, delete all nodes that have duplicate number...

    崔曉明 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<