寶寶也能看懂的 leetcode 周賽 - 174 - 1

1337. 方陣中戰鬥力最弱的 K 行

Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列之 leetcode 周賽題解。git

這裏是第 174 期的第 1 題,也是題目列表中的第 1337 題 -- 『方陣中戰鬥力最弱的 K 行』github

題目描述

給你一個大小爲 m * n 的方陣 mat,方陣由若干軍人和平民組成,分別用 01 表示。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 就是 1

官方難度

EASY排序

解決思路

題目把簡單的內容進行了一點包裝,不過相信小夥伴們也很容易能看出來。什麼,還有寶寶沒看出來?那就讓小豬來揭開它神祕的頭蓋骨吧! yeah~

首先是給定的數據是一個二維數組,其中每一行裏有士兵(用 1 表示)和平民(用 0 表示),而且士兵必定是在平民前面。這句話背後透露了幾個信息:

  • 數據只有 0 和 1,而且一行的士兵數量其實就是這一行值求和的結果。
  • 若是一個位置是平民,那麼士兵的數量必定小於這個值。即這一行的數據是有序的。

而後咱們再看,需求是要返回戰鬥力排名前 k 的行的序號。也就是說,咱們須要按照每行的戰鬥力進行排序,而戰鬥力就是士兵的數量。那麼結合上面的信息,咱們直接的思路就很清晰了。

直接方案

根據上面的分析,咱們能夠很容易的獲得直接方案的流程以下:

  1. 對每一行的數據求和,連同序號一塊兒放進新的數組。
  2. 按照要求對該數組進行排序。
  3. 返回前 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) 級別,也就是利用二分查找。

具體流程以下:

  1. 利用二分查找,尋找每一行第一個 0 的位置,並把它和序號一塊兒放進新的數組。
  2. 按照要求對該數組進行排序。
  3. 返回前 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 個這樣的值。

不過有一點須要注意的是,因爲可能會出現多個戰鬥力全滿的行,因此最後還須要再處理一下這種狀況。

具體流程以下:

  1. 一列一列的遍歷原始數據。
  2. 若是遇到出現了 0,而且是在沒有被訪問過的行,那麼把行號放進結果,並記錄這一行已經被訪問了。
  3. 處理可能的戰鬥力全滿的狀況。

基於這個流程,咱們能夠實現相似下面的代碼:

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;
    }
  }
};

總結

周賽第一題慣例送分。小豬這裏提到了不一樣的思路和一些小優化,不太重點仍是最開始根據題目描述獲得的信息,由於後面全部的內容都是基於前面的信息想到的。

相關連接

qrcode_green.jpeg

相關文章
相關標籤/搜索