5分鐘帶你領略:某跳動公司面試出鏡率最高的算法之一——虛擬十叉樹建模問題

首先,這個確實不是標題黨,接下來我保證講的都是硬幹貨。也許有人會以爲很是偏很是難很是怪,可是我要說的是,對於技術社區來說,系統知識理論的學習自有去處,我以爲社區裏面應該注入一些新的血液,分享一些有信息量的內容,而不是將明明已經整理得很是好的知識點翻來覆去地「炒現飯」並以此來佔據社區最新文章欄。程序員

好,裝B結束。如今步入正題。框架

首先擺上題目:學習

摘自leetcode,本人最近剛買vip,所以看獲得企業的出題頻率:)ui

乍一看這一題貌似毫無頭緒,什麼是字典序?如何定位這個數?沒錯,剛接觸這個題目的時候,個人腦筋裏也是一團亂麻。spa

可是我以爲做爲一個擁有聰明才智的程序員來講,最重要的能力就是迅速抽象問題、拆解問題的能力。通過一段時間的思考,個人腦筋裏仍是沒有答案。3d

哈哈。指針

言歸正傳,咱們來分析一下這個問題。code

首先,什麼是字典序?cdn

什麼是字典序?

簡而言之,就是根據數字的前綴進行排序,blog

好比10 < 9,由於10的前綴是1,比9小,

再好比112 < 12,由於112的前綴11小於12。

這樣排序下來,會跟日常的升序排序會有很是大的不一樣。先給你一個直觀的感覺,一個數乘10,或者加1,哪一個大?可能你會吃驚,後者會更大。

但其實掌握它的本質以後,你一點都不會吃驚。

問題建模

畫一個圖你就懂了。

每個節點都擁有10個孩子節點,由於做爲一個前綴 ,它後面能夠接0~9這十個數字。並且你能夠很是容易地發現,整個字典序排列也就是對十叉樹進行先序遍歷。1, 10, 100, 101, ... 11, 110 ...

回到題目的意思,咱們須要找到排在第k位的數。找到他的排位,須要搞清楚三件事情:

  1. 怎麼肯定一個前綴下全部子節點的個數?
  2. 若是第k個數在當前的前綴下,怎麼繼續往下面的子節點找?
  3. 若是第k個數不在當前的前綴,即當前的前綴比較小,如何擴大前綴,增大尋找的範圍?

接下來 ,咱們一一拆解這些問題。

理順思路

1. 肯定指定前綴下全部子節點數

如今的任務就是給定一個前綴,返回下面子節點總數。

咱們如今的思路就是用下一個前綴的起點減去當前前綴的起點,那麼就是當前前綴下的全部子節點數總和啦。

//prefix是前綴,n是上界
var getCount = (prefix, n) => {
    let cur = prefix;
    let next = prefix + 1;//下一個前綴
    let count = 0;
    //當前的前綴固然不能大於上界
    while(cur <= n) {
        count += next - cur;//下一個前綴的起點減去當前前綴的起點
        cur *= 10; 
        next *= 10;
        // 若是說剛剛prefix是1,next是2,那麼如今分別變成10和20
        // 1爲前綴的子節點增長10個,十叉樹增長一層, 變成了兩層
        
        // 若是說如今prefix是10,next是20,那麼如今分別變成100和200,
        // 1爲前綴的子節點增長100個,十叉樹又增長了一層,變成了三層
    }
    return count;//把當前前綴下的子節點和返回去。
}
複製代碼

固然,不知道你們發現一個問題沒有,當next的值大於上界的時候,那以這個前綴爲根節點的十叉樹就不是滿十叉樹了啊,應該到上界那裏,後面都再也不有子節點。所以,count += next - cur仍是有些問題的,咱們來修正這個問題:

count += Math.min(n+1, next) - cur;
複製代碼

你可能會問:咦?怎麼是n+1,而不是n呢?不是說好了n是上界嗎?

我舉個例子,倘若如今上界n爲12,算出以1爲前綴的子節點數,首先1自己是一個節點,接下來要算下面10,11,12,一共有4個子節點。

那麼若是用Math.min(n, next) - cur會怎麼樣?

這時候算出來會少一個,12 - 10加上根節點,最後只有3個。所以咱們務必要寫n+1。

如今,咱們搞定了前綴的子節點數問題。

2. 第k個數在當前前綴下

如今無非就是往子樹裏面去看。

prefix這樣處理就能夠了。

prefix *= 10
複製代碼

3.第k個數不在當前前綴下

說白了,當前的前綴小了嘛,咱們擴大前綴。

prefix ++;
複製代碼

框架搭建

整合一下剛剛的思路。

let findKthNumber = function(n, k) {
  let p = 1;//做爲一個指針,指向當前所在位置,當p==k時,也就是到了排位第k的數
  let prefix = 1;//前綴
  while(p < k) {
    let count = getCount(prefix, n);//得到當前前綴下全部子節點的個數和
    if(p + count > k) { //第k個數在當前前綴下
      prefix *= 10;
      p++; //把指針指向了第一個子節點的位置,好比11乘10後變成110,指針從11指向了110
    } else if(p + count <= k) { //第k個數不在當前前綴下
      prefix ++;
      p += count;//注意這裏的操做,把指針指向了下一前綴的起點
    }
  }
  return prefix;
};
複製代碼

完整代碼展現

/** * @param {number} n * @param {number} k * @return {number} */
var findKthNumber = function(n, k) {
  let getCount = (prefix, n) => {
    let count =  0;
    for(let cur = prefix, next = prefix + 1; cur <= n; cur *= 10, next *= 10) 
      count += Math.min(next, n+1) - cur;
    return count;
  }
  let p = 1;
  let prefix = 1;
  while(p < k) {
    let count = getCount(prefix, n);
    if(p + count > k) {
      prefix *= 10;
      p++;
    } else if(p + count <= k) {
      prefix ++;
      p += count;
    }
  }
  return prefix;
};
複製代碼

成功AC!

相關文章
相關標籤/搜索