|
| 1 | +## 题目地址(1128. 等价多米诺骨牌对的数量) |
| 2 | + |
| 3 | +https://leetcode-cn.com/problems/number-of-equivalent-domino-pairs/ |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +
|
| 9 | +给你一个由一些多米诺骨牌组成的列表 dominoes。 |
| 10 | +
|
| 11 | +如果其中某一张多米诺骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌,我们就认为这两张牌是等价的。 |
| 12 | +
|
| 13 | +形式上,dominoes[i] = [a, b] 和 dominoes[j] = [c, d] 等价的前提是 a==c 且 b==d,或是 a==d 且 b==c。 |
| 14 | +
|
| 15 | +在 0 <= i < j < dominoes.length 的前提下,找出满足 dominoes[i] 和 dominoes[j] 等价的骨牌对 (i, j) 的数量。 |
| 16 | +
|
| 17 | + |
| 18 | +
|
| 19 | +示例: |
| 20 | +
|
| 21 | +输入:dominoes = [[1,2],[2,1],[3,4],[5,6]] |
| 22 | +输出:1 |
| 23 | + |
| 24 | +
|
| 25 | +提示: |
| 26 | +
|
| 27 | +1 <= dominoes.length <= 40000 |
| 28 | +1 <= dominoes[i][j] <= 9 |
| 29 | +
|
| 30 | +
|
| 31 | +``` |
| 32 | + |
| 33 | +## 前置知识 |
| 34 | + |
| 35 | +- 组合计数 |
| 36 | + |
| 37 | +## “排序” + 计数 |
| 38 | + |
| 39 | +### 思路 |
| 40 | + |
| 41 | +我们可以用一个哈希表存储所有的 [a,b] 对的计数信息。为了让形如 [3,4] 和 [4,3]被算到一起,我们可以对其进行排序处理。由于 dominoe 长度固定为 2,因此只需要**判断两者的大小并选择性交换即可**。 |
| 42 | + |
| 43 | +接下来,可以使用组合公式 $$C_{n}^{2}$$ 计算等价骨牌对的数量,其中 n 为单个骨牌的数量。 比如**排序后** [3,4] 骨牌对有 5 个,那么 n 就是 5,由 [3,4]构成的等价骨牌对的数量就是 $$C_{5}^{2} = 5\times(5-1)\div2 = 10$$ 个。 |
| 44 | + |
| 45 | +### 代码 |
| 46 | + |
| 47 | +代码支持:Python3 |
| 48 | + |
| 49 | +Python3 Code: |
| 50 | + |
| 51 | +```python |
| 52 | +class Solution: |
| 53 | + def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int: |
| 54 | + n = len(dominoes) |
| 55 | + cnt = 0 |
| 56 | + cntMapper = dict() |
| 57 | + |
| 58 | + for a, b in dominoes: |
| 59 | + k = str(a) + str(b) if a > b else str(b) + str(a) |
| 60 | + cntMapper[k] = cntMapper.get(k, 0) + 1 |
| 61 | + for k in cntMapper: |
| 62 | + v = cntMapper[k] |
| 63 | + if v > 1: |
| 64 | + cnt += (v * (v - 1)) // 2 |
| 65 | + return cnt |
| 66 | + |
| 67 | +``` |
| 68 | + |
| 69 | +**复杂度分析** |
| 70 | + |
| 71 | +令 N 为数组长度。 |
| 72 | + |
| 73 | +- 时间复杂度:$$O(N)$$ |
| 74 | +- 空间复杂度:$$O(N)$$ |
| 75 | + |
| 76 | +## 状态压缩 + 一次遍历 |
| 77 | + |
| 78 | +### 思路 |
| 79 | + |
| 80 | +观察到题目给的数据范围是 `1 <= dominoes[i][j] <= 9`。这个数字很小,很容易让人想到状态压缩。关于状态压缩这部分可以看我之前写过的题解[状压 DP 是什么?这篇题解带你入门](https://mp.weixin.qq.com/s/ecxTTrRvUJbdWwSFbKgDiw "状压 DP 是什么?这篇题解带你入门") |
| 81 | + |
| 82 | +由于数字不会超过 9,因此使用一个 5 bit 表示就足够了。 |
| 83 | + |
| 84 | +我们可以用两个 5 bit 分别表示 a 和 b,即一共 10 个 bit 就够了。10 个 bit 一共最多 1024 种状态,因此使用一个 1024 大小的数组是足够的。 |
| 85 | + |
| 86 | +上面代码我们是先进行一次遍历,求出计数信息。然后再次遍历计算总和。实际上,我们可以将两者合二为一,专业的话来说就是 **One Pass**,中文是一次遍历。 |
| 87 | + |
| 88 | +注意到我们前面计算总和用到了组合公式 $$C_{n}^{2}$$,等价于 $$n\times(n-1)\div{2}$$,这其实就是等差数列 `1,2,3....n-1`的求和公式。同时注意到我们的计数信息也是每次增加 1 的,即从 0 -> 1, 1 -> 2, n - 1 -> n。也就是说**我们的计数信息其实就是公差为 1 的等差数列,正好对应前面写的等差数列**。那我们是不是可以从 1 开始累加计数信息,直到 n -1(注意不是 n)。 |
| 89 | + |
| 90 | +> 力扣中有好几个题目都使用到了这种 One Pass 技巧。 |
| 91 | +
|
| 92 | +### 代码 |
| 93 | + |
| 94 | +代码支持:Python3 |
| 95 | + |
| 96 | +Python3 Code: |
| 97 | + |
| 98 | +```python |
| 99 | + counts = [0] * 1024 |
| 100 | + ans = 0 |
| 101 | + for a, b in dominoes: |
| 102 | + if a >= b: v = a <<5 | b |
| 103 | + else: v = b << 5 | a |
| 104 | + ans += counts[v] |
| 105 | + counts[v] += 1 |
| 106 | + return ans |
| 107 | +``` |
| 108 | + |
| 109 | +**复杂度分析** |
| 110 | + |
| 111 | +令 N 为数组长度。 |
| 112 | + |
| 113 | +- 时间复杂度:$$O(N)$$ |
| 114 | +- 空间复杂度:$$O(1024)$$ |
| 115 | + |
| 116 | +## 状态压缩优化 |
| 117 | + |
| 118 | +### 思路 |
| 119 | + |
| 120 | +代码上,我使用了 int 来存储,因此实际上会用 32 个字节(取决于不同的编程语言),这并没有发挥二进制的状态压缩的优点。由于 `1 <= dominoes[i][j] <= 9`,我们也可直接用 9 进制来存,刚好 `9 * 9 = 81` 种状态。 这样开辟一个大小为 81 的数组即可。 |
| 121 | + |
| 122 | +### 代码 |
| 123 | + |
| 124 | +代码支持:Python3 |
| 125 | + |
| 126 | +Python3 Code: |
| 127 | + |
| 128 | +```python |
| 129 | + |
| 130 | +class Solution: |
| 131 | + def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int: |
| 132 | + counts = [0] * 9 * 9 |
| 133 | + ans = 0 |
| 134 | + for a, b in dominoes: |
| 135 | + if a >= b: v = a * 9 + b |
| 136 | + else: v = b * 9 + a |
| 137 | + ans += counts[v] |
| 138 | + counts[v] += 1 |
| 139 | + return ans |
| 140 | +``` |
| 141 | + |
| 142 | +**复杂度分析** |
| 143 | + |
| 144 | +令 N 为数组长度。 |
| 145 | + |
| 146 | +- 时间复杂度:$$O(N)$$ |
| 147 | +- 空间复杂度:$$O(81)$$ |
| 148 | + |
| 149 | +## 关键点 |
| 150 | + |
| 151 | +- 使用状态压缩可提高性能 |
| 152 | +- 使用求和公式技巧可在一次遍历内计算结果 |
0 commit comments