一起养成写作习惯!这是我参与「掘金日新计划 · 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^40 <= hand[i] <= 10^91 <= 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 个元素。