LeetCode探索(49):846-一手顺子

140 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

题目

Alice 手中有一把牌,她想要重新排列这些牌,分成若干组,使每一组的牌数都是 groupSize ,并且由 groupSize 张连续的牌组成。

给你一个整数数组 hand 其中 hand[i] 是写在第 i 张牌,和一个整数 groupSize 。如果她可能重新排列这些牌,返回 true ;否则,返回 false

示例 1:

输入:hand = [1,2,3,6,2,3,4,7,8], groupSize = 3
输出:true
解释:Alice 手中的牌可以被重新排列为 [1,2,3][2,3,4][6,7,8]

示例 2:

输入:hand = [1,2,3,4,5], groupSize = 4
输出:false
解释:Alice 手中的牌无法被重新排列成几个大小为 4 的组。

提示:

  • 1 <= hand.length <= 10^4
  • 0 <= hand[i] <= 10^9
  • 1 <= groupSize <= hand.length

思考

这是一道有意思的题目,难度中等。

首先是读懂题意:一个顺子中会有一张最小的牌。把一副牌的一个顺子去掉以后,又会有一个新的顺子,该顺子中又会有最小的牌,如此反复。我们在分组后需要满足所有分组都是顺子,否则返回false。

我们可以假设这些牌中最小的数字是x,那么其所在组的数字范围是[x, x+groupSize-1]。对其余牌继续使用贪心的策略分组,直至所有的牌分组结束或者无法完成分组。这就是"贪心"策略。

具体来说,我们可以对数组从小到大排序,并借助哈希表计数,然后,利用2层for循环进行遍历,其中外层for循环控制一个顺子的第一张牌,而内层for循环遍历顺子的其他牌。如果内层for循环碰到不满足顺子条件的牌时,返回false,否则,一直遍历下去,直至遍历完所有牌,此时返回true。在内层for循环中,每次遍历时我们需要及时地更新哈希表的计数。

解答

方法一:贪心

/**
 * @author 觅迹寻踪
 * @param {number[]} hand
 * @param {number} groupSize
 * @return {boolean}
 */
var isNStraightHand = function(hand, groupSize) {
  // 隐含条件
  if (hand.length % groupSize !== 0) {
    return false
  }
  // 排序
  hand.sort((a, b) => a - b)
  // 计数
  let map = new Map()
  for (const num of hand) {
    map.set(num, (map.get(num) || 0) + 1)
  }
  // 2层for循环
  for (const num of hand) {
    // 跳过内层for循环
    if (!map.has(num)) {
      continue
    }
    for (let j = 0; j < groupSize; j++) {
      let cur = num + j
      if (!map.has(cur)) {
        return false
      }
      map.set(cur, map.get(cur) - 1)
      if (map.get(cur) === 0) {
        map.delete(cur)
      }
    }
  }
  return true
}
// 执行用时:104 ms, 在所有 JavaScript 提交中击败了86.45%的用户
// 内存消耗:51.3 MB, 在所有 JavaScript 提交中击败了6.51%的用户
// 通过测试用例:84 / 84

复杂度分析

  • 时间复杂度:O(nlogn),其中 n 是数组 hand 的长度。对数组 hand 排序需要 O(nlogn) 的时间,排序之后遍历数组 hand 两次,每次遍历的时间复杂度都是 O(n),因此总时间复杂度是 O(nlogn)。
  • 空间复杂度:O(n),其中 n 是数组 hand 的长度。哈希表中最多存储 n 个元素。

参考