Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列之 leetcode 周賽題解。git
這裏是第 170 期的第 3 題,也是題目列表中的第 1311 題 -- 『獲取你好友已觀看的視頻』github
有 n
我的,每一個人都有一個 0
到 n-1
的惟一 id。算法
給你數組 watchedVideos
和 friends
,其中 watchedVideos[i]
和 friends[i]
分別表示 id = i
的人觀看過的視頻列表和他的好友列表。shell
Level 1 的視頻包含全部你好友觀看過的視頻,level 2 的視頻包含全部你好友的好友觀看過的視頻,以此類推。通常的,Level 爲 k 的視頻包含全部從你出發,最短距離爲 k 的好友觀看過的視頻。segmentfault
給定你的 id
和一個 level
值,請你找出全部指定 level
的視頻,並將它們按觀看頻率升序返回。若是有頻率相同的視頻,請將它們按名字字典序從小到大排列。數組
示例 1:數據結構
輸入:watchedVideos = [["A","B"],["C"],["B","C"],["D"]], friends = [[1,2],[0,3],[0,3],[1,2]], id = 0, level = 1 輸出:["B","C"] 解釋: 你的 id 爲 0 ,你的朋友包括: id 爲 1 -> watchedVideos = ["C"] id 爲 2 -> watchedVideos = ["B","C"] 你朋友觀看過視頻的頻率爲: B -> 1 C -> 2
示例 2:ide
輸入:watchedVideos = [["A","B"],["C"],["B","C"],["D"]], friends = [[1,2],[0,3],[0,3],[1,2]], id = 0, level = 2 輸出:["D"] 解釋: 你的 id 爲 0 ,你朋友的朋友只有一我的,他的 id 爲 3 。
提示:函數
n == watchedVideos.length == friends.length
2 <= n <= 100
1 <= watchedVideos[i].length <= 100
1 <= watchedVideos[i][j].length <= 8
0 <= friends[i].length < n
0 <= friends[i][j] < n
0 <= id < n
1 <= level < n
friends[i]
包含 j
,那麼 friends[j]
包含 i
MEDIUMspa
這道題一眼看上去還挺嚇人,一堆參數,很長的限制條件,連例子都好長。然而仔細想一下就會發現,其實都是紙腦撫,哼,小豬纔不會被你騙到呢,略略略 >.<
題目的意思就是根據給定的初始 id
,去尋找他第 level
輪的朋友們,從而獲得朋友們看過的視頻列表。最終把這個列表進行排序返回。那麼總的來講就分爲兩部分,第一部分就是找到最終的目標朋友列表,第二部分就是把視頻列表按要求進行排序返回。
咱們先看第一部分,這裏的流程其實很是清晰:
id
尋找到朋友列表。level
輪便可終止。看到這個流程,結合咱們前幾期的題解,相信小夥們應該很容易能想到一個思路,那就是廣度優先遍歷。要是沒想到的話,哼,罰你關注個人公衆號,略略略 >.<
關於廣度優先遍歷,我後續關於算法和數據結構的新坑會詳細說明。這裏能夠先結合思路和下面的代碼,或者翻翻我以前的題解,應該就能明白。不過這裏有個小坑小夥伴們須要注意,那就是第 level
輪的好友指的是最短距離爲 level
的好友。例如你的好友是 A、B、C,而 A 的好友是 C、D,B 的好友是 E,C 的好友是 A、F。那麼這裏獲得的你的第一輪好友列表是 A、B、C,而第二輪好友列表是 D、E、F,並無 A、C,由於他們已經在第一輪中出現了,在第二輪中並非最短距離了。
這裏給出一個比較容易理解的模板化的代碼,固然咱們爲了處理最短距離問題,加入一個 visited
用以進行標識。
const visited = new Uint8Array(friends.length); let queue = [id]; visited[id] = 1; while (level--) { const next = []; for (const id of queue) { for (const newId of friends[id]) { if (visited[newId] === 0) { next.push(newId); visited[newId] = 1; } } } queue = next; }
題目中對於這個排序規則作了幾點要求:
那麼咱們這裏的處理流程就比較明晰了:
前者能夠基於 Map
很容易的實現,後者因爲 JS 的數組 sort
能夠支持自定義的排序函數,因此也很容易實現。具體代碼以下:
// 統計觀看次數 const freq = new Map(); for (const id of queue) { for (const video of watchedVideos[id]) { freq.set(video, freq.has(video) ? freq.get(video) + 1 : 1); } } // 自定義排序函數 (a, b) => a[1] === b[1] ? (a[0] > b[0] ? 1 : -1) : a[1] - b[1];
結合上述兩部份內容,咱們很容易能獲得相似下面的最終代碼:
const watchedVideosByFriends = (watchedVideos, friends, id, level) => { const visited = new Uint8Array(friends.length); let queue = [id]; visited[id] = 1; while (level--) { const next = []; for (const id of queue) { for (const newId of friends[id]) { if (visited[newId] === 0) { next.push(newId); visited[newId] = 1; } } } queue = next; } const freq = new Map(); for (const id of queue) { for (const video of watchedVideos[id]) { freq.set(video, freq.has(video) ? freq.get(video) + 1 : 1); } } return Array.from(freq.entries()).sort((a, b) => { return a[1] === b[1] ? (a[0] > b[0] ? 1 : -1) : a[1] - b[1] }).map(item => item[0]); };
這段代碼跑出了 128ms,暫時 beats 100%。
這道題目總體比較直白,沒有太多能夠分析的部分。不過因爲它的流程其實比較明顯的能夠拆分紅獨立的兩部分分別進行實現,這樣每一部分的邏輯和實現都會更加清晰和容易。對應的,咱們在實際工做開發中,其實把 feature 和 function 進行合理的拆分也是會頗有利於整理和維護的。