Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列之 leetcode 周賽題解。git
這裏是第 174 期的第 1 題,也是題目列表中的第 1337 題 -- 『方陣中戰鬥力最弱的 K 行』github
給你一個大小爲 m * n
的方陣 mat
,方陣由若干軍人和平民組成,分別用 0
和 1
表示。shell
請你返回方陣中戰鬥力最弱的 k
行的索引,按從最弱到最強排序。segmentfault
若是第 i
行的軍人數量少於第 j
行,或者兩行軍人數量相同但 i
小於 j
,那麼咱們認爲第 i
行的戰鬥力比第 j
行弱。數組
軍人 老是 排在一行中的靠前位置,也就是說 1 老是出如今 0 以前。優化
示例 1:spa
輸入:mat = [[1,1,0,0,0], [1,1,1,1,0], [1,0,0,0,0], [1,1,0,0,0], [1,1,1,1,1]], k = 3 輸出:[2,0,3] 解釋: 每行中的軍人數目: 行 0 -> 2 行 1 -> 4 行 2 -> 1 行 3 -> 2 行 4 -> 5 從最弱到最強對這些行排序後獲得 [2,0,3,1,4]
示例 2:code
輸入:mat = [[1,0,0,0], [1,1,1,1], [1,0,0,0], [1,0,0,0]], k = 2 輸出:[0,2] 解釋: 每行中的軍人數目: 行 0 -> 1 行 1 -> 4 行 2 -> 1 行 3 -> 1 從最弱到最強對這些行排序後獲得 [0,2,3,1]
提示:blog
m == mat.length
n == mat[i].length
2 <= n, m <= 100
1 <= k <= m
matrix[i][j]
不是 0 就是 1EASY排序
題目把簡單的內容進行了一點包裝,不過相信小夥伴們也很容易能看出來。什麼,還有寶寶沒看出來?那就讓小豬來揭開它神祕的頭蓋骨吧! yeah~
首先是給定的數據是一個二維數組,其中每一行裏有士兵(用 1 表示)和平民(用 0 表示),而且士兵必定是在平民前面。這句話背後透露了幾個信息:
而後咱們再看,需求是要返回戰鬥力排名前 k
的行的序號。也就是說,咱們須要按照每行的戰鬥力進行排序,而戰鬥力就是士兵的數量。那麼結合上面的信息,咱們直接的思路就很清晰了。
根據上面的分析,咱們能夠很容易的獲得直接方案的流程以下:
k
個的須要。基於這個流程,咱們能夠實現相似下面的代碼:
const kWeakestRows = (mat, k) => { const m = mat.length; const n = mat[0].length; const ret = []; for (let i = 0; i < m; ++i) { let cur = 0; for (let j = 0; j < n; ++j, ++cur) { if (mat[i][j] === 0) break; } ret.push([cur, i]); } return ret .sort((a, b) => a[0] === b[0] ? a[1] - b[1] : a[0] - b[0]) .slice(0, k) .map(item => item[1]); };
上面的方案其實咱們只用到了信息中的第一條。那麼第二條信息,士兵必定在平民左邊,每一行是有序的,這個咱們該如何利用呢?咱們能夠想象一下,結合這一條信息,若是咱們知道了最後一個士兵的位置,是否是就已經知道了士兵的數量?而在一個有序數組中,尋找一個目標值通用的最快的方式應該能夠從 O(n) 變成 O(logn) 級別,也就是利用二分查找。
具體流程以下:
k
個的須要。基於這個流程,咱們能夠實現相似下面的代碼:
const kWeakestRows = (mat, k) => { const m = mat.length; const n = mat[0].length; const rows = []; const ret = new Uint8Array(k); for (let i = 0; i < m; ++i) { rows.push([search(mat[i], 0, n), i]); } rows.sort((a, b) => a[0] === b[0] ? a[1] - b[1] : a[0] - b[0]); for (let i = 0; i < k; ++i) { ret[i] = rows[i][1]; } return ret; function search(arr, left, right) { if (left === right) return left; const mid = Math.floor((left + right) / 2); return arr[mid] === 0 ? search(arr, left, mid) : search(arr, mid + 1, right); } };
咱們再試試從另一個角度看這個問題。根據咱們以前獲得的信息,若是一個位置出現了平民,那麼它的右邊就再也不會有士兵了,也就是和所它的戰鬥力已經被肯定了,也就是說其實它在咱們上面排序中的位置也就已經肯定了。
那麼基於這個思路,咱們來縱向的看一下數據,即一列一列的看。咱們會發現,當咱們在某一列遇到某行第一次出現 0 的時候,它其實就是咱們目前狀態下的最小戰鬥力。而咱們最終須要的其實就是前 k
個這樣的值。
不過有一點須要注意的是,因爲可能會出現多個戰鬥力全滿的行,因此最後還須要再處理一下這種狀況。
具體流程以下:
基於這個流程,咱們能夠實現相似下面的代碼:
const kWeakestRows = (mat, k) => { const m = mat.length; const n = mat[0].length; const ret = new Uint8Array(k); const visited = new Uint8Array(m); let idx = 0; for (let i = 0; i < n; ++i) { for (let j = 0; j < m; ++j) { if (visited[j] === 0 && mat[j][i] === 0) { ret[idx] = j; visited[j] = 1; if (++idx === k) return ret; } } } for (let i = 0; i < m; ++i) { if (visited[i] === 0) { ret[idx] = i; if (++idx === k) return ret; } } };
周賽第一題慣例送分。小豬這裏提到了不一樣的思路和一些小優化,不太重點仍是最開始根據題目描述獲得的信息,由於後面全部的內容都是基於前面的信息想到的。