diff --git a/solution/0800-0899/0846.Hand of Straights/README.md b/solution/0800-0899/0846.Hand of Straights/README.md index 42532a8a87d77..e3f2a87a9d75a 100644 --- a/solution/0800-0899/0846.Hand of Straights/README.md +++ b/solution/0800-0899/0846.Hand of Straights/README.md @@ -64,11 +64,15 @@ tags: ### 方法一:哈希表 + 排序 -我们先用哈希表 `cnt` 统计数组 `hand` 中每个数字出现的次数,然后对数组 `hand` 进行排序。 +我们首先判断数组 $\textit{hand}$ 的长度是否能被 $\textit{groupSize}$ 整除,如果不能整除,说明无法将数组划分成若干个长度为 $\textit{groupSize}$ 的子数组,直接返回 $\text{false}$。 -接下来,我们遍历数组 `hand`,对于数组中的每个数字 $v$,如果 $v$ 在哈希表 `cnt` 中出现的次数不为 $0$,则我们枚举 $v$ 到 $v+groupSize-1$ 的每个数字,如果这些数字在哈希表 `cnt` 中出现的次数都不为 $0$,则我们将这些数字的出现次数减 $1$,如果减 $1$ 后这些数字的出现次数为 $0$,则我们在哈希表 `cnt` 中删除这些数字。否则说明无法将数组划分成若干个长度为 $groupSize$ 的子数组,返回 `false`。如果可以将数组划分成若干个长度为 $groupSize$ 的子数组,则遍历结束后返回 `true`。 +接下来,我们用一个哈希表 $\textit{cnt}$ 统计数组 $\textit{hand}$ 中每个数字出现的次数,然后对数组 $\textit{hand}$ 进行排序。 -时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 `hand` 的长度。 +然后,我们遍历排序后的数组 $\textit{hand}$,对于每个数字 $x$,如果 $\textit{cnt}[x]$ 不为 $0$,我们枚举 $x$ 到 $x+\textit{groupSize}-1$ 的每个数字 $y$,如果 $\textit{cnt}[y]$ 为 $0$,说明无法将数组划分成若干个长度为 $\textit{groupSize}$ 的子数组,直接返回 $\text{false}$。否则,我们将 $\textit{cnt}[y]$ 减 $1$。 + +遍历结束后,说明可以将数组划分成若干个长度为 $\textit{groupSize}$ 的子数组,返回 $\text{true}$。 + +时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 $\textit{hand}$ 的长度。 @@ -77,15 +81,15 @@ tags: ```python class Solution: def isNStraightHand(self, hand: List[int], groupSize: int) -> bool: + if len(hand) % groupSize: + return False cnt = Counter(hand) - for v in sorted(hand): - if cnt[v]: - for x in range(v, v + groupSize): - if cnt[x] == 0: + for x in sorted(hand): + if cnt[x]: + for y in range(x, x + groupSize): + if cnt[y] == 0: return False - cnt[x] -= 1 - if cnt[x] == 0: - cnt.pop(x) + cnt[y] -= 1 return True ``` @@ -94,21 +98,20 @@ class Solution: ```java class Solution { public boolean isNStraightHand(int[] hand, int groupSize) { - Map cnt = new HashMap<>(); - for (int v : hand) { - cnt.put(v, cnt.getOrDefault(v, 0) + 1); + if (hand.length % groupSize != 0) { + return false; } Arrays.sort(hand); - for (int v : hand) { - if (cnt.containsKey(v)) { - for (int x = v; x < v + groupSize; ++x) { - if (!cnt.containsKey(x)) { + Map cnt = new HashMap<>(); + for (int x : hand) { + cnt.merge(x, 1, Integer::sum); + } + for (int x : hand) { + if (cnt.getOrDefault(x, 0) > 0) { + for (int y = x; y < x + groupSize; ++y) { + if (cnt.merge(y, -1, Integer::sum) < 0) { return false; } - cnt.put(x, cnt.get(x) - 1); - if (cnt.get(x) == 0) { - cnt.remove(x); - } } } } @@ -123,17 +126,22 @@ class Solution { class Solution { public: bool isNStraightHand(vector& hand, int groupSize) { + if (hand.size() % groupSize) { + return false; + } + ranges::sort(hand); unordered_map cnt; - for (int& v : hand) ++cnt[v]; - sort(hand.begin(), hand.end()); - for (int& v : hand) { - if (cnt.count(v)) { - for (int x = v; x < v + groupSize; ++x) { - if (!cnt.count(x)) { + for (int x : hand) { + ++cnt[x]; + } + for (int x : hand) { + if (cnt.contains(x)) { + for (int y = x; y < x + groupSize; ++y) { + if (!cnt.contains(y)) { return false; } - if (--cnt[x] == 0) { - cnt.erase(x); + if (--cnt[y] == 0) { + cnt.erase(y); } } } @@ -147,21 +155,21 @@ public: ```go func isNStraightHand(hand []int, groupSize int) bool { - cnt := map[int]int{} - for _, v := range hand { - cnt[v]++ + if len(hand)%groupSize != 0 { + return false } sort.Ints(hand) - for _, v := range hand { - if _, ok := cnt[v]; ok { - for x := v; x < v+groupSize; x++ { - if _, ok := cnt[x]; !ok { + cnt := map[int]int{} + for _, x := range hand { + cnt[x]++ + } + for _, x := range hand { + if cnt[x] > 0 { + for y := x; y < x+groupSize; y++ { + if cnt[y] == 0 { return false } - cnt[x]-- - if cnt[x] == 0 { - delete(cnt, x) - } + cnt[y]-- } } } @@ -172,24 +180,25 @@ func isNStraightHand(hand []int, groupSize int) bool { #### TypeScript ```ts -function isNStraightHand(hand: number[], groupSize: number) { - const cnt: Record = {}; - for (const i of hand) { - cnt[i] = (cnt[i] ?? 0) + 1; +function isNStraightHand(hand: number[], groupSize: number): boolean { + if (hand.length % groupSize !== 0) { + return false; } - - const keys = Object.keys(cnt).map(Number); - for (const i of keys) { - while (cnt[i]) { - for (let j = i; j < groupSize + i; j++) { - if (!cnt[j]) { + const cnt = new Map(); + for (const x of hand) { + cnt.set(x, (cnt.get(x) || 0) + 1); + } + hand.sort((a, b) => a - b); + for (const x of hand) { + if (cnt.get(x)! > 0) { + for (let y = x; y < x + groupSize; y++) { + if ((cnt.get(y) || 0) === 0) { return false; } - cnt[j]--; + cnt.set(y, cnt.get(y)! - 1); } } } - return true; } ``` @@ -202,11 +211,13 @@ function isNStraightHand(hand: number[], groupSize: number) { ### 方法二:有序集合 -我们也可以使用有序集合统计数组 `hand` 中每个数字出现的次数。 +与方法一类似,我们首先判断数组 $\textit{hand}$ 的长度是否能被 $\textit{groupSize}$ 整除,如果不能整除,说明无法将数组划分成若干个长度为 $\textit{groupSize}$ 的子数组,直接返回 $\text{false}$。 + +接下来,我们用一个有序集合 $\textit{sd}$ 统计数组 $\textit{hand}$ 中每个数字出现的次数。 -接下来,循环取出有序集合中的最小值 $v$,然后枚举 $v$ 到 $v+groupSize-1$ 的每个数字,如果这些数字在有序集合中出现的次数都不为 $0$,则我们将这些数字的出现次数减 $1$,如果出现次数减 $1$ 后为 $0$,则将该数字从有序集合中删除,否则说明无法将数组划分成若干个长度为 $groupSize$ 的子数组,返回 `false`。如果可以将数组划分成若干个长度为 $groupSize$ 的子数组,则遍历结束后返回 `true`。 +然后,我们循环取出有序集合中的最小值 $x$,然后枚举 $x$ 到 $x+\textit{groupSize}-1$ 的每个数字 $y$,如果这些数字在有序集合中出现的次数都不为 $0$,则我们将这些数字的出现次数减 $1$,如果出现次数减 $1$ 后为 $0$,则将该数字从有序集合中删除,否则说明无法将数组划分成若干个长度为 $\textit{groupSize}$ 的子数组,返回 $\text{false}$。如果可以将数组划分成若干个长度为 $\textit{groupSize}$ 的子数组,则遍历结束后返回 $\text{true}$。 -时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 `hand` 的长度。 +时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 $\textit{hand}$ 的长度。 @@ -215,23 +226,19 @@ function isNStraightHand(hand: number[], groupSize: number) { ```python class Solution: def isNStraightHand(self, hand: List[int], groupSize: int) -> bool: - if len(hand) % groupSize != 0: + if len(hand) % groupSize: return False - sd = SortedDict() - for h in hand: - if h in sd: - sd[h] += 1 - else: - sd[h] = 1 + cnt = Counter(hand) + sd = SortedDict(cnt) while sd: - v = sd.peekitem(0)[0] - for i in range(v, v + groupSize): - if i not in sd: + x = next(iter(sd)) + for y in range(x, x + groupSize): + if y not in sd: return False - if sd[i] == 1: - sd.pop(i) + if sd[y] == 1: + del sd[y] else: - sd[i] -= 1 + sd[y] -= 1 return True ``` @@ -244,19 +251,18 @@ class Solution { return false; } TreeMap tm = new TreeMap<>(); - for (int h : hand) { - tm.put(h, tm.getOrDefault(h, 0) + 1); + for (int x : hand) { + tm.merge(x, 1, Integer::sum); } while (!tm.isEmpty()) { - int v = tm.firstKey(); - for (int i = v; i < v + groupSize; ++i) { - if (!tm.containsKey(i)) { + int x = tm.firstKey(); + for (int y = x; y < x + groupSize; ++y) { + int t = tm.merge(y, -1, Integer::sum); + if (t < 0) { return false; } - if (tm.get(i) == 1) { - tm.remove(i); - } else { - tm.put(i, tm.get(i) - 1); + if (t == 0) { + tm.remove(y); } } } @@ -271,17 +277,22 @@ class Solution { class Solution { public: bool isNStraightHand(vector& hand, int groupSize) { - if (hand.size() % groupSize != 0) return false; + if (hand.size() % groupSize) { + return false; + } map mp; - for (int& h : hand) mp[h] += 1; + for (int x : hand) { + ++mp[x]; + } while (!mp.empty()) { - int v = mp.begin()->first; - for (int i = v; i < v + groupSize; ++i) { - if (!mp.count(i)) return false; - if (mp[i] == 1) - mp.erase(i); - else - mp[i] -= 1; + int x = mp.begin()->first; + for (int y = x; y < x + groupSize; ++y) { + if (!mp.contains(y)) { + return false; + } + if (--mp[y] == 0) { + mp.erase(y); + } } } return true; @@ -296,24 +307,25 @@ func isNStraightHand(hand []int, groupSize int) bool { if len(hand)%groupSize != 0 { return false } - m := treemap.NewWithIntComparator() - for _, h := range hand { - if v, ok := m.Get(h); ok { - m.Put(h, v.(int)+1) + tm := treemap.NewWithIntComparator() + for _, x := range hand { + if v, ok := tm.Get(x); ok { + tm.Put(x, v.(int)+1) } else { - m.Put(h, 1) + tm.Put(x, 1) } } - for !m.Empty() { - v, _ := m.Min() - for i := v.(int); i < v.(int)+groupSize; i++ { - if _, ok := m.Get(i); !ok { - return false - } - if v, _ := m.Get(i); v.(int) == 1 { - m.Remove(i) + for !tm.Empty() { + x, _ := tm.Min() + for y := x.(int); y < x.(int)+groupSize; y++ { + if v, ok := tm.Get(y); ok { + if v.(int) == 1 { + tm.Remove(y) + } else { + tm.Put(y, v.(int)-1) + } } else { - m.Put(i, v.(int)-1) + return false } } } @@ -325,33 +337,511 @@ func isNStraightHand(hand []int, groupSize int) bool { ```ts function isNStraightHand(hand: number[], groupSize: number): boolean { - const n = hand.length; - if (n % groupSize) { + if (hand.length % groupSize !== 0) { return false; } + const tm = new TreeMap(); + for (const x of hand) { + tm.set(x, (tm.get(x) || 0) + 1); + } + while (tm.size()) { + const x = tm.first()![0]; + for (let y = x; y < x + groupSize; ++y) { + if (!tm.has(y)) { + return false; + } + if (tm.get(y)! === 1) { + tm.delete(y); + } else { + tm.set(y, tm.get(y)! - 1); + } + } + } + return true; +} - const groups: number[][] = Array.from({ length: n / groupSize }, () => []); - hand.sort((a, b) => a - b); +type Compare = (lhs: T, rhs: T) => number; + +class RBTreeNode { + data: T; + count: number; + left: RBTreeNode | null; + right: RBTreeNode | null; + parent: RBTreeNode | null; + color: number; + constructor(data: T) { + this.data = data; + this.left = this.right = this.parent = null; + this.color = 0; + this.count = 1; + } + + sibling(): RBTreeNode | null { + if (!this.parent) return null; // sibling null if no parent + return this.isOnLeft() ? this.parent.right : this.parent.left; + } + + isOnLeft(): boolean { + return this === this.parent!.left; + } + + hasRedChild(): boolean { + return ( + Boolean(this.left && this.left.color === 0) || + Boolean(this.right && this.right.color === 0) + ); + } +} + +class RBTree { + root: RBTreeNode | null; + lt: (l: T, r: T) => boolean; + constructor(compare: Compare = (l: T, r: T) => (l < r ? -1 : l > r ? 1 : 0)) { + this.root = null; + this.lt = (l: T, r: T) => compare(l, r) < 0; + } + + rotateLeft(pt: RBTreeNode): void { + const right = pt.right!; + pt.right = right.left; + + if (pt.right) pt.right.parent = pt; + right.parent = pt.parent; + + if (!pt.parent) this.root = right; + else if (pt === pt.parent.left) pt.parent.left = right; + else pt.parent.right = right; + + right.left = pt; + pt.parent = right; + } + + rotateRight(pt: RBTreeNode): void { + const left = pt.left!; + pt.left = left.right; + + if (pt.left) pt.left.parent = pt; + left.parent = pt.parent; + + if (!pt.parent) this.root = left; + else if (pt === pt.parent.left) pt.parent.left = left; + else pt.parent.right = left; + + left.right = pt; + pt.parent = left; + } + + swapColor(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.color; + p1.color = p2.color; + p2.color = tmp; + } + + swapData(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.data; + p1.data = p2.data; + p2.data = tmp; + } + + fixAfterInsert(pt: RBTreeNode): void { + let parent = null; + let grandParent = null; + + while (pt !== this.root && pt.color !== 1 && pt.parent?.color === 0) { + parent = pt.parent; + grandParent = pt.parent.parent; + + /* Case : A + Parent of pt is left child of Grand-parent of pt */ + if (parent === grandParent?.left) { + const uncle = grandParent.right; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle && uncle.color === 0) { + grandParent.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent; + } else { + /* Case : 2 + pt is right child of its parent + Left-rotation required */ + if (pt === parent.right) { + this.rotateLeft(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is left child of its parent + Right-rotation required */ + this.rotateRight(grandParent); + this.swapColor(parent!, grandParent); + pt = parent!; + } + } else { + /* Case : B + Parent of pt is right child of Grand-parent of pt */ + const uncle = grandParent!.left; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle != null && uncle.color === 0) { + grandParent!.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent!; + } else { + /* Case : 2 + pt is left child of its parent + Right-rotation required */ + if (pt === parent.left) { + this.rotateRight(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is right child of its parent + Left-rotation required */ + this.rotateLeft(grandParent!); + this.swapColor(parent!, grandParent!); + pt = parent!; + } + } + } + this.root!.color = 1; + } + + delete(val: T): boolean { + const node = this.find(val); + if (!node) return false; + node.count--; + if (!node.count) this.deleteNode(node); + return true; + } + + deleteAll(val: T): boolean { + const node = this.find(val); + if (!node) return false; + this.deleteNode(node); + return true; + } + + deleteNode(v: RBTreeNode): void { + const u = BSTreplace(v); + + // True when u and v are both black + const uvBlack = (u === null || u.color === 1) && v.color === 1; + const parent = v.parent!; + + if (!u) { + // u is null therefore v is leaf + if (v === this.root) this.root = null; + // v is root, making root null + else { + if (uvBlack) { + // u and v both black + // v is leaf, fix double black at v + this.fixDoubleBlack(v); + } else { + // u or v is red + if (v.sibling()) { + // sibling is not null, make it red" + v.sibling()!.color = 0; + } + } + // delete v from the tree + if (v.isOnLeft()) parent.left = null; + else parent.right = null; + } + return; + } + + if (!v.left || !v.right) { + // v has 1 child + if (v === this.root) { + // v is root, assign the value of u to v, and delete u + v.data = u.data; + v.left = v.right = null; + } else { + // Detach v from tree and move u up + if (v.isOnLeft()) parent.left = u; + else parent.right = u; + u.parent = parent; + if (uvBlack) this.fixDoubleBlack(u); + // u and v both black, fix double black at u + else u.color = 1; // u or v red, color u black + } + return; + } - for (let i = 0; i < n; i++) { - let isPushed = false; + // v has 2 children, swap data with successor and recurse + this.swapData(u, v); + this.deleteNode(u); + + // find node that replaces a deleted node in BST + function BSTreplace(x: RBTreeNode): RBTreeNode | null { + // when node have 2 children + if (x.left && x.right) return successor(x.right); + // when leaf + if (!x.left && !x.right) return null; + // when single child + return x.left ?? x.right; + } + // find node that do not have a left child + // in the subtree of the given node + function successor(x: RBTreeNode): RBTreeNode { + let temp = x; + while (temp.left) temp = temp.left; + return temp; + } + } - for (const g of groups) { - if (g.length === groupSize || (g.length && hand[i] - g.at(-1)! !== 1)) { - continue; + fixDoubleBlack(x: RBTreeNode): void { + if (x === this.root) return; // Reached root + + const sibling = x.sibling(); + const parent = x.parent!; + if (!sibling) { + // No sibiling, double black pushed up + this.fixDoubleBlack(parent); + } else { + if (sibling.color === 0) { + // Sibling red + parent.color = 0; + sibling.color = 1; + if (sibling.isOnLeft()) this.rotateRight(parent); + // left case + else this.rotateLeft(parent); // right case + this.fixDoubleBlack(x); + } else { + // Sibling black + if (sibling.hasRedChild()) { + // at least 1 red children + if (sibling.left && sibling.left.color === 0) { + if (sibling.isOnLeft()) { + // left left + sibling.left.color = sibling.color; + sibling.color = parent.color; + this.rotateRight(parent); + } else { + // right left + sibling.left.color = parent.color; + this.rotateRight(sibling); + this.rotateLeft(parent); + } + } else { + if (sibling.isOnLeft()) { + // left right + sibling.right!.color = parent.color; + this.rotateLeft(sibling); + this.rotateRight(parent); + } else { + // right right + sibling.right!.color = sibling.color; + sibling.color = parent.color; + this.rotateLeft(parent); + } + } + parent.color = 1; + } else { + // 2 black children + sibling.color = 0; + if (parent.color === 1) this.fixDoubleBlack(parent); + else parent.color = 1; + } } + } + } - g.push(hand[i]); - isPushed = true; - break; + insert(data: T): boolean { + // search for a position to insert + let parent = this.root; + while (parent) { + if (this.lt(data, parent.data)) { + if (!parent.left) break; + else parent = parent.left; + } else if (this.lt(parent.data, data)) { + if (!parent.right) break; + else parent = parent.right; + } else break; } - if (!isPushed) { + // insert node into parent + const node = new RBTreeNode(data); + if (!parent) this.root = node; + else if (this.lt(node.data, parent.data)) parent.left = node; + else if (this.lt(parent.data, node.data)) parent.right = node; + else { + parent.count++; return false; } + node.parent = parent; + this.fixAfterInsert(node); + return true; + } + + search(predicate: (val: T) => boolean, direction: 'left' | 'right'): T | undefined { + let p = this.root; + let result = null; + while (p) { + if (predicate(p.data)) { + result = p; + p = p[direction]; + } else { + p = p[direction === 'left' ? 'right' : 'left']; + } + } + return result?.data; } - return true; + find(data: T): RBTreeNode | null { + let p = this.root; + while (p) { + if (this.lt(data, p.data)) { + p = p.left; + } else if (this.lt(p.data, data)) { + p = p.right; + } else break; + } + return p ?? null; + } + + count(data: T): number { + const node = this.find(data); + return node ? node.count : 0; + } + + *inOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.inOrder(root.left!)) yield v; + yield root.data; + for (const v of this.inOrder(root.right!)) yield v; + } + + *reverseInOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.reverseInOrder(root.right!)) yield v; + yield root.data; + for (const v of this.reverseInOrder(root.left!)) yield v; + } +} + +class TreeMap { + _size: number; + tree: RBTree; + map: Map = new Map(); + compare: Compare; + constructor( + collection: Array<[K, V]> | Compare = [], + compare: Compare = (l: K, r: K) => (l < r ? -1 : l > r ? 1 : 0), + ) { + if (typeof collection === 'function') { + compare = collection; + collection = []; + } + this._size = 0; + this.compare = compare; + this.tree = new RBTree(compare); + for (const [key, val] of collection) this.set(key, val); + } + + size(): number { + return this._size; + } + + has(key: K): boolean { + return !!this.tree.find(key); + } + + get(key: K): V | undefined { + return this.map.get(key); + } + + set(key: K, val: V): boolean { + const successful = this.tree.insert(key); + this._size += successful ? 1 : 0; + this.map.set(key, val); + return successful; + } + + delete(key: K): boolean { + const deleted = this.tree.deleteAll(key); + this._size -= deleted ? 1 : 0; + return deleted; + } + + ceil(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) >= 0, 'left')); + } + + floor(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) <= 0, 'right')); + } + + higher(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) > 0, 'left')); + } + + lower(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) < 0, 'right')); + } + + first(): [K, V] | undefined { + return this.toKeyValue(this.tree.inOrder().next().value); + } + + last(): [K, V] | undefined { + return this.toKeyValue(this.tree.reverseInOrder().next().value); + } + + shift(): [K, V] | undefined { + const first = this.first(); + if (first === undefined) return undefined; + this.delete(first[0]); + return first; + } + + pop(): [K, V] | undefined { + const last = this.last(); + if (last === undefined) return undefined; + this.delete(last[0]); + return last; + } + + toKeyValue(key: K): [K, V]; + toKeyValue(key: undefined): undefined; + toKeyValue(key: K | undefined): [K, V] | undefined; + toKeyValue(key: K | undefined): [K, V] | undefined { + return key != null ? [key, this.map.get(key)!] : undefined; + } + + *[Symbol.iterator](): Generator<[K, V], void, void> { + for (const key of this.keys()) yield this.toKeyValue(key); + } + + *keys(): Generator { + for (const key of this.tree.inOrder()) yield key; + } + + *values(): Generator { + for (const key of this.keys()) yield this.map.get(key)!; + return undefined; + } + + *rkeys(): Generator { + for (const key of this.tree.reverseInOrder()) yield key; + return undefined; + } + + *rvalues(): Generator { + for (const key of this.rkeys()) yield this.map.get(key)!; + return undefined; + } } ``` diff --git a/solution/0800-0899/0846.Hand of Straights/README_EN.md b/solution/0800-0899/0846.Hand of Straights/README_EN.md index e8eef656750af..a0efb8f95ece4 100644 --- a/solution/0800-0899/0846.Hand of Straights/README_EN.md +++ b/solution/0800-0899/0846.Hand of Straights/README_EN.md @@ -59,7 +59,17 @@ tags: -### Solution 1 +### Solution 1: Hash Table + Sorting + +We first check whether the length of the array $\textit{hand}$ is divisible by $\textit{groupSize}$. If it is not, this means that the array cannot be partitioned into multiple subarrays of length $\textit{groupSize}$, so we return $\text{false}$. + +Next, we use a hash table $\textit{cnt}$ to count the occurrences of each number in the array $\textit{hand}$, and then we sort the array $\textit{hand}$. + +After that, we iterate over the sorted array $\textit{hand}$. For each number $x$, if $\textit{cnt}[x] \neq 0$, we enumerate every number $y$ from $x$ to $x + \textit{groupSize} - 1$. If $\textit{cnt}[y] = 0$, it means that we cannot partition the array into multiple subarrays of length $\textit{groupSize}$, so we return $\text{false}$. Otherwise, we decrement $\textit{cnt}[y]$ by $1$. + +If the iteration completes successfully, it means that the array can be partitioned into multiple valid subarrays, so we return $\text{true}$. + +The time complexity is $O(n \times \log n)$, and the space complexity is $O(n)$, where $n$ is the length of the array $\textit{hand}$. @@ -68,15 +78,15 @@ tags: ```python class Solution: def isNStraightHand(self, hand: List[int], groupSize: int) -> bool: + if len(hand) % groupSize: + return False cnt = Counter(hand) - for v in sorted(hand): - if cnt[v]: - for x in range(v, v + groupSize): - if cnt[x] == 0: + for x in sorted(hand): + if cnt[x]: + for y in range(x, x + groupSize): + if cnt[y] == 0: return False - cnt[x] -= 1 - if cnt[x] == 0: - cnt.pop(x) + cnt[y] -= 1 return True ``` @@ -85,21 +95,20 @@ class Solution: ```java class Solution { public boolean isNStraightHand(int[] hand, int groupSize) { - Map cnt = new HashMap<>(); - for (int v : hand) { - cnt.put(v, cnt.getOrDefault(v, 0) + 1); + if (hand.length % groupSize != 0) { + return false; } Arrays.sort(hand); - for (int v : hand) { - if (cnt.containsKey(v)) { - for (int x = v; x < v + groupSize; ++x) { - if (!cnt.containsKey(x)) { + Map cnt = new HashMap<>(); + for (int x : hand) { + cnt.merge(x, 1, Integer::sum); + } + for (int x : hand) { + if (cnt.getOrDefault(x, 0) > 0) { + for (int y = x; y < x + groupSize; ++y) { + if (cnt.merge(y, -1, Integer::sum) < 0) { return false; } - cnt.put(x, cnt.get(x) - 1); - if (cnt.get(x) == 0) { - cnt.remove(x); - } } } } @@ -114,17 +123,22 @@ class Solution { class Solution { public: bool isNStraightHand(vector& hand, int groupSize) { + if (hand.size() % groupSize) { + return false; + } + ranges::sort(hand); unordered_map cnt; - for (int& v : hand) ++cnt[v]; - sort(hand.begin(), hand.end()); - for (int& v : hand) { - if (cnt.count(v)) { - for (int x = v; x < v + groupSize; ++x) { - if (!cnt.count(x)) { + for (int x : hand) { + ++cnt[x]; + } + for (int x : hand) { + if (cnt.contains(x)) { + for (int y = x; y < x + groupSize; ++y) { + if (!cnt.contains(y)) { return false; } - if (--cnt[x] == 0) { - cnt.erase(x); + if (--cnt[y] == 0) { + cnt.erase(y); } } } @@ -138,21 +152,21 @@ public: ```go func isNStraightHand(hand []int, groupSize int) bool { - cnt := map[int]int{} - for _, v := range hand { - cnt[v]++ + if len(hand)%groupSize != 0 { + return false } sort.Ints(hand) - for _, v := range hand { - if _, ok := cnt[v]; ok { - for x := v; x < v+groupSize; x++ { - if _, ok := cnt[x]; !ok { + cnt := map[int]int{} + for _, x := range hand { + cnt[x]++ + } + for _, x := range hand { + if cnt[x] > 0 { + for y := x; y < x+groupSize; y++ { + if cnt[y] == 0 { return false } - cnt[x]-- - if cnt[x] == 0 { - delete(cnt, x) - } + cnt[y]-- } } } @@ -163,24 +177,25 @@ func isNStraightHand(hand []int, groupSize int) bool { #### TypeScript ```ts -function isNStraightHand(hand: number[], groupSize: number) { - const cnt: Record = {}; - for (const i of hand) { - cnt[i] = (cnt[i] ?? 0) + 1; +function isNStraightHand(hand: number[], groupSize: number): boolean { + if (hand.length % groupSize !== 0) { + return false; } - - const keys = Object.keys(cnt).map(Number); - for (const i of keys) { - while (cnt[i]) { - for (let j = i; j < groupSize + i; j++) { - if (!cnt[j]) { + const cnt = new Map(); + for (const x of hand) { + cnt.set(x, (cnt.get(x) || 0) + 1); + } + hand.sort((a, b) => a - b); + for (const x of hand) { + if (cnt.get(x)! > 0) { + for (let y = x; y < x + groupSize; y++) { + if ((cnt.get(y) || 0) === 0) { return false; } - cnt[j]--; + cnt.set(y, cnt.get(y)! - 1); } } } - return true; } ``` @@ -191,7 +206,15 @@ function isNStraightHand(hand: number[], groupSize: number) { -### Solution 2 +### Solution 2: Ordered Set + +Similar to Solution 1, we first check whether the length of the array $\textit{hand}$ is divisible by $\textit{groupSize}$. If it is not, this means that the array cannot be partitioned into multiple subarrays of length $\textit{groupSize}$, so we return $\text{false}$. + +Next, we use an ordered set $\textit{sd}$ to count the occurrences of each number in the array $\textit{hand}$. + +Then, we repeatedly take the smallest value $x$ from the ordered set and enumerate each number $y$ from $x$ to $x + \textit{groupSize} - 1$. If all these numbers appear at least once in the ordered set, we decrement their occurrence count by $1$. If any count reaches $0$, we remove that number from the ordered set. Otherwise, if we encounter a number that does not exist in the ordered set, it means that the array cannot be partitioned into valid subarrays, so we return $\text{false}$. If the iteration completes successfully, it means that the array can be partitioned into multiple valid subarrays, so we return $\text{true}$. + +The time complexity is $O(n \times \log n)$, and the space complexity is $O(n)$, where $n$ is the length of the array $\textit{hand}$. @@ -200,23 +223,19 @@ function isNStraightHand(hand: number[], groupSize: number) { ```python class Solution: def isNStraightHand(self, hand: List[int], groupSize: int) -> bool: - if len(hand) % groupSize != 0: + if len(hand) % groupSize: return False - sd = SortedDict() - for h in hand: - if h in sd: - sd[h] += 1 - else: - sd[h] = 1 + cnt = Counter(hand) + sd = SortedDict(cnt) while sd: - v = sd.peekitem(0)[0] - for i in range(v, v + groupSize): - if i not in sd: + x = next(iter(sd)) + for y in range(x, x + groupSize): + if y not in sd: return False - if sd[i] == 1: - sd.pop(i) + if sd[y] == 1: + del sd[y] else: - sd[i] -= 1 + sd[y] -= 1 return True ``` @@ -229,19 +248,18 @@ class Solution { return false; } TreeMap tm = new TreeMap<>(); - for (int h : hand) { - tm.put(h, tm.getOrDefault(h, 0) + 1); + for (int x : hand) { + tm.merge(x, 1, Integer::sum); } while (!tm.isEmpty()) { - int v = tm.firstKey(); - for (int i = v; i < v + groupSize; ++i) { - if (!tm.containsKey(i)) { + int x = tm.firstKey(); + for (int y = x; y < x + groupSize; ++y) { + int t = tm.merge(y, -1, Integer::sum); + if (t < 0) { return false; } - if (tm.get(i) == 1) { - tm.remove(i); - } else { - tm.put(i, tm.get(i) - 1); + if (t == 0) { + tm.remove(y); } } } @@ -256,17 +274,22 @@ class Solution { class Solution { public: bool isNStraightHand(vector& hand, int groupSize) { - if (hand.size() % groupSize != 0) return false; + if (hand.size() % groupSize) { + return false; + } map mp; - for (int& h : hand) mp[h] += 1; + for (int x : hand) { + ++mp[x]; + } while (!mp.empty()) { - int v = mp.begin()->first; - for (int i = v; i < v + groupSize; ++i) { - if (!mp.count(i)) return false; - if (mp[i] == 1) - mp.erase(i); - else - mp[i] -= 1; + int x = mp.begin()->first; + for (int y = x; y < x + groupSize; ++y) { + if (!mp.contains(y)) { + return false; + } + if (--mp[y] == 0) { + mp.erase(y); + } } } return true; @@ -281,24 +304,25 @@ func isNStraightHand(hand []int, groupSize int) bool { if len(hand)%groupSize != 0 { return false } - m := treemap.NewWithIntComparator() - for _, h := range hand { - if v, ok := m.Get(h); ok { - m.Put(h, v.(int)+1) + tm := treemap.NewWithIntComparator() + for _, x := range hand { + if v, ok := tm.Get(x); ok { + tm.Put(x, v.(int)+1) } else { - m.Put(h, 1) + tm.Put(x, 1) } } - for !m.Empty() { - v, _ := m.Min() - for i := v.(int); i < v.(int)+groupSize; i++ { - if _, ok := m.Get(i); !ok { - return false - } - if v, _ := m.Get(i); v.(int) == 1 { - m.Remove(i) + for !tm.Empty() { + x, _ := tm.Min() + for y := x.(int); y < x.(int)+groupSize; y++ { + if v, ok := tm.Get(y); ok { + if v.(int) == 1 { + tm.Remove(y) + } else { + tm.Put(y, v.(int)-1) + } } else { - m.Put(i, v.(int)-1) + return false } } } @@ -310,33 +334,511 @@ func isNStraightHand(hand []int, groupSize int) bool { ```ts function isNStraightHand(hand: number[], groupSize: number): boolean { - const n = hand.length; - if (n % groupSize) { + if (hand.length % groupSize !== 0) { return false; } + const tm = new TreeMap(); + for (const x of hand) { + tm.set(x, (tm.get(x) || 0) + 1); + } + while (tm.size()) { + const x = tm.first()![0]; + for (let y = x; y < x + groupSize; ++y) { + if (!tm.has(y)) { + return false; + } + if (tm.get(y)! === 1) { + tm.delete(y); + } else { + tm.set(y, tm.get(y)! - 1); + } + } + } + return true; +} - const groups: number[][] = Array.from({ length: n / groupSize }, () => []); - hand.sort((a, b) => a - b); +type Compare = (lhs: T, rhs: T) => number; + +class RBTreeNode { + data: T; + count: number; + left: RBTreeNode | null; + right: RBTreeNode | null; + parent: RBTreeNode | null; + color: number; + constructor(data: T) { + this.data = data; + this.left = this.right = this.parent = null; + this.color = 0; + this.count = 1; + } + + sibling(): RBTreeNode | null { + if (!this.parent) return null; // sibling null if no parent + return this.isOnLeft() ? this.parent.right : this.parent.left; + } + + isOnLeft(): boolean { + return this === this.parent!.left; + } + + hasRedChild(): boolean { + return ( + Boolean(this.left && this.left.color === 0) || + Boolean(this.right && this.right.color === 0) + ); + } +} + +class RBTree { + root: RBTreeNode | null; + lt: (l: T, r: T) => boolean; + constructor(compare: Compare = (l: T, r: T) => (l < r ? -1 : l > r ? 1 : 0)) { + this.root = null; + this.lt = (l: T, r: T) => compare(l, r) < 0; + } + + rotateLeft(pt: RBTreeNode): void { + const right = pt.right!; + pt.right = right.left; + + if (pt.right) pt.right.parent = pt; + right.parent = pt.parent; + + if (!pt.parent) this.root = right; + else if (pt === pt.parent.left) pt.parent.left = right; + else pt.parent.right = right; + + right.left = pt; + pt.parent = right; + } + + rotateRight(pt: RBTreeNode): void { + const left = pt.left!; + pt.left = left.right; + + if (pt.left) pt.left.parent = pt; + left.parent = pt.parent; + + if (!pt.parent) this.root = left; + else if (pt === pt.parent.left) pt.parent.left = left; + else pt.parent.right = left; + + left.right = pt; + pt.parent = left; + } + + swapColor(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.color; + p1.color = p2.color; + p2.color = tmp; + } + + swapData(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.data; + p1.data = p2.data; + p2.data = tmp; + } + + fixAfterInsert(pt: RBTreeNode): void { + let parent = null; + let grandParent = null; + + while (pt !== this.root && pt.color !== 1 && pt.parent?.color === 0) { + parent = pt.parent; + grandParent = pt.parent.parent; + + /* Case : A + Parent of pt is left child of Grand-parent of pt */ + if (parent === grandParent?.left) { + const uncle = grandParent.right; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle && uncle.color === 0) { + grandParent.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent; + } else { + /* Case : 2 + pt is right child of its parent + Left-rotation required */ + if (pt === parent.right) { + this.rotateLeft(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is left child of its parent + Right-rotation required */ + this.rotateRight(grandParent); + this.swapColor(parent!, grandParent); + pt = parent!; + } + } else { + /* Case : B + Parent of pt is right child of Grand-parent of pt */ + const uncle = grandParent!.left; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle != null && uncle.color === 0) { + grandParent!.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent!; + } else { + /* Case : 2 + pt is left child of its parent + Right-rotation required */ + if (pt === parent.left) { + this.rotateRight(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is right child of its parent + Left-rotation required */ + this.rotateLeft(grandParent!); + this.swapColor(parent!, grandParent!); + pt = parent!; + } + } + } + this.root!.color = 1; + } - for (let i = 0; i < n; i++) { - let isPushed = false; + delete(val: T): boolean { + const node = this.find(val); + if (!node) return false; + node.count--; + if (!node.count) this.deleteNode(node); + return true; + } + + deleteAll(val: T): boolean { + const node = this.find(val); + if (!node) return false; + this.deleteNode(node); + return true; + } + + deleteNode(v: RBTreeNode): void { + const u = BSTreplace(v); + + // True when u and v are both black + const uvBlack = (u === null || u.color === 1) && v.color === 1; + const parent = v.parent!; + + if (!u) { + // u is null therefore v is leaf + if (v === this.root) this.root = null; + // v is root, making root null + else { + if (uvBlack) { + // u and v both black + // v is leaf, fix double black at v + this.fixDoubleBlack(v); + } else { + // u or v is red + if (v.sibling()) { + // sibling is not null, make it red" + v.sibling()!.color = 0; + } + } + // delete v from the tree + if (v.isOnLeft()) parent.left = null; + else parent.right = null; + } + return; + } + + if (!v.left || !v.right) { + // v has 1 child + if (v === this.root) { + // v is root, assign the value of u to v, and delete u + v.data = u.data; + v.left = v.right = null; + } else { + // Detach v from tree and move u up + if (v.isOnLeft()) parent.left = u; + else parent.right = u; + u.parent = parent; + if (uvBlack) this.fixDoubleBlack(u); + // u and v both black, fix double black at u + else u.color = 1; // u or v red, color u black + } + return; + } + + // v has 2 children, swap data with successor and recurse + this.swapData(u, v); + this.deleteNode(u); + + // find node that replaces a deleted node in BST + function BSTreplace(x: RBTreeNode): RBTreeNode | null { + // when node have 2 children + if (x.left && x.right) return successor(x.right); + // when leaf + if (!x.left && !x.right) return null; + // when single child + return x.left ?? x.right; + } + // find node that do not have a left child + // in the subtree of the given node + function successor(x: RBTreeNode): RBTreeNode { + let temp = x; + while (temp.left) temp = temp.left; + return temp; + } + } - for (const g of groups) { - if (g.length === groupSize || (g.length && hand[i] - g.at(-1)! !== 1)) { - continue; + fixDoubleBlack(x: RBTreeNode): void { + if (x === this.root) return; // Reached root + + const sibling = x.sibling(); + const parent = x.parent!; + if (!sibling) { + // No sibiling, double black pushed up + this.fixDoubleBlack(parent); + } else { + if (sibling.color === 0) { + // Sibling red + parent.color = 0; + sibling.color = 1; + if (sibling.isOnLeft()) this.rotateRight(parent); + // left case + else this.rotateLeft(parent); // right case + this.fixDoubleBlack(x); + } else { + // Sibling black + if (sibling.hasRedChild()) { + // at least 1 red children + if (sibling.left && sibling.left.color === 0) { + if (sibling.isOnLeft()) { + // left left + sibling.left.color = sibling.color; + sibling.color = parent.color; + this.rotateRight(parent); + } else { + // right left + sibling.left.color = parent.color; + this.rotateRight(sibling); + this.rotateLeft(parent); + } + } else { + if (sibling.isOnLeft()) { + // left right + sibling.right!.color = parent.color; + this.rotateLeft(sibling); + this.rotateRight(parent); + } else { + // right right + sibling.right!.color = sibling.color; + sibling.color = parent.color; + this.rotateLeft(parent); + } + } + parent.color = 1; + } else { + // 2 black children + sibling.color = 0; + if (parent.color === 1) this.fixDoubleBlack(parent); + else parent.color = 1; + } } + } + } - g.push(hand[i]); - isPushed = true; - break; + insert(data: T): boolean { + // search for a position to insert + let parent = this.root; + while (parent) { + if (this.lt(data, parent.data)) { + if (!parent.left) break; + else parent = parent.left; + } else if (this.lt(parent.data, data)) { + if (!parent.right) break; + else parent = parent.right; + } else break; } - if (!isPushed) { + // insert node into parent + const node = new RBTreeNode(data); + if (!parent) this.root = node; + else if (this.lt(node.data, parent.data)) parent.left = node; + else if (this.lt(parent.data, node.data)) parent.right = node; + else { + parent.count++; return false; } + node.parent = parent; + this.fixAfterInsert(node); + return true; } - return true; + search(predicate: (val: T) => boolean, direction: 'left' | 'right'): T | undefined { + let p = this.root; + let result = null; + while (p) { + if (predicate(p.data)) { + result = p; + p = p[direction]; + } else { + p = p[direction === 'left' ? 'right' : 'left']; + } + } + return result?.data; + } + + find(data: T): RBTreeNode | null { + let p = this.root; + while (p) { + if (this.lt(data, p.data)) { + p = p.left; + } else if (this.lt(p.data, data)) { + p = p.right; + } else break; + } + return p ?? null; + } + + count(data: T): number { + const node = this.find(data); + return node ? node.count : 0; + } + + *inOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.inOrder(root.left!)) yield v; + yield root.data; + for (const v of this.inOrder(root.right!)) yield v; + } + + *reverseInOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.reverseInOrder(root.right!)) yield v; + yield root.data; + for (const v of this.reverseInOrder(root.left!)) yield v; + } +} + +class TreeMap { + _size: number; + tree: RBTree; + map: Map = new Map(); + compare: Compare; + constructor( + collection: Array<[K, V]> | Compare = [], + compare: Compare = (l: K, r: K) => (l < r ? -1 : l > r ? 1 : 0), + ) { + if (typeof collection === 'function') { + compare = collection; + collection = []; + } + this._size = 0; + this.compare = compare; + this.tree = new RBTree(compare); + for (const [key, val] of collection) this.set(key, val); + } + + size(): number { + return this._size; + } + + has(key: K): boolean { + return !!this.tree.find(key); + } + + get(key: K): V | undefined { + return this.map.get(key); + } + + set(key: K, val: V): boolean { + const successful = this.tree.insert(key); + this._size += successful ? 1 : 0; + this.map.set(key, val); + return successful; + } + + delete(key: K): boolean { + const deleted = this.tree.deleteAll(key); + this._size -= deleted ? 1 : 0; + return deleted; + } + + ceil(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) >= 0, 'left')); + } + + floor(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) <= 0, 'right')); + } + + higher(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) > 0, 'left')); + } + + lower(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) < 0, 'right')); + } + + first(): [K, V] | undefined { + return this.toKeyValue(this.tree.inOrder().next().value); + } + + last(): [K, V] | undefined { + return this.toKeyValue(this.tree.reverseInOrder().next().value); + } + + shift(): [K, V] | undefined { + const first = this.first(); + if (first === undefined) return undefined; + this.delete(first[0]); + return first; + } + + pop(): [K, V] | undefined { + const last = this.last(); + if (last === undefined) return undefined; + this.delete(last[0]); + return last; + } + + toKeyValue(key: K): [K, V]; + toKeyValue(key: undefined): undefined; + toKeyValue(key: K | undefined): [K, V] | undefined; + toKeyValue(key: K | undefined): [K, V] | undefined { + return key != null ? [key, this.map.get(key)!] : undefined; + } + + *[Symbol.iterator](): Generator<[K, V], void, void> { + for (const key of this.keys()) yield this.toKeyValue(key); + } + + *keys(): Generator { + for (const key of this.tree.inOrder()) yield key; + } + + *values(): Generator { + for (const key of this.keys()) yield this.map.get(key)!; + return undefined; + } + + *rkeys(): Generator { + for (const key of this.tree.reverseInOrder()) yield key; + return undefined; + } + + *rvalues(): Generator { + for (const key of this.rkeys()) yield this.map.get(key)!; + return undefined; + } } ``` diff --git a/solution/0800-0899/0846.Hand of Straights/Solution.cpp b/solution/0800-0899/0846.Hand of Straights/Solution.cpp index 343ee6d466449..4d9fdeb7f9ba6 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution.cpp +++ b/solution/0800-0899/0846.Hand of Straights/Solution.cpp @@ -1,21 +1,26 @@ class Solution { public: bool isNStraightHand(vector& hand, int groupSize) { + if (hand.size() % groupSize) { + return false; + } + ranges::sort(hand); unordered_map cnt; - for (int& v : hand) ++cnt[v]; - sort(hand.begin(), hand.end()); - for (int& v : hand) { - if (cnt.count(v)) { - for (int x = v; x < v + groupSize; ++x) { - if (!cnt.count(x)) { + for (int x : hand) { + ++cnt[x]; + } + for (int x : hand) { + if (cnt.contains(x)) { + for (int y = x; y < x + groupSize; ++y) { + if (!cnt.contains(y)) { return false; } - if (--cnt[x] == 0) { - cnt.erase(x); + if (--cnt[y] == 0) { + cnt.erase(y); } } } } return true; } -}; \ No newline at end of file +}; diff --git a/solution/0800-0899/0846.Hand of Straights/Solution.go b/solution/0800-0899/0846.Hand of Straights/Solution.go index 052092ae3469c..022bb6de7e2c0 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution.go +++ b/solution/0800-0899/0846.Hand of Straights/Solution.go @@ -1,21 +1,21 @@ func isNStraightHand(hand []int, groupSize int) bool { - cnt := map[int]int{} - for _, v := range hand { - cnt[v]++ + if len(hand)%groupSize != 0 { + return false } sort.Ints(hand) - for _, v := range hand { - if _, ok := cnt[v]; ok { - for x := v; x < v+groupSize; x++ { - if _, ok := cnt[x]; !ok { + cnt := map[int]int{} + for _, x := range hand { + cnt[x]++ + } + for _, x := range hand { + if cnt[x] > 0 { + for y := x; y < x+groupSize; y++ { + if cnt[y] == 0 { return false } - cnt[x]-- - if cnt[x] == 0 { - delete(cnt, x) - } + cnt[y]-- } } } return true -} \ No newline at end of file +} diff --git a/solution/0800-0899/0846.Hand of Straights/Solution.java b/solution/0800-0899/0846.Hand of Straights/Solution.java index 736d0922e891b..69354d24442cf 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution.java +++ b/solution/0800-0899/0846.Hand of Straights/Solution.java @@ -1,23 +1,22 @@ class Solution { public boolean isNStraightHand(int[] hand, int groupSize) { - Map cnt = new HashMap<>(); - for (int v : hand) { - cnt.put(v, cnt.getOrDefault(v, 0) + 1); + if (hand.length % groupSize != 0) { + return false; } Arrays.sort(hand); - for (int v : hand) { - if (cnt.containsKey(v)) { - for (int x = v; x < v + groupSize; ++x) { - if (!cnt.containsKey(x)) { + Map cnt = new HashMap<>(); + for (int x : hand) { + cnt.merge(x, 1, Integer::sum); + } + for (int x : hand) { + if (cnt.getOrDefault(x, 0) > 0) { + for (int y = x; y < x + groupSize; ++y) { + if (cnt.merge(y, -1, Integer::sum) < 0) { return false; } - cnt.put(x, cnt.get(x) - 1); - if (cnt.get(x) == 0) { - cnt.remove(x); - } } } } return true; } -} \ No newline at end of file +} diff --git a/solution/0800-0899/0846.Hand of Straights/Solution.py b/solution/0800-0899/0846.Hand of Straights/Solution.py index 025af0a82f294..c7eccc1a5a41f 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution.py +++ b/solution/0800-0899/0846.Hand of Straights/Solution.py @@ -1,12 +1,12 @@ class Solution: def isNStraightHand(self, hand: List[int], groupSize: int) -> bool: + if len(hand) % groupSize: + return False cnt = Counter(hand) - for v in sorted(hand): - if cnt[v]: - for x in range(v, v + groupSize): - if cnt[x] == 0: + for x in sorted(hand): + if cnt[x]: + for y in range(x, x + groupSize): + if cnt[y] == 0: return False - cnt[x] -= 1 - if cnt[x] == 0: - cnt.pop(x) + cnt[y] -= 1 return True diff --git a/solution/0800-0899/0846.Hand of Straights/Solution.ts b/solution/0800-0899/0846.Hand of Straights/Solution.ts index 0396d0bf50443..562559046c37c 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution.ts +++ b/solution/0800-0899/0846.Hand of Straights/Solution.ts @@ -1,20 +1,21 @@ -function isNStraightHand(hand: number[], groupSize: number) { - const cnt: Record = {}; - for (const i of hand) { - cnt[i] = (cnt[i] ?? 0) + 1; +function isNStraightHand(hand: number[], groupSize: number): boolean { + if (hand.length % groupSize !== 0) { + return false; } - - const keys = Object.keys(cnt).map(Number); - for (const i of keys) { - while (cnt[i]) { - for (let j = i; j < groupSize + i; j++) { - if (!cnt[j]) { + const cnt = new Map(); + for (const x of hand) { + cnt.set(x, (cnt.get(x) || 0) + 1); + } + hand.sort((a, b) => a - b); + for (const x of hand) { + if (cnt.get(x)! > 0) { + for (let y = x; y < x + groupSize; y++) { + if ((cnt.get(y) || 0) === 0) { return false; } - cnt[j]--; + cnt.set(y, cnt.get(y)! - 1); } } } - return true; } diff --git a/solution/0800-0899/0846.Hand of Straights/Solution2.cpp b/solution/0800-0899/0846.Hand of Straights/Solution2.cpp index d36a24b98490c..b670ab8f5e47a 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution2.cpp +++ b/solution/0800-0899/0846.Hand of Straights/Solution2.cpp @@ -1,19 +1,24 @@ class Solution { public: bool isNStraightHand(vector& hand, int groupSize) { - if (hand.size() % groupSize != 0) return false; + if (hand.size() % groupSize) { + return false; + } map mp; - for (int& h : hand) mp[h] += 1; + for (int x : hand) { + ++mp[x]; + } while (!mp.empty()) { - int v = mp.begin()->first; - for (int i = v; i < v + groupSize; ++i) { - if (!mp.count(i)) return false; - if (mp[i] == 1) - mp.erase(i); - else - mp[i] -= 1; + int x = mp.begin()->first; + for (int y = x; y < x + groupSize; ++y) { + if (!mp.contains(y)) { + return false; + } + if (--mp[y] == 0) { + mp.erase(y); + } } } return true; } -}; \ No newline at end of file +}; diff --git a/solution/0800-0899/0846.Hand of Straights/Solution2.go b/solution/0800-0899/0846.Hand of Straights/Solution2.go index d364408ceb4c5..83b10d62e3d13 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution2.go +++ b/solution/0800-0899/0846.Hand of Straights/Solution2.go @@ -2,26 +2,27 @@ func isNStraightHand(hand []int, groupSize int) bool { if len(hand)%groupSize != 0 { return false } - m := treemap.NewWithIntComparator() - for _, h := range hand { - if v, ok := m.Get(h); ok { - m.Put(h, v.(int)+1) + tm := treemap.NewWithIntComparator() + for _, x := range hand { + if v, ok := tm.Get(x); ok { + tm.Put(x, v.(int)+1) } else { - m.Put(h, 1) + tm.Put(x, 1) } } - for !m.Empty() { - v, _ := m.Min() - for i := v.(int); i < v.(int)+groupSize; i++ { - if _, ok := m.Get(i); !ok { - return false - } - if v, _ := m.Get(i); v.(int) == 1 { - m.Remove(i) + for !tm.Empty() { + x, _ := tm.Min() + for y := x.(int); y < x.(int)+groupSize; y++ { + if v, ok := tm.Get(y); ok { + if v.(int) == 1 { + tm.Remove(y) + } else { + tm.Put(y, v.(int)-1) + } } else { - m.Put(i, v.(int)-1) + return false } } } return true -} \ No newline at end of file +} diff --git a/solution/0800-0899/0846.Hand of Straights/Solution2.java b/solution/0800-0899/0846.Hand of Straights/Solution2.java index 5f8bcca97a56c..e875f280dae90 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution2.java +++ b/solution/0800-0899/0846.Hand of Straights/Solution2.java @@ -4,22 +4,21 @@ public boolean isNStraightHand(int[] hand, int groupSize) { return false; } TreeMap tm = new TreeMap<>(); - for (int h : hand) { - tm.put(h, tm.getOrDefault(h, 0) + 1); + for (int x : hand) { + tm.merge(x, 1, Integer::sum); } while (!tm.isEmpty()) { - int v = tm.firstKey(); - for (int i = v; i < v + groupSize; ++i) { - if (!tm.containsKey(i)) { + int x = tm.firstKey(); + for (int y = x; y < x + groupSize; ++y) { + int t = tm.merge(y, -1, Integer::sum); + if (t < 0) { return false; } - if (tm.get(i) == 1) { - tm.remove(i); - } else { - tm.put(i, tm.get(i) - 1); + if (t == 0) { + tm.remove(y); } } } return true; } -} \ No newline at end of file +} diff --git a/solution/0800-0899/0846.Hand of Straights/Solution2.py b/solution/0800-0899/0846.Hand of Straights/Solution2.py index 60cc15f201b41..3e193822d7b4f 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution2.py +++ b/solution/0800-0899/0846.Hand of Straights/Solution2.py @@ -1,20 +1,16 @@ class Solution: def isNStraightHand(self, hand: List[int], groupSize: int) -> bool: - if len(hand) % groupSize != 0: + if len(hand) % groupSize: return False - sd = SortedDict() - for h in hand: - if h in sd: - sd[h] += 1 - else: - sd[h] = 1 + cnt = Counter(hand) + sd = SortedDict(cnt) while sd: - v = sd.peekitem(0)[0] - for i in range(v, v + groupSize): - if i not in sd: + x = next(iter(sd)) + for y in range(x, x + groupSize): + if y not in sd: return False - if sd[i] == 1: - sd.pop(i) + if sd[y] == 1: + del sd[y] else: - sd[i] -= 1 + sd[y] -= 1 return True diff --git a/solution/0800-0899/0846.Hand of Straights/Solution2.ts b/solution/0800-0899/0846.Hand of Straights/Solution2.ts index 5cd2d00b224b8..10e86ac07d79d 100644 --- a/solution/0800-0899/0846.Hand of Straights/Solution2.ts +++ b/solution/0800-0899/0846.Hand of Straights/Solution2.ts @@ -1,29 +1,507 @@ function isNStraightHand(hand: number[], groupSize: number): boolean { - const n = hand.length; - if (n % groupSize) { + if (hand.length % groupSize !== 0) { return false; } + const tm = new TreeMap(); + for (const x of hand) { + tm.set(x, (tm.get(x) || 0) + 1); + } + while (tm.size()) { + const x = tm.first()![0]; + for (let y = x; y < x + groupSize; ++y) { + if (!tm.has(y)) { + return false; + } + if (tm.get(y)! === 1) { + tm.delete(y); + } else { + tm.set(y, tm.get(y)! - 1); + } + } + } + return true; +} + +type Compare = (lhs: T, rhs: T) => number; + +class RBTreeNode { + data: T; + count: number; + left: RBTreeNode | null; + right: RBTreeNode | null; + parent: RBTreeNode | null; + color: number; + constructor(data: T) { + this.data = data; + this.left = this.right = this.parent = null; + this.color = 0; + this.count = 1; + } + + sibling(): RBTreeNode | null { + if (!this.parent) return null; // sibling null if no parent + return this.isOnLeft() ? this.parent.right : this.parent.left; + } + + isOnLeft(): boolean { + return this === this.parent!.left; + } + + hasRedChild(): boolean { + return ( + Boolean(this.left && this.left.color === 0) || + Boolean(this.right && this.right.color === 0) + ); + } +} + +class RBTree { + root: RBTreeNode | null; + lt: (l: T, r: T) => boolean; + constructor(compare: Compare = (l: T, r: T) => (l < r ? -1 : l > r ? 1 : 0)) { + this.root = null; + this.lt = (l: T, r: T) => compare(l, r) < 0; + } + + rotateLeft(pt: RBTreeNode): void { + const right = pt.right!; + pt.right = right.left; + + if (pt.right) pt.right.parent = pt; + right.parent = pt.parent; + + if (!pt.parent) this.root = right; + else if (pt === pt.parent.left) pt.parent.left = right; + else pt.parent.right = right; + + right.left = pt; + pt.parent = right; + } + + rotateRight(pt: RBTreeNode): void { + const left = pt.left!; + pt.left = left.right; + + if (pt.left) pt.left.parent = pt; + left.parent = pt.parent; + + if (!pt.parent) this.root = left; + else if (pt === pt.parent.left) pt.parent.left = left; + else pt.parent.right = left; + + left.right = pt; + pt.parent = left; + } - const groups: number[][] = Array.from({ length: n / groupSize }, () => []); - hand.sort((a, b) => a - b); + swapColor(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.color; + p1.color = p2.color; + p2.color = tmp; + } + + swapData(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.data; + p1.data = p2.data; + p2.data = tmp; + } + + fixAfterInsert(pt: RBTreeNode): void { + let parent = null; + let grandParent = null; + + while (pt !== this.root && pt.color !== 1 && pt.parent?.color === 0) { + parent = pt.parent; + grandParent = pt.parent.parent; + + /* Case : A + Parent of pt is left child of Grand-parent of pt */ + if (parent === grandParent?.left) { + const uncle = grandParent.right; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle && uncle.color === 0) { + grandParent.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent; + } else { + /* Case : 2 + pt is right child of its parent + Left-rotation required */ + if (pt === parent.right) { + this.rotateLeft(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is left child of its parent + Right-rotation required */ + this.rotateRight(grandParent); + this.swapColor(parent!, grandParent); + pt = parent!; + } + } else { + /* Case : B + Parent of pt is right child of Grand-parent of pt */ + const uncle = grandParent!.left; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle != null && uncle.color === 0) { + grandParent!.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent!; + } else { + /* Case : 2 + pt is left child of its parent + Right-rotation required */ + if (pt === parent.left) { + this.rotateRight(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is right child of its parent + Left-rotation required */ + this.rotateLeft(grandParent!); + this.swapColor(parent!, grandParent!); + pt = parent!; + } + } + } + this.root!.color = 1; + } + + delete(val: T): boolean { + const node = this.find(val); + if (!node) return false; + node.count--; + if (!node.count) this.deleteNode(node); + return true; + } + + deleteAll(val: T): boolean { + const node = this.find(val); + if (!node) return false; + this.deleteNode(node); + return true; + } + + deleteNode(v: RBTreeNode): void { + const u = BSTreplace(v); + + // True when u and v are both black + const uvBlack = (u === null || u.color === 1) && v.color === 1; + const parent = v.parent!; + + if (!u) { + // u is null therefore v is leaf + if (v === this.root) this.root = null; + // v is root, making root null + else { + if (uvBlack) { + // u and v both black + // v is leaf, fix double black at v + this.fixDoubleBlack(v); + } else { + // u or v is red + if (v.sibling()) { + // sibling is not null, make it red" + v.sibling()!.color = 0; + } + } + // delete v from the tree + if (v.isOnLeft()) parent.left = null; + else parent.right = null; + } + return; + } + + if (!v.left || !v.right) { + // v has 1 child + if (v === this.root) { + // v is root, assign the value of u to v, and delete u + v.data = u.data; + v.left = v.right = null; + } else { + // Detach v from tree and move u up + if (v.isOnLeft()) parent.left = u; + else parent.right = u; + u.parent = parent; + if (uvBlack) this.fixDoubleBlack(u); + // u and v both black, fix double black at u + else u.color = 1; // u or v red, color u black + } + return; + } + + // v has 2 children, swap data with successor and recurse + this.swapData(u, v); + this.deleteNode(u); + + // find node that replaces a deleted node in BST + function BSTreplace(x: RBTreeNode): RBTreeNode | null { + // when node have 2 children + if (x.left && x.right) return successor(x.right); + // when leaf + if (!x.left && !x.right) return null; + // when single child + return x.left ?? x.right; + } + // find node that do not have a left child + // in the subtree of the given node + function successor(x: RBTreeNode): RBTreeNode { + let temp = x; + while (temp.left) temp = temp.left; + return temp; + } + } - for (let i = 0; i < n; i++) { - let isPushed = false; + fixDoubleBlack(x: RBTreeNode): void { + if (x === this.root) return; // Reached root - for (const g of groups) { - if (g.length === groupSize || (g.length && hand[i] - g.at(-1)! !== 1)) { - continue; + const sibling = x.sibling(); + const parent = x.parent!; + if (!sibling) { + // No sibiling, double black pushed up + this.fixDoubleBlack(parent); + } else { + if (sibling.color === 0) { + // Sibling red + parent.color = 0; + sibling.color = 1; + if (sibling.isOnLeft()) this.rotateRight(parent); + // left case + else this.rotateLeft(parent); // right case + this.fixDoubleBlack(x); + } else { + // Sibling black + if (sibling.hasRedChild()) { + // at least 1 red children + if (sibling.left && sibling.left.color === 0) { + if (sibling.isOnLeft()) { + // left left + sibling.left.color = sibling.color; + sibling.color = parent.color; + this.rotateRight(parent); + } else { + // right left + sibling.left.color = parent.color; + this.rotateRight(sibling); + this.rotateLeft(parent); + } + } else { + if (sibling.isOnLeft()) { + // left right + sibling.right!.color = parent.color; + this.rotateLeft(sibling); + this.rotateRight(parent); + } else { + // right right + sibling.right!.color = sibling.color; + sibling.color = parent.color; + this.rotateLeft(parent); + } + } + parent.color = 1; + } else { + // 2 black children + sibling.color = 0; + if (parent.color === 1) this.fixDoubleBlack(parent); + else parent.color = 1; + } } + } + } - g.push(hand[i]); - isPushed = true; - break; + insert(data: T): boolean { + // search for a position to insert + let parent = this.root; + while (parent) { + if (this.lt(data, parent.data)) { + if (!parent.left) break; + else parent = parent.left; + } else if (this.lt(parent.data, data)) { + if (!parent.right) break; + else parent = parent.right; + } else break; } - if (!isPushed) { + // insert node into parent + const node = new RBTreeNode(data); + if (!parent) this.root = node; + else if (this.lt(node.data, parent.data)) parent.left = node; + else if (this.lt(parent.data, node.data)) parent.right = node; + else { + parent.count++; return false; } + node.parent = parent; + this.fixAfterInsert(node); + return true; } - return true; + search(predicate: (val: T) => boolean, direction: 'left' | 'right'): T | undefined { + let p = this.root; + let result = null; + while (p) { + if (predicate(p.data)) { + result = p; + p = p[direction]; + } else { + p = p[direction === 'left' ? 'right' : 'left']; + } + } + return result?.data; + } + + find(data: T): RBTreeNode | null { + let p = this.root; + while (p) { + if (this.lt(data, p.data)) { + p = p.left; + } else if (this.lt(p.data, data)) { + p = p.right; + } else break; + } + return p ?? null; + } + + count(data: T): number { + const node = this.find(data); + return node ? node.count : 0; + } + + *inOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.inOrder(root.left!)) yield v; + yield root.data; + for (const v of this.inOrder(root.right!)) yield v; + } + + *reverseInOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.reverseInOrder(root.right!)) yield v; + yield root.data; + for (const v of this.reverseInOrder(root.left!)) yield v; + } +} + +class TreeMap { + _size: number; + tree: RBTree; + map: Map = new Map(); + compare: Compare; + constructor( + collection: Array<[K, V]> | Compare = [], + compare: Compare = (l: K, r: K) => (l < r ? -1 : l > r ? 1 : 0), + ) { + if (typeof collection === 'function') { + compare = collection; + collection = []; + } + this._size = 0; + this.compare = compare; + this.tree = new RBTree(compare); + for (const [key, val] of collection) this.set(key, val); + } + + size(): number { + return this._size; + } + + has(key: K): boolean { + return !!this.tree.find(key); + } + + get(key: K): V | undefined { + return this.map.get(key); + } + + set(key: K, val: V): boolean { + const successful = this.tree.insert(key); + this._size += successful ? 1 : 0; + this.map.set(key, val); + return successful; + } + + delete(key: K): boolean { + const deleted = this.tree.deleteAll(key); + this._size -= deleted ? 1 : 0; + return deleted; + } + + ceil(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) >= 0, 'left')); + } + + floor(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) <= 0, 'right')); + } + + higher(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) > 0, 'left')); + } + + lower(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) < 0, 'right')); + } + + first(): [K, V] | undefined { + return this.toKeyValue(this.tree.inOrder().next().value); + } + + last(): [K, V] | undefined { + return this.toKeyValue(this.tree.reverseInOrder().next().value); + } + + shift(): [K, V] | undefined { + const first = this.first(); + if (first === undefined) return undefined; + this.delete(first[0]); + return first; + } + + pop(): [K, V] | undefined { + const last = this.last(); + if (last === undefined) return undefined; + this.delete(last[0]); + return last; + } + + toKeyValue(key: K): [K, V]; + toKeyValue(key: undefined): undefined; + toKeyValue(key: K | undefined): [K, V] | undefined; + toKeyValue(key: K | undefined): [K, V] | undefined { + return key != null ? [key, this.map.get(key)!] : undefined; + } + + *[Symbol.iterator](): Generator<[K, V], void, void> { + for (const key of this.keys()) yield this.toKeyValue(key); + } + + *keys(): Generator { + for (const key of this.tree.inOrder()) yield key; + } + + *values(): Generator { + for (const key of this.keys()) yield this.map.get(key)!; + return undefined; + } + + *rkeys(): Generator { + for (const key of this.tree.reverseInOrder()) yield key; + return undefined; + } + + *rvalues(): Generator { + for (const key of this.rkeys()) yield this.map.get(key)!; + return undefined; + } } diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/README.md b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/README.md index 7b0a32e6cbd23..f3d77b0187599 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/README.md +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/README.md @@ -71,9 +71,13 @@ tags: ### 方法一:哈希表 + 排序 -我们用一个哈希表 $\textit{cnt}$ 统计数组 $\textit{nums}$ 中每个数字出现的次数,然后对数组 $\textit{nums}$ 进行排序。 +我们首先判断数组 $\textit{nums}$ 的长度是否能被 $\textit{k}$ 整除,如果不能整除,说明无法将数组划分成若干个长度为 $\textit{k}$ 的子数组,直接返回 $\text{false}$。 -接下来,我们遍历数组 $\textit{nums}$,对于数组中的每个数字 $v$,如果 $v$ 在哈希表 $\textit{cnt}$ 中出现的次数不为 $0$,则我们枚举 $v$ 到 $v+k-1$ 的每个数字,如果这些数字在哈希表 $\textit{cnt}$ 中出现的次数都不为 $0$,则我们将这些数字的出现次数减 $1$,如果减 $1$ 后这些数字的出现次数为 $0$,则我们在哈希表 $\textit{cnt}$ 中删除这些数字。否则说明无法将数组划分成若干个长度为 $k$ 的子数组,返回 `false`。如果可以将数组划分成若干个长度为 $k$ 的子数组,则遍历结束后返回 `true`。 +接下来,我们用一个哈希表 $\textit{cnt}$ 统计数组 $\textit{nums}$ 中每个数字出现的次数,然后对数组 $\textit{nums}$ 进行排序。 + +然后,我们遍历排序后的数组 $\textit{nums}$,对于每个数字 $x$,如果 $\textit{cnt}[x]$ 不为 $0$,我们枚举 $x$ 到 $x+\textit{k}-1$ 的每个数字 $y$,如果 $\textit{cnt}[y]$ 为 $0$,说明无法将数组划分成若干个长度为 $\textit{k}$ 的子数组,直接返回 $\text{false}$。否则,我们将 $\textit{cnt}[y]$ 减 $1$。 + +遍历结束后,说明可以将数组划分成若干个长度为 $\textit{k}$ 的子数组,返回 $\text{true}$。 时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 $\textit{nums}$ 的长度。 @@ -84,15 +88,15 @@ tags: ```python class Solution: def isPossibleDivide(self, nums: List[int], k: int) -> bool: + if len(nums) % k: + return False cnt = Counter(nums) - for v in sorted(nums): - if cnt[v]: - for x in range(v, v + k): - if cnt[x] == 0: + for x in sorted(nums): + if cnt[x]: + for y in range(x, x + k): + if cnt[y] == 0: return False - cnt[x] -= 1 - if cnt[x] == 0: - cnt.pop(x) + cnt[y] -= 1 return True ``` @@ -101,20 +105,20 @@ class Solution: ```java class Solution { public boolean isPossibleDivide(int[] nums, int k) { - Map cnt = new HashMap<>(); - for (int v : nums) { - cnt.merge(v, 1, Integer::sum); + if (nums.length % k != 0) { + return false; } Arrays.sort(nums); - for (int v : nums) { - if (cnt.containsKey(v)) { - for (int x = v; x < v + k; ++x) { - if (!cnt.containsKey(x)) { + Map cnt = new HashMap<>(); + for (int x : nums) { + cnt.merge(x, 1, Integer::sum); + } + for (int x : nums) { + if (cnt.getOrDefault(x, 0) > 0) { + for (int y = x; y < x + k; ++y) { + if (cnt.merge(y, -1, Integer::sum) < 0) { return false; } - if (cnt.merge(x, -1, Integer::sum) == 0) { - cnt.remove(x); - } } } } @@ -129,17 +133,22 @@ class Solution { class Solution { public: bool isPossibleDivide(vector& nums, int k) { + if (nums.size() % k) { + return false; + } + ranges::sort(nums); unordered_map cnt; - for (int& v : nums) ++cnt[v]; - sort(nums.begin(), nums.end()); - for (int& v : nums) { - if (cnt.count(v)) { - for (int x = v; x < v + k; ++x) { - if (!cnt.count(x)) { + for (int x : nums) { + ++cnt[x]; + } + for (int x : nums) { + if (cnt.contains(x)) { + for (int y = x; y < x + k; ++y) { + if (!cnt.contains(y)) { return false; } - if (--cnt[x] == 0) { - cnt.erase(x); + if (--cnt[y] == 0) { + cnt.erase(y); } } } @@ -153,21 +162,21 @@ public: ```go func isPossibleDivide(nums []int, k int) bool { - cnt := map[int]int{} - for _, v := range nums { - cnt[v]++ + if len(nums)%k != 0 { + return false } sort.Ints(nums) - for _, v := range nums { - if _, ok := cnt[v]; ok { - for x := v; x < v+k; x++ { - if _, ok := cnt[x]; !ok { + cnt := map[int]int{} + for _, x := range nums { + cnt[x]++ + } + for _, x := range nums { + if cnt[x] > 0 { + for y := x; y < x+k; y++ { + if cnt[y] == 0 { return false } - cnt[x]-- - if cnt[x] == 0 { - delete(cnt, x) - } + cnt[y]-- } } } @@ -175,6 +184,32 @@ func isPossibleDivide(nums []int, k int) bool { } ``` +#### TypeScript + +```ts +function isPossibleDivide(nums: number[], k: number): boolean { + if (nums.length % k !== 0) { + return false; + } + const cnt = new Map(); + for (const x of nums) { + cnt.set(x, (cnt.get(x) || 0) + 1); + } + nums.sort((a, b) => a - b); + for (const x of nums) { + if (cnt.get(x)! > 0) { + for (let y = x; y < x + k; y++) { + if ((cnt.get(y) || 0) === 0) { + return false; + } + cnt.set(y, cnt.get(y)! - 1); + } + } + } + return true; +} +``` + @@ -183,9 +218,11 @@ func isPossibleDivide(nums []int, k int) bool { ### 方法二:有序集合 -我们也可以使用有序集合统计数组 $\textit{nums}$ 中每个数字出现的次数。 +与方法一类似,我们首先判断数组 $\textit{nums}$ 的长度是否能被 $\textit{k}$ 整除,如果不能整除,说明无法将数组划分成若干个长度为 $\textit{k}$ 的子数组,直接返回 $\text{false}$。 -接下来,循环取出有序集合中的最小值 $v$,然后枚举 $v$ 到 $v+k-1$ 的每个数字,如果这些数字在有序集合中出现的次数都不为 $0$,则我们将这些数字的出现次数减 $1$,如果出现次数减 $1$ 后为 $0$,则将该数字从有序集合中删除,否则说明无法将数组划分成若干个长度为 $k$ 的子数组,返回 `false`。如果可以将数组划分成若干个长度为 $k$ 的子数组,则遍历结束后返回 `true`。 +接下来,我们用一个有序集合 $\textit{sd}$ 统计数组 $\textit{nums}$ 中每个数字出现的次数。 + +然后,我们循环取出有序集合中的最小值 $x$,然后枚举 $x$ 到 $x+\textit{k}-1$ 的每个数字 $y$,如果这些数字在有序集合中出现的次数都不为 $0$,则我们将这些数字的出现次数减 $1$,如果出现次数减 $1$ 后为 $0$,则将该数字从有序集合中删除,否则说明无法将数组划分成若干个长度为 $\textit{k}$ 的子数组,返回 $\text{false}$。如果可以将数组划分成若干个长度为 $\textit{k}$ 的子数组,则遍历结束后返回 $\text{true}$。 时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 $\textit{nums}$ 的长度。 @@ -196,23 +233,19 @@ func isPossibleDivide(nums []int, k int) bool { ```python class Solution: def isPossibleDivide(self, nums: List[int], k: int) -> bool: - if len(nums) % k != 0: + if len(nums) % k: return False - sd = SortedDict() - for h in nums: - if h in sd: - sd[h] += 1 - else: - sd[h] = 1 + cnt = Counter(nums) + sd = SortedDict(cnt) while sd: - v = sd.peekitem(0)[0] - for i in range(v, v + k): - if i not in sd: + x = next(iter(sd)) + for y in range(x, x + k): + if y not in sd: return False - if sd[i] == 1: - sd.pop(i) + if sd[y] == 1: + del sd[y] else: - sd[i] -= 1 + sd[y] -= 1 return True ``` @@ -225,17 +258,18 @@ class Solution { return false; } TreeMap tm = new TreeMap<>(); - for (int h : nums) { - tm.merge(h, 1, Integer::sum); + for (int x : nums) { + tm.merge(x, 1, Integer::sum); } while (!tm.isEmpty()) { - int v = tm.firstKey(); - for (int i = v; i < v + k; ++i) { - if (!tm.containsKey(i)) { + int x = tm.firstKey(); + for (int y = x; y < x + k; ++y) { + int t = tm.merge(y, -1, Integer::sum); + if (t < 0) { return false; } - if (tm.merge(i, -1, Integer::sum) == 0) { - tm.remove(i); + if (t == 0) { + tm.remove(y); } } } @@ -254,17 +288,17 @@ public: return false; } map mp; - for (int& h : nums) { - mp[h] += 1; + for (int x : nums) { + ++mp[x]; } while (!mp.empty()) { - int v = mp.begin()->first; - for (int i = v; i < v + k; ++i) { - if (!mp.contains(i)) { + int x = mp.begin()->first; + for (int y = x; y < x + k; ++y) { + if (!mp.contains(y)) { return false; } - if (--mp[i] == 0) { - mp.erase(i); + if (--mp[y] == 0) { + mp.erase(y); } } } @@ -280,24 +314,25 @@ func isPossibleDivide(nums []int, k int) bool { if len(nums)%k != 0 { return false } - m := treemap.NewWithIntComparator() - for _, h := range nums { - if v, ok := m.Get(h); ok { - m.Put(h, v.(int)+1) + tm := treemap.NewWithIntComparator() + for _, x := range nums { + if v, ok := tm.Get(x); ok { + tm.Put(x, v.(int)+1) } else { - m.Put(h, 1) + tm.Put(x, 1) } } - for !m.Empty() { - v, _ := m.Min() - for i := v.(int); i < v.(int)+k; i++ { - if _, ok := m.Get(i); !ok { - return false - } - if v, _ := m.Get(i); v.(int) == 1 { - m.Remove(i) + for !tm.Empty() { + x, _ := tm.Min() + for y := x.(int); y < x.(int)+k; y++ { + if v, ok := tm.Get(y); ok { + if v.(int) == 1 { + tm.Remove(y) + } else { + tm.Put(y, v.(int)-1) + } } else { - m.Put(i, v.(int)-1) + return false } } } @@ -305,6 +340,518 @@ func isPossibleDivide(nums []int, k int) bool { } ``` +#### TypeScript + +```ts +function isPossibleDivide(nums: number[], k: number): boolean { + if (nums.length % k !== 0) { + return false; + } + const tm = new TreeMap(); + for (const x of nums) { + tm.set(x, (tm.get(x) || 0) + 1); + } + while (tm.size()) { + const x = tm.first()![0]; + for (let y = x; y < x + k; ++y) { + if (!tm.has(y)) { + return false; + } + if (tm.get(y)! === 1) { + tm.delete(y); + } else { + tm.set(y, tm.get(y)! - 1); + } + } + } + return true; +} + +type Compare = (lhs: T, rhs: T) => number; + +class RBTreeNode { + data: T; + count: number; + left: RBTreeNode | null; + right: RBTreeNode | null; + parent: RBTreeNode | null; + color: number; + constructor(data: T) { + this.data = data; + this.left = this.right = this.parent = null; + this.color = 0; + this.count = 1; + } + + sibling(): RBTreeNode | null { + if (!this.parent) return null; // sibling null if no parent + return this.isOnLeft() ? this.parent.right : this.parent.left; + } + + isOnLeft(): boolean { + return this === this.parent!.left; + } + + hasRedChild(): boolean { + return ( + Boolean(this.left && this.left.color === 0) || + Boolean(this.right && this.right.color === 0) + ); + } +} + +class RBTree { + root: RBTreeNode | null; + lt: (l: T, r: T) => boolean; + constructor(compare: Compare = (l: T, r: T) => (l < r ? -1 : l > r ? 1 : 0)) { + this.root = null; + this.lt = (l: T, r: T) => compare(l, r) < 0; + } + + rotateLeft(pt: RBTreeNode): void { + const right = pt.right!; + pt.right = right.left; + + if (pt.right) pt.right.parent = pt; + right.parent = pt.parent; + + if (!pt.parent) this.root = right; + else if (pt === pt.parent.left) pt.parent.left = right; + else pt.parent.right = right; + + right.left = pt; + pt.parent = right; + } + + rotateRight(pt: RBTreeNode): void { + const left = pt.left!; + pt.left = left.right; + + if (pt.left) pt.left.parent = pt; + left.parent = pt.parent; + + if (!pt.parent) this.root = left; + else if (pt === pt.parent.left) pt.parent.left = left; + else pt.parent.right = left; + + left.right = pt; + pt.parent = left; + } + + swapColor(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.color; + p1.color = p2.color; + p2.color = tmp; + } + + swapData(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.data; + p1.data = p2.data; + p2.data = tmp; + } + + fixAfterInsert(pt: RBTreeNode): void { + let parent = null; + let grandParent = null; + + while (pt !== this.root && pt.color !== 1 && pt.parent?.color === 0) { + parent = pt.parent; + grandParent = pt.parent.parent; + + /* Case : A + Parent of pt is left child of Grand-parent of pt */ + if (parent === grandParent?.left) { + const uncle = grandParent.right; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle && uncle.color === 0) { + grandParent.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent; + } else { + /* Case : 2 + pt is right child of its parent + Left-rotation required */ + if (pt === parent.right) { + this.rotateLeft(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is left child of its parent + Right-rotation required */ + this.rotateRight(grandParent); + this.swapColor(parent!, grandParent); + pt = parent!; + } + } else { + /* Case : B + Parent of pt is right child of Grand-parent of pt */ + const uncle = grandParent!.left; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle != null && uncle.color === 0) { + grandParent!.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent!; + } else { + /* Case : 2 + pt is left child of its parent + Right-rotation required */ + if (pt === parent.left) { + this.rotateRight(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is right child of its parent + Left-rotation required */ + this.rotateLeft(grandParent!); + this.swapColor(parent!, grandParent!); + pt = parent!; + } + } + } + this.root!.color = 1; + } + + delete(val: T): boolean { + const node = this.find(val); + if (!node) return false; + node.count--; + if (!node.count) this.deleteNode(node); + return true; + } + + deleteAll(val: T): boolean { + const node = this.find(val); + if (!node) return false; + this.deleteNode(node); + return true; + } + + deleteNode(v: RBTreeNode): void { + const u = BSTreplace(v); + + // True when u and v are both black + const uvBlack = (u === null || u.color === 1) && v.color === 1; + const parent = v.parent!; + + if (!u) { + // u is null therefore v is leaf + if (v === this.root) this.root = null; + // v is root, making root null + else { + if (uvBlack) { + // u and v both black + // v is leaf, fix double black at v + this.fixDoubleBlack(v); + } else { + // u or v is red + if (v.sibling()) { + // sibling is not null, make it red" + v.sibling()!.color = 0; + } + } + // delete v from the tree + if (v.isOnLeft()) parent.left = null; + else parent.right = null; + } + return; + } + + if (!v.left || !v.right) { + // v has 1 child + if (v === this.root) { + // v is root, assign the value of u to v, and delete u + v.data = u.data; + v.left = v.right = null; + } else { + // Detach v from tree and move u up + if (v.isOnLeft()) parent.left = u; + else parent.right = u; + u.parent = parent; + if (uvBlack) this.fixDoubleBlack(u); + // u and v both black, fix double black at u + else u.color = 1; // u or v red, color u black + } + return; + } + + // v has 2 children, swap data with successor and recurse + this.swapData(u, v); + this.deleteNode(u); + + // find node that replaces a deleted node in BST + function BSTreplace(x: RBTreeNode): RBTreeNode | null { + // when node have 2 children + if (x.left && x.right) return successor(x.right); + // when leaf + if (!x.left && !x.right) return null; + // when single child + return x.left ?? x.right; + } + // find node that do not have a left child + // in the subtree of the given node + function successor(x: RBTreeNode): RBTreeNode { + let temp = x; + while (temp.left) temp = temp.left; + return temp; + } + } + + fixDoubleBlack(x: RBTreeNode): void { + if (x === this.root) return; // Reached root + + const sibling = x.sibling(); + const parent = x.parent!; + if (!sibling) { + // No sibiling, double black pushed up + this.fixDoubleBlack(parent); + } else { + if (sibling.color === 0) { + // Sibling red + parent.color = 0; + sibling.color = 1; + if (sibling.isOnLeft()) this.rotateRight(parent); + // left case + else this.rotateLeft(parent); // right case + this.fixDoubleBlack(x); + } else { + // Sibling black + if (sibling.hasRedChild()) { + // at least 1 red children + if (sibling.left && sibling.left.color === 0) { + if (sibling.isOnLeft()) { + // left left + sibling.left.color = sibling.color; + sibling.color = parent.color; + this.rotateRight(parent); + } else { + // right left + sibling.left.color = parent.color; + this.rotateRight(sibling); + this.rotateLeft(parent); + } + } else { + if (sibling.isOnLeft()) { + // left right + sibling.right!.color = parent.color; + this.rotateLeft(sibling); + this.rotateRight(parent); + } else { + // right right + sibling.right!.color = sibling.color; + sibling.color = parent.color; + this.rotateLeft(parent); + } + } + parent.color = 1; + } else { + // 2 black children + sibling.color = 0; + if (parent.color === 1) this.fixDoubleBlack(parent); + else parent.color = 1; + } + } + } + } + + insert(data: T): boolean { + // search for a position to insert + let parent = this.root; + while (parent) { + if (this.lt(data, parent.data)) { + if (!parent.left) break; + else parent = parent.left; + } else if (this.lt(parent.data, data)) { + if (!parent.right) break; + else parent = parent.right; + } else break; + } + + // insert node into parent + const node = new RBTreeNode(data); + if (!parent) this.root = node; + else if (this.lt(node.data, parent.data)) parent.left = node; + else if (this.lt(parent.data, node.data)) parent.right = node; + else { + parent.count++; + return false; + } + node.parent = parent; + this.fixAfterInsert(node); + return true; + } + + search(predicate: (val: T) => boolean, direction: 'left' | 'right'): T | undefined { + let p = this.root; + let result = null; + while (p) { + if (predicate(p.data)) { + result = p; + p = p[direction]; + } else { + p = p[direction === 'left' ? 'right' : 'left']; + } + } + return result?.data; + } + + find(data: T): RBTreeNode | null { + let p = this.root; + while (p) { + if (this.lt(data, p.data)) { + p = p.left; + } else if (this.lt(p.data, data)) { + p = p.right; + } else break; + } + return p ?? null; + } + + count(data: T): number { + const node = this.find(data); + return node ? node.count : 0; + } + + *inOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.inOrder(root.left!)) yield v; + yield root.data; + for (const v of this.inOrder(root.right!)) yield v; + } + + *reverseInOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.reverseInOrder(root.right!)) yield v; + yield root.data; + for (const v of this.reverseInOrder(root.left!)) yield v; + } +} + +class TreeMap { + _size: number; + tree: RBTree; + map: Map = new Map(); + compare: Compare; + constructor( + collection: Array<[K, V]> | Compare = [], + compare: Compare = (l: K, r: K) => (l < r ? -1 : l > r ? 1 : 0), + ) { + if (typeof collection === 'function') { + compare = collection; + collection = []; + } + this._size = 0; + this.compare = compare; + this.tree = new RBTree(compare); + for (const [key, val] of collection) this.set(key, val); + } + + size(): number { + return this._size; + } + + has(key: K): boolean { + return !!this.tree.find(key); + } + + get(key: K): V | undefined { + return this.map.get(key); + } + + set(key: K, val: V): boolean { + const successful = this.tree.insert(key); + this._size += successful ? 1 : 0; + this.map.set(key, val); + return successful; + } + + delete(key: K): boolean { + const deleted = this.tree.deleteAll(key); + this._size -= deleted ? 1 : 0; + return deleted; + } + + ceil(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) >= 0, 'left')); + } + + floor(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) <= 0, 'right')); + } + + higher(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) > 0, 'left')); + } + + lower(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) < 0, 'right')); + } + + first(): [K, V] | undefined { + return this.toKeyValue(this.tree.inOrder().next().value); + } + + last(): [K, V] | undefined { + return this.toKeyValue(this.tree.reverseInOrder().next().value); + } + + shift(): [K, V] | undefined { + const first = this.first(); + if (first === undefined) return undefined; + this.delete(first[0]); + return first; + } + + pop(): [K, V] | undefined { + const last = this.last(); + if (last === undefined) return undefined; + this.delete(last[0]); + return last; + } + + toKeyValue(key: K): [K, V]; + toKeyValue(key: undefined): undefined; + toKeyValue(key: K | undefined): [K, V] | undefined; + toKeyValue(key: K | undefined): [K, V] | undefined { + return key != null ? [key, this.map.get(key)!] : undefined; + } + + *[Symbol.iterator](): Generator<[K, V], void, void> { + for (const key of this.keys()) yield this.toKeyValue(key); + } + + *keys(): Generator { + for (const key of this.tree.inOrder()) yield key; + } + + *values(): Generator { + for (const key of this.keys()) yield this.map.get(key)!; + return undefined; + } + + *rkeys(): Generator { + for (const key of this.tree.reverseInOrder()) yield key; + return undefined; + } + + *rvalues(): Generator { + for (const key of this.rkeys()) yield this.map.get(key)!; + return undefined; + } +} +``` + diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/README_EN.md b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/README_EN.md index 3357bd9a99781..134f2e4aba0a0 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/README_EN.md +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/README_EN.md @@ -69,9 +69,13 @@ tags: ### Solution 1: Hash Table + Sorting -We use a hash table $\textit{cnt}$ to count the occurrences of each number in the array $\textit{nums}$, and then sort the array $\textit{nums}$. +First, we check if the length of the array $\textit{nums}$ is divisible by $\textit{k}$. If it is not divisible, it means the array cannot be divided into subarrays of length $\textit{k}$, and we return $\text{false}$ directly. -Next, we traverse the array $\textit{nums}$. For each number $v$ in the array, if the count of $v$ in the hash table $\textit{cnt}$ is not zero, we enumerate each number from $v$ to $v+k-1$. If the counts of these numbers in the hash table $\textit{cnt}$ are all non-zero, we decrement the counts of these numbers by 1. If the count becomes zero after decrementing, we remove these numbers from the hash table $\textit{cnt}$. Otherwise, it means we cannot divide the array into several subarrays of length $k$, and we return `false`. If we can divide the array into several subarrays of length $k$, we return `true` after the traversal. +Next, we use a hash table $\textit{cnt}$ to count the occurrences of each number in the array $\textit{nums}$, and then we sort the array $\textit{nums}$. + +After sorting, we iterate through the array $\textit{nums}$, and for each number $x$, if $\textit{cnt}[x]$ is not $0$, we enumerate each number $y$ from $x$ to $x + \textit{k} - 1$. If $\textit{cnt}[y]$ is $0$, it means we cannot divide the array into subarrays of length $\textit{k}$, and we return $\text{false}$ directly. Otherwise, we decrement $\textit{cnt}[y]$ by $1$. + +After the loop, if no issues were encountered, it means we can divide the array into subarrays of length $\textit{k}$, and we return $\text{true}$. The time complexity is $O(n \times \log n)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array $\textit{nums}$. @@ -82,15 +86,15 @@ The time complexity is $O(n \times \log n)$, and the space complexity is $O(n)$. ```python class Solution: def isPossibleDivide(self, nums: List[int], k: int) -> bool: + if len(nums) % k: + return False cnt = Counter(nums) - for v in sorted(nums): - if cnt[v]: - for x in range(v, v + k): - if cnt[x] == 0: + for x in sorted(nums): + if cnt[x]: + for y in range(x, x + k): + if cnt[y] == 0: return False - cnt[x] -= 1 - if cnt[x] == 0: - cnt.pop(x) + cnt[y] -= 1 return True ``` @@ -99,20 +103,20 @@ class Solution: ```java class Solution { public boolean isPossibleDivide(int[] nums, int k) { - Map cnt = new HashMap<>(); - for (int v : nums) { - cnt.merge(v, 1, Integer::sum); + if (nums.length % k != 0) { + return false; } Arrays.sort(nums); - for (int v : nums) { - if (cnt.containsKey(v)) { - for (int x = v; x < v + k; ++x) { - if (!cnt.containsKey(x)) { + Map cnt = new HashMap<>(); + for (int x : nums) { + cnt.merge(x, 1, Integer::sum); + } + for (int x : nums) { + if (cnt.getOrDefault(x, 0) > 0) { + for (int y = x; y < x + k; ++y) { + if (cnt.merge(y, -1, Integer::sum) < 0) { return false; } - if (cnt.merge(x, -1, Integer::sum) == 0) { - cnt.remove(x); - } } } } @@ -127,17 +131,22 @@ class Solution { class Solution { public: bool isPossibleDivide(vector& nums, int k) { + if (nums.size() % k) { + return false; + } + ranges::sort(nums); unordered_map cnt; - for (int& v : nums) ++cnt[v]; - sort(nums.begin(), nums.end()); - for (int& v : nums) { - if (cnt.count(v)) { - for (int x = v; x < v + k; ++x) { - if (!cnt.count(x)) { + for (int x : nums) { + ++cnt[x]; + } + for (int x : nums) { + if (cnt.contains(x)) { + for (int y = x; y < x + k; ++y) { + if (!cnt.contains(y)) { return false; } - if (--cnt[x] == 0) { - cnt.erase(x); + if (--cnt[y] == 0) { + cnt.erase(y); } } } @@ -151,21 +160,21 @@ public: ```go func isPossibleDivide(nums []int, k int) bool { - cnt := map[int]int{} - for _, v := range nums { - cnt[v]++ + if len(nums)%k != 0 { + return false } sort.Ints(nums) - for _, v := range nums { - if _, ok := cnt[v]; ok { - for x := v; x < v+k; x++ { - if _, ok := cnt[x]; !ok { + cnt := map[int]int{} + for _, x := range nums { + cnt[x]++ + } + for _, x := range nums { + if cnt[x] > 0 { + for y := x; y < x+k; y++ { + if cnt[y] == 0 { return false } - cnt[x]-- - if cnt[x] == 0 { - delete(cnt, x) - } + cnt[y]-- } } } @@ -173,17 +182,47 @@ func isPossibleDivide(nums []int, k int) bool { } ``` +#### TypeScript + +```ts +function isPossibleDivide(nums: number[], k: number): boolean { + if (nums.length % k !== 0) { + return false; + } + const cnt = new Map(); + for (const x of nums) { + cnt.set(x, (cnt.get(x) || 0) + 1); + } + nums.sort((a, b) => a - b); + for (const x of nums) { + if (cnt.get(x)! > 0) { + for (let y = x; y < x + k; y++) { + if ((cnt.get(y) || 0) === 0) { + return false; + } + cnt.set(y, cnt.get(y)! - 1); + } + } + } + return true; +} +``` + -### Solution 1: Ordered Set +### Solution 2: Ordered Set + +Similar to Solution 1, we first check if the length of the array $\textit{nums}$ is divisible by $\textit{k}$. If it is not divisible, it means the array cannot be divided into subarrays of length $\textit{k}$, and we return $\text{false}$ directly. -We can also use an ordered set to count the occurrences of each number in the array $\textit{nums}$. +Next, we use an ordered set $\textit{sd}$ to count the occurrences of each number in the array $\textit{nums}$. -Next, we loop to extract the minimum value $v$ from the ordered set, then enumerate each number from $v$ to $v+k-1$. If the occurrences of these numbers in the ordered set are all non-zero, we decrement the occurrence count of these numbers by 1. If the occurrence count becomes 0 after decrementing, we remove the number from the ordered set. Otherwise, it means we cannot divide the array into several subarrays of length $k$, and we return `false`. If we can divide the array into several subarrays of length $k$, we return `true` after the traversal. +Then, we repeatedly extract the smallest value $x$ from the ordered set and enumerate each number $y$ from $x$ to $x + \textit{k} - 1$. If these numbers all appear in the ordered set with non-zero occurrences, we decrement their occurrence count by $1$. If the occurrence count becomes $0$ after the decrement, we remove the number from the ordered set; otherwise, it means we cannot divide the array into subarrays of length $\textit{k}$, and we return $\text{false}$. + +If we can successfully divide the array into subarrays of length $\textit{k}$, we return $\text{true}$ after completing the traversal. The time complexity is $O(n \times \log n)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array $\textit{nums}$. @@ -194,23 +233,19 @@ The time complexity is $O(n \times \log n)$, and the space complexity is $O(n)$. ```python class Solution: def isPossibleDivide(self, nums: List[int], k: int) -> bool: - if len(nums) % k != 0: + if len(nums) % k: return False - sd = SortedDict() - for h in nums: - if h in sd: - sd[h] += 1 - else: - sd[h] = 1 + cnt = Counter(nums) + sd = SortedDict(cnt) while sd: - v = sd.peekitem(0)[0] - for i in range(v, v + k): - if i not in sd: + x = next(iter(sd)) + for y in range(x, x + k): + if y not in sd: return False - if sd[i] == 1: - sd.pop(i) + if sd[y] == 1: + del sd[y] else: - sd[i] -= 1 + sd[y] -= 1 return True ``` @@ -223,17 +258,18 @@ class Solution { return false; } TreeMap tm = new TreeMap<>(); - for (int h : nums) { - tm.merge(h, 1, Integer::sum); + for (int x : nums) { + tm.merge(x, 1, Integer::sum); } while (!tm.isEmpty()) { - int v = tm.firstKey(); - for (int i = v; i < v + k; ++i) { - if (!tm.containsKey(i)) { + int x = tm.firstKey(); + for (int y = x; y < x + k; ++y) { + int t = tm.merge(y, -1, Integer::sum); + if (t < 0) { return false; } - if (tm.merge(i, -1, Integer::sum) == 0) { - tm.remove(i); + if (t == 0) { + tm.remove(y); } } } @@ -252,17 +288,17 @@ public: return false; } map mp; - for (int& h : nums) { - mp[h] += 1; + for (int x : nums) { + ++mp[x]; } while (!mp.empty()) { - int v = mp.begin()->first; - for (int i = v; i < v + k; ++i) { - if (!mp.contains(i)) { + int x = mp.begin()->first; + for (int y = x; y < x + k; ++y) { + if (!mp.contains(y)) { return false; } - if (--mp[i] == 0) { - mp.erase(i); + if (--mp[y] == 0) { + mp.erase(y); } } } @@ -278,24 +314,25 @@ func isPossibleDivide(nums []int, k int) bool { if len(nums)%k != 0 { return false } - m := treemap.NewWithIntComparator() - for _, h := range nums { - if v, ok := m.Get(h); ok { - m.Put(h, v.(int)+1) + tm := treemap.NewWithIntComparator() + for _, x := range nums { + if v, ok := tm.Get(x); ok { + tm.Put(x, v.(int)+1) } else { - m.Put(h, 1) + tm.Put(x, 1) } } - for !m.Empty() { - v, _ := m.Min() - for i := v.(int); i < v.(int)+k; i++ { - if _, ok := m.Get(i); !ok { - return false - } - if v, _ := m.Get(i); v.(int) == 1 { - m.Remove(i) + for !tm.Empty() { + x, _ := tm.Min() + for y := x.(int); y < x.(int)+k; y++ { + if v, ok := tm.Get(y); ok { + if v.(int) == 1 { + tm.Remove(y) + } else { + tm.Put(y, v.(int)-1) + } } else { - m.Put(i, v.(int)-1) + return false } } } @@ -303,6 +340,518 @@ func isPossibleDivide(nums []int, k int) bool { } ``` +#### TypeScript + +```ts +function isPossibleDivide(nums: number[], k: number): boolean { + if (nums.length % k !== 0) { + return false; + } + const tm = new TreeMap(); + for (const x of nums) { + tm.set(x, (tm.get(x) || 0) + 1); + } + while (tm.size()) { + const x = tm.first()![0]; + for (let y = x; y < x + k; ++y) { + if (!tm.has(y)) { + return false; + } + if (tm.get(y)! === 1) { + tm.delete(y); + } else { + tm.set(y, tm.get(y)! - 1); + } + } + } + return true; +} + +type Compare = (lhs: T, rhs: T) => number; + +class RBTreeNode { + data: T; + count: number; + left: RBTreeNode | null; + right: RBTreeNode | null; + parent: RBTreeNode | null; + color: number; + constructor(data: T) { + this.data = data; + this.left = this.right = this.parent = null; + this.color = 0; + this.count = 1; + } + + sibling(): RBTreeNode | null { + if (!this.parent) return null; // sibling null if no parent + return this.isOnLeft() ? this.parent.right : this.parent.left; + } + + isOnLeft(): boolean { + return this === this.parent!.left; + } + + hasRedChild(): boolean { + return ( + Boolean(this.left && this.left.color === 0) || + Boolean(this.right && this.right.color === 0) + ); + } +} + +class RBTree { + root: RBTreeNode | null; + lt: (l: T, r: T) => boolean; + constructor(compare: Compare = (l: T, r: T) => (l < r ? -1 : l > r ? 1 : 0)) { + this.root = null; + this.lt = (l: T, r: T) => compare(l, r) < 0; + } + + rotateLeft(pt: RBTreeNode): void { + const right = pt.right!; + pt.right = right.left; + + if (pt.right) pt.right.parent = pt; + right.parent = pt.parent; + + if (!pt.parent) this.root = right; + else if (pt === pt.parent.left) pt.parent.left = right; + else pt.parent.right = right; + + right.left = pt; + pt.parent = right; + } + + rotateRight(pt: RBTreeNode): void { + const left = pt.left!; + pt.left = left.right; + + if (pt.left) pt.left.parent = pt; + left.parent = pt.parent; + + if (!pt.parent) this.root = left; + else if (pt === pt.parent.left) pt.parent.left = left; + else pt.parent.right = left; + + left.right = pt; + pt.parent = left; + } + + swapColor(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.color; + p1.color = p2.color; + p2.color = tmp; + } + + swapData(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.data; + p1.data = p2.data; + p2.data = tmp; + } + + fixAfterInsert(pt: RBTreeNode): void { + let parent = null; + let grandParent = null; + + while (pt !== this.root && pt.color !== 1 && pt.parent?.color === 0) { + parent = pt.parent; + grandParent = pt.parent.parent; + + /* Case : A + Parent of pt is left child of Grand-parent of pt */ + if (parent === grandParent?.left) { + const uncle = grandParent.right; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle && uncle.color === 0) { + grandParent.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent; + } else { + /* Case : 2 + pt is right child of its parent + Left-rotation required */ + if (pt === parent.right) { + this.rotateLeft(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is left child of its parent + Right-rotation required */ + this.rotateRight(grandParent); + this.swapColor(parent!, grandParent); + pt = parent!; + } + } else { + /* Case : B + Parent of pt is right child of Grand-parent of pt */ + const uncle = grandParent!.left; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle != null && uncle.color === 0) { + grandParent!.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent!; + } else { + /* Case : 2 + pt is left child of its parent + Right-rotation required */ + if (pt === parent.left) { + this.rotateRight(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is right child of its parent + Left-rotation required */ + this.rotateLeft(grandParent!); + this.swapColor(parent!, grandParent!); + pt = parent!; + } + } + } + this.root!.color = 1; + } + + delete(val: T): boolean { + const node = this.find(val); + if (!node) return false; + node.count--; + if (!node.count) this.deleteNode(node); + return true; + } + + deleteAll(val: T): boolean { + const node = this.find(val); + if (!node) return false; + this.deleteNode(node); + return true; + } + + deleteNode(v: RBTreeNode): void { + const u = BSTreplace(v); + + // True when u and v are both black + const uvBlack = (u === null || u.color === 1) && v.color === 1; + const parent = v.parent!; + + if (!u) { + // u is null therefore v is leaf + if (v === this.root) this.root = null; + // v is root, making root null + else { + if (uvBlack) { + // u and v both black + // v is leaf, fix double black at v + this.fixDoubleBlack(v); + } else { + // u or v is red + if (v.sibling()) { + // sibling is not null, make it red" + v.sibling()!.color = 0; + } + } + // delete v from the tree + if (v.isOnLeft()) parent.left = null; + else parent.right = null; + } + return; + } + + if (!v.left || !v.right) { + // v has 1 child + if (v === this.root) { + // v is root, assign the value of u to v, and delete u + v.data = u.data; + v.left = v.right = null; + } else { + // Detach v from tree and move u up + if (v.isOnLeft()) parent.left = u; + else parent.right = u; + u.parent = parent; + if (uvBlack) this.fixDoubleBlack(u); + // u and v both black, fix double black at u + else u.color = 1; // u or v red, color u black + } + return; + } + + // v has 2 children, swap data with successor and recurse + this.swapData(u, v); + this.deleteNode(u); + + // find node that replaces a deleted node in BST + function BSTreplace(x: RBTreeNode): RBTreeNode | null { + // when node have 2 children + if (x.left && x.right) return successor(x.right); + // when leaf + if (!x.left && !x.right) return null; + // when single child + return x.left ?? x.right; + } + // find node that do not have a left child + // in the subtree of the given node + function successor(x: RBTreeNode): RBTreeNode { + let temp = x; + while (temp.left) temp = temp.left; + return temp; + } + } + + fixDoubleBlack(x: RBTreeNode): void { + if (x === this.root) return; // Reached root + + const sibling = x.sibling(); + const parent = x.parent!; + if (!sibling) { + // No sibiling, double black pushed up + this.fixDoubleBlack(parent); + } else { + if (sibling.color === 0) { + // Sibling red + parent.color = 0; + sibling.color = 1; + if (sibling.isOnLeft()) this.rotateRight(parent); + // left case + else this.rotateLeft(parent); // right case + this.fixDoubleBlack(x); + } else { + // Sibling black + if (sibling.hasRedChild()) { + // at least 1 red children + if (sibling.left && sibling.left.color === 0) { + if (sibling.isOnLeft()) { + // left left + sibling.left.color = sibling.color; + sibling.color = parent.color; + this.rotateRight(parent); + } else { + // right left + sibling.left.color = parent.color; + this.rotateRight(sibling); + this.rotateLeft(parent); + } + } else { + if (sibling.isOnLeft()) { + // left right + sibling.right!.color = parent.color; + this.rotateLeft(sibling); + this.rotateRight(parent); + } else { + // right right + sibling.right!.color = sibling.color; + sibling.color = parent.color; + this.rotateLeft(parent); + } + } + parent.color = 1; + } else { + // 2 black children + sibling.color = 0; + if (parent.color === 1) this.fixDoubleBlack(parent); + else parent.color = 1; + } + } + } + } + + insert(data: T): boolean { + // search for a position to insert + let parent = this.root; + while (parent) { + if (this.lt(data, parent.data)) { + if (!parent.left) break; + else parent = parent.left; + } else if (this.lt(parent.data, data)) { + if (!parent.right) break; + else parent = parent.right; + } else break; + } + + // insert node into parent + const node = new RBTreeNode(data); + if (!parent) this.root = node; + else if (this.lt(node.data, parent.data)) parent.left = node; + else if (this.lt(parent.data, node.data)) parent.right = node; + else { + parent.count++; + return false; + } + node.parent = parent; + this.fixAfterInsert(node); + return true; + } + + search(predicate: (val: T) => boolean, direction: 'left' | 'right'): T | undefined { + let p = this.root; + let result = null; + while (p) { + if (predicate(p.data)) { + result = p; + p = p[direction]; + } else { + p = p[direction === 'left' ? 'right' : 'left']; + } + } + return result?.data; + } + + find(data: T): RBTreeNode | null { + let p = this.root; + while (p) { + if (this.lt(data, p.data)) { + p = p.left; + } else if (this.lt(p.data, data)) { + p = p.right; + } else break; + } + return p ?? null; + } + + count(data: T): number { + const node = this.find(data); + return node ? node.count : 0; + } + + *inOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.inOrder(root.left!)) yield v; + yield root.data; + for (const v of this.inOrder(root.right!)) yield v; + } + + *reverseInOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.reverseInOrder(root.right!)) yield v; + yield root.data; + for (const v of this.reverseInOrder(root.left!)) yield v; + } +} + +class TreeMap { + _size: number; + tree: RBTree; + map: Map = new Map(); + compare: Compare; + constructor( + collection: Array<[K, V]> | Compare = [], + compare: Compare = (l: K, r: K) => (l < r ? -1 : l > r ? 1 : 0), + ) { + if (typeof collection === 'function') { + compare = collection; + collection = []; + } + this._size = 0; + this.compare = compare; + this.tree = new RBTree(compare); + for (const [key, val] of collection) this.set(key, val); + } + + size(): number { + return this._size; + } + + has(key: K): boolean { + return !!this.tree.find(key); + } + + get(key: K): V | undefined { + return this.map.get(key); + } + + set(key: K, val: V): boolean { + const successful = this.tree.insert(key); + this._size += successful ? 1 : 0; + this.map.set(key, val); + return successful; + } + + delete(key: K): boolean { + const deleted = this.tree.deleteAll(key); + this._size -= deleted ? 1 : 0; + return deleted; + } + + ceil(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) >= 0, 'left')); + } + + floor(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) <= 0, 'right')); + } + + higher(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) > 0, 'left')); + } + + lower(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) < 0, 'right')); + } + + first(): [K, V] | undefined { + return this.toKeyValue(this.tree.inOrder().next().value); + } + + last(): [K, V] | undefined { + return this.toKeyValue(this.tree.reverseInOrder().next().value); + } + + shift(): [K, V] | undefined { + const first = this.first(); + if (first === undefined) return undefined; + this.delete(first[0]); + return first; + } + + pop(): [K, V] | undefined { + const last = this.last(); + if (last === undefined) return undefined; + this.delete(last[0]); + return last; + } + + toKeyValue(key: K): [K, V]; + toKeyValue(key: undefined): undefined; + toKeyValue(key: K | undefined): [K, V] | undefined; + toKeyValue(key: K | undefined): [K, V] | undefined { + return key != null ? [key, this.map.get(key)!] : undefined; + } + + *[Symbol.iterator](): Generator<[K, V], void, void> { + for (const key of this.keys()) yield this.toKeyValue(key); + } + + *keys(): Generator { + for (const key of this.tree.inOrder()) yield key; + } + + *values(): Generator { + for (const key of this.keys()) yield this.map.get(key)!; + return undefined; + } + + *rkeys(): Generator { + for (const key of this.tree.reverseInOrder()) yield key; + return undefined; + } + + *rvalues(): Generator { + for (const key of this.rkeys()) yield this.map.get(key)!; + return undefined; + } +} +``` + diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.cpp b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.cpp index 64ec622d7852e..fa63b583fc0ad 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.cpp +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.cpp @@ -1,21 +1,26 @@ class Solution { public: bool isPossibleDivide(vector& nums, int k) { + if (nums.size() % k) { + return false; + } + ranges::sort(nums); unordered_map cnt; - for (int& v : nums) ++cnt[v]; - sort(nums.begin(), nums.end()); - for (int& v : nums) { - if (cnt.count(v)) { - for (int x = v; x < v + k; ++x) { - if (!cnt.count(x)) { + for (int x : nums) { + ++cnt[x]; + } + for (int x : nums) { + if (cnt.contains(x)) { + for (int y = x; y < x + k; ++y) { + if (!cnt.contains(y)) { return false; } - if (--cnt[x] == 0) { - cnt.erase(x); + if (--cnt[y] == 0) { + cnt.erase(y); } } } } return true; } -}; \ No newline at end of file +}; diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.go b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.go index da098ac3122ef..72bf15f9fc900 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.go +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.go @@ -1,21 +1,21 @@ func isPossibleDivide(nums []int, k int) bool { - cnt := map[int]int{} - for _, v := range nums { - cnt[v]++ + if len(nums)%k != 0 { + return false } sort.Ints(nums) - for _, v := range nums { - if _, ok := cnt[v]; ok { - for x := v; x < v+k; x++ { - if _, ok := cnt[x]; !ok { + cnt := map[int]int{} + for _, x := range nums { + cnt[x]++ + } + for _, x := range nums { + if cnt[x] > 0 { + for y := x; y < x+k; y++ { + if cnt[y] == 0 { return false } - cnt[x]-- - if cnt[x] == 0 { - delete(cnt, x) - } + cnt[y]-- } } } return true -} \ No newline at end of file +} diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.java b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.java index 78e5e2cb23bb2..1875641aea81d 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.java +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.java @@ -1,19 +1,19 @@ class Solution { public boolean isPossibleDivide(int[] nums, int k) { - Map cnt = new HashMap<>(); - for (int v : nums) { - cnt.merge(v, 1, Integer::sum); + if (nums.length % k != 0) { + return false; } Arrays.sort(nums); - for (int v : nums) { - if (cnt.containsKey(v)) { - for (int x = v; x < v + k; ++x) { - if (!cnt.containsKey(x)) { + Map cnt = new HashMap<>(); + for (int x : nums) { + cnt.merge(x, 1, Integer::sum); + } + for (int x : nums) { + if (cnt.getOrDefault(x, 0) > 0) { + for (int y = x; y < x + k; ++y) { + if (cnt.merge(y, -1, Integer::sum) < 0) { return false; } - if (cnt.merge(x, -1, Integer::sum) == 0) { - cnt.remove(x); - } } } } diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.py b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.py index 2372eb0608602..8a9e233be64f9 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.py +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.py @@ -1,12 +1,12 @@ class Solution: def isPossibleDivide(self, nums: List[int], k: int) -> bool: + if len(nums) % k: + return False cnt = Counter(nums) - for v in sorted(nums): - if cnt[v]: - for x in range(v, v + k): - if cnt[x] == 0: + for x in sorted(nums): + if cnt[x]: + for y in range(x, x + k): + if cnt[y] == 0: return False - cnt[x] -= 1 - if cnt[x] == 0: - cnt.pop(x) + cnt[y] -= 1 return True diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.ts b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.ts new file mode 100644 index 0000000000000..6cf1239c13943 --- /dev/null +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution.ts @@ -0,0 +1,21 @@ +function isPossibleDivide(nums: number[], k: number): boolean { + if (nums.length % k !== 0) { + return false; + } + const cnt = new Map(); + for (const x of nums) { + cnt.set(x, (cnt.get(x) || 0) + 1); + } + nums.sort((a, b) => a - b); + for (const x of nums) { + if (cnt.get(x)! > 0) { + for (let y = x; y < x + k; y++) { + if ((cnt.get(y) || 0) === 0) { + return false; + } + cnt.set(y, cnt.get(y)! - 1); + } + } + } + return true; +} diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.cpp b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.cpp index 64f8a77a8a50c..7c09c14df31f1 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.cpp +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.cpp @@ -5,17 +5,17 @@ class Solution { return false; } map mp; - for (int& h : nums) { - mp[h] += 1; + for (int x : nums) { + ++mp[x]; } while (!mp.empty()) { - int v = mp.begin()->first; - for (int i = v; i < v + k; ++i) { - if (!mp.contains(i)) { + int x = mp.begin()->first; + for (int y = x; y < x + k; ++y) { + if (!mp.contains(y)) { return false; } - if (--mp[i] == 0) { - mp.erase(i); + if (--mp[y] == 0) { + mp.erase(y); } } } diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.go b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.go index a1a6e92d3d4fe..761152cb8902e 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.go +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.go @@ -2,26 +2,27 @@ func isPossibleDivide(nums []int, k int) bool { if len(nums)%k != 0 { return false } - m := treemap.NewWithIntComparator() - for _, h := range nums { - if v, ok := m.Get(h); ok { - m.Put(h, v.(int)+1) + tm := treemap.NewWithIntComparator() + for _, x := range nums { + if v, ok := tm.Get(x); ok { + tm.Put(x, v.(int)+1) } else { - m.Put(h, 1) + tm.Put(x, 1) } } - for !m.Empty() { - v, _ := m.Min() - for i := v.(int); i < v.(int)+k; i++ { - if _, ok := m.Get(i); !ok { - return false - } - if v, _ := m.Get(i); v.(int) == 1 { - m.Remove(i) + for !tm.Empty() { + x, _ := tm.Min() + for y := x.(int); y < x.(int)+k; y++ { + if v, ok := tm.Get(y); ok { + if v.(int) == 1 { + tm.Remove(y) + } else { + tm.Put(y, v.(int)-1) + } } else { - m.Put(i, v.(int)-1) + return false } } } return true -} \ No newline at end of file +} diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.java b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.java index a4672ac2f3a03..a8751020e8a17 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.java +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.java @@ -4,17 +4,18 @@ public boolean isPossibleDivide(int[] nums, int k) { return false; } TreeMap tm = new TreeMap<>(); - for (int h : nums) { - tm.merge(h, 1, Integer::sum); + for (int x : nums) { + tm.merge(x, 1, Integer::sum); } while (!tm.isEmpty()) { - int v = tm.firstKey(); - for (int i = v; i < v + k; ++i) { - if (!tm.containsKey(i)) { + int x = tm.firstKey(); + for (int y = x; y < x + k; ++y) { + int t = tm.merge(y, -1, Integer::sum); + if (t < 0) { return false; } - if (tm.merge(i, -1, Integer::sum) == 0) { - tm.remove(i); + if (t == 0) { + tm.remove(y); } } } diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.py b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.py index b0f6493ebbd89..382bdf9689149 100644 --- a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.py +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.py @@ -1,20 +1,16 @@ class Solution: def isPossibleDivide(self, nums: List[int], k: int) -> bool: - if len(nums) % k != 0: + if len(nums) % k: return False - sd = SortedDict() - for h in nums: - if h in sd: - sd[h] += 1 - else: - sd[h] = 1 + cnt = Counter(nums) + sd = SortedDict(cnt) while sd: - v = sd.peekitem(0)[0] - for i in range(v, v + k): - if i not in sd: + x = next(iter(sd)) + for y in range(x, x + k): + if y not in sd: return False - if sd[i] == 1: - sd.pop(i) + if sd[y] == 1: + del sd[y] else: - sd[i] -= 1 + sd[y] -= 1 return True diff --git a/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.ts b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.ts new file mode 100644 index 0000000000000..96908565c26de --- /dev/null +++ b/solution/1200-1299/1296.Divide Array in Sets of K Consecutive Numbers/Solution2.ts @@ -0,0 +1,507 @@ +function isPossibleDivide(nums: number[], k: number): boolean { + if (nums.length % k !== 0) { + return false; + } + const tm = new TreeMap(); + for (const x of nums) { + tm.set(x, (tm.get(x) || 0) + 1); + } + while (tm.size()) { + const x = tm.first()![0]; + for (let y = x; y < x + k; ++y) { + if (!tm.has(y)) { + return false; + } + if (tm.get(y)! === 1) { + tm.delete(y); + } else { + tm.set(y, tm.get(y)! - 1); + } + } + } + return true; +} + +type Compare = (lhs: T, rhs: T) => number; + +class RBTreeNode { + data: T; + count: number; + left: RBTreeNode | null; + right: RBTreeNode | null; + parent: RBTreeNode | null; + color: number; + constructor(data: T) { + this.data = data; + this.left = this.right = this.parent = null; + this.color = 0; + this.count = 1; + } + + sibling(): RBTreeNode | null { + if (!this.parent) return null; // sibling null if no parent + return this.isOnLeft() ? this.parent.right : this.parent.left; + } + + isOnLeft(): boolean { + return this === this.parent!.left; + } + + hasRedChild(): boolean { + return ( + Boolean(this.left && this.left.color === 0) || + Boolean(this.right && this.right.color === 0) + ); + } +} + +class RBTree { + root: RBTreeNode | null; + lt: (l: T, r: T) => boolean; + constructor(compare: Compare = (l: T, r: T) => (l < r ? -1 : l > r ? 1 : 0)) { + this.root = null; + this.lt = (l: T, r: T) => compare(l, r) < 0; + } + + rotateLeft(pt: RBTreeNode): void { + const right = pt.right!; + pt.right = right.left; + + if (pt.right) pt.right.parent = pt; + right.parent = pt.parent; + + if (!pt.parent) this.root = right; + else if (pt === pt.parent.left) pt.parent.left = right; + else pt.parent.right = right; + + right.left = pt; + pt.parent = right; + } + + rotateRight(pt: RBTreeNode): void { + const left = pt.left!; + pt.left = left.right; + + if (pt.left) pt.left.parent = pt; + left.parent = pt.parent; + + if (!pt.parent) this.root = left; + else if (pt === pt.parent.left) pt.parent.left = left; + else pt.parent.right = left; + + left.right = pt; + pt.parent = left; + } + + swapColor(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.color; + p1.color = p2.color; + p2.color = tmp; + } + + swapData(p1: RBTreeNode, p2: RBTreeNode): void { + const tmp = p1.data; + p1.data = p2.data; + p2.data = tmp; + } + + fixAfterInsert(pt: RBTreeNode): void { + let parent = null; + let grandParent = null; + + while (pt !== this.root && pt.color !== 1 && pt.parent?.color === 0) { + parent = pt.parent; + grandParent = pt.parent.parent; + + /* Case : A + Parent of pt is left child of Grand-parent of pt */ + if (parent === grandParent?.left) { + const uncle = grandParent.right; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle && uncle.color === 0) { + grandParent.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent; + } else { + /* Case : 2 + pt is right child of its parent + Left-rotation required */ + if (pt === parent.right) { + this.rotateLeft(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is left child of its parent + Right-rotation required */ + this.rotateRight(grandParent); + this.swapColor(parent!, grandParent); + pt = parent!; + } + } else { + /* Case : B + Parent of pt is right child of Grand-parent of pt */ + const uncle = grandParent!.left; + + /* Case : 1 + The uncle of pt is also red + Only Recoloring required */ + if (uncle != null && uncle.color === 0) { + grandParent!.color = 0; + parent.color = 1; + uncle.color = 1; + pt = grandParent!; + } else { + /* Case : 2 + pt is left child of its parent + Right-rotation required */ + if (pt === parent.left) { + this.rotateRight(parent); + pt = parent; + parent = pt.parent; + } + + /* Case : 3 + pt is right child of its parent + Left-rotation required */ + this.rotateLeft(grandParent!); + this.swapColor(parent!, grandParent!); + pt = parent!; + } + } + } + this.root!.color = 1; + } + + delete(val: T): boolean { + const node = this.find(val); + if (!node) return false; + node.count--; + if (!node.count) this.deleteNode(node); + return true; + } + + deleteAll(val: T): boolean { + const node = this.find(val); + if (!node) return false; + this.deleteNode(node); + return true; + } + + deleteNode(v: RBTreeNode): void { + const u = BSTreplace(v); + + // True when u and v are both black + const uvBlack = (u === null || u.color === 1) && v.color === 1; + const parent = v.parent!; + + if (!u) { + // u is null therefore v is leaf + if (v === this.root) this.root = null; + // v is root, making root null + else { + if (uvBlack) { + // u and v both black + // v is leaf, fix double black at v + this.fixDoubleBlack(v); + } else { + // u or v is red + if (v.sibling()) { + // sibling is not null, make it red" + v.sibling()!.color = 0; + } + } + // delete v from the tree + if (v.isOnLeft()) parent.left = null; + else parent.right = null; + } + return; + } + + if (!v.left || !v.right) { + // v has 1 child + if (v === this.root) { + // v is root, assign the value of u to v, and delete u + v.data = u.data; + v.left = v.right = null; + } else { + // Detach v from tree and move u up + if (v.isOnLeft()) parent.left = u; + else parent.right = u; + u.parent = parent; + if (uvBlack) this.fixDoubleBlack(u); + // u and v both black, fix double black at u + else u.color = 1; // u or v red, color u black + } + return; + } + + // v has 2 children, swap data with successor and recurse + this.swapData(u, v); + this.deleteNode(u); + + // find node that replaces a deleted node in BST + function BSTreplace(x: RBTreeNode): RBTreeNode | null { + // when node have 2 children + if (x.left && x.right) return successor(x.right); + // when leaf + if (!x.left && !x.right) return null; + // when single child + return x.left ?? x.right; + } + // find node that do not have a left child + // in the subtree of the given node + function successor(x: RBTreeNode): RBTreeNode { + let temp = x; + while (temp.left) temp = temp.left; + return temp; + } + } + + fixDoubleBlack(x: RBTreeNode): void { + if (x === this.root) return; // Reached root + + const sibling = x.sibling(); + const parent = x.parent!; + if (!sibling) { + // No sibiling, double black pushed up + this.fixDoubleBlack(parent); + } else { + if (sibling.color === 0) { + // Sibling red + parent.color = 0; + sibling.color = 1; + if (sibling.isOnLeft()) this.rotateRight(parent); + // left case + else this.rotateLeft(parent); // right case + this.fixDoubleBlack(x); + } else { + // Sibling black + if (sibling.hasRedChild()) { + // at least 1 red children + if (sibling.left && sibling.left.color === 0) { + if (sibling.isOnLeft()) { + // left left + sibling.left.color = sibling.color; + sibling.color = parent.color; + this.rotateRight(parent); + } else { + // right left + sibling.left.color = parent.color; + this.rotateRight(sibling); + this.rotateLeft(parent); + } + } else { + if (sibling.isOnLeft()) { + // left right + sibling.right!.color = parent.color; + this.rotateLeft(sibling); + this.rotateRight(parent); + } else { + // right right + sibling.right!.color = sibling.color; + sibling.color = parent.color; + this.rotateLeft(parent); + } + } + parent.color = 1; + } else { + // 2 black children + sibling.color = 0; + if (parent.color === 1) this.fixDoubleBlack(parent); + else parent.color = 1; + } + } + } + } + + insert(data: T): boolean { + // search for a position to insert + let parent = this.root; + while (parent) { + if (this.lt(data, parent.data)) { + if (!parent.left) break; + else parent = parent.left; + } else if (this.lt(parent.data, data)) { + if (!parent.right) break; + else parent = parent.right; + } else break; + } + + // insert node into parent + const node = new RBTreeNode(data); + if (!parent) this.root = node; + else if (this.lt(node.data, parent.data)) parent.left = node; + else if (this.lt(parent.data, node.data)) parent.right = node; + else { + parent.count++; + return false; + } + node.parent = parent; + this.fixAfterInsert(node); + return true; + } + + search(predicate: (val: T) => boolean, direction: 'left' | 'right'): T | undefined { + let p = this.root; + let result = null; + while (p) { + if (predicate(p.data)) { + result = p; + p = p[direction]; + } else { + p = p[direction === 'left' ? 'right' : 'left']; + } + } + return result?.data; + } + + find(data: T): RBTreeNode | null { + let p = this.root; + while (p) { + if (this.lt(data, p.data)) { + p = p.left; + } else if (this.lt(p.data, data)) { + p = p.right; + } else break; + } + return p ?? null; + } + + count(data: T): number { + const node = this.find(data); + return node ? node.count : 0; + } + + *inOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.inOrder(root.left!)) yield v; + yield root.data; + for (const v of this.inOrder(root.right!)) yield v; + } + + *reverseInOrder(root: RBTreeNode = this.root!): Generator { + if (!root) return; + for (const v of this.reverseInOrder(root.right!)) yield v; + yield root.data; + for (const v of this.reverseInOrder(root.left!)) yield v; + } +} + +class TreeMap { + _size: number; + tree: RBTree; + map: Map = new Map(); + compare: Compare; + constructor( + collection: Array<[K, V]> | Compare = [], + compare: Compare = (l: K, r: K) => (l < r ? -1 : l > r ? 1 : 0), + ) { + if (typeof collection === 'function') { + compare = collection; + collection = []; + } + this._size = 0; + this.compare = compare; + this.tree = new RBTree(compare); + for (const [key, val] of collection) this.set(key, val); + } + + size(): number { + return this._size; + } + + has(key: K): boolean { + return !!this.tree.find(key); + } + + get(key: K): V | undefined { + return this.map.get(key); + } + + set(key: K, val: V): boolean { + const successful = this.tree.insert(key); + this._size += successful ? 1 : 0; + this.map.set(key, val); + return successful; + } + + delete(key: K): boolean { + const deleted = this.tree.deleteAll(key); + this._size -= deleted ? 1 : 0; + return deleted; + } + + ceil(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) >= 0, 'left')); + } + + floor(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) <= 0, 'right')); + } + + higher(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) > 0, 'left')); + } + + lower(target: K): [K, V] | undefined { + return this.toKeyValue(this.tree.search(key => this.compare(key, target) < 0, 'right')); + } + + first(): [K, V] | undefined { + return this.toKeyValue(this.tree.inOrder().next().value); + } + + last(): [K, V] | undefined { + return this.toKeyValue(this.tree.reverseInOrder().next().value); + } + + shift(): [K, V] | undefined { + const first = this.first(); + if (first === undefined) return undefined; + this.delete(first[0]); + return first; + } + + pop(): [K, V] | undefined { + const last = this.last(); + if (last === undefined) return undefined; + this.delete(last[0]); + return last; + } + + toKeyValue(key: K): [K, V]; + toKeyValue(key: undefined): undefined; + toKeyValue(key: K | undefined): [K, V] | undefined; + toKeyValue(key: K | undefined): [K, V] | undefined { + return key != null ? [key, this.map.get(key)!] : undefined; + } + + *[Symbol.iterator](): Generator<[K, V], void, void> { + for (const key of this.keys()) yield this.toKeyValue(key); + } + + *keys(): Generator { + for (const key of this.tree.inOrder()) yield key; + } + + *values(): Generator { + for (const key of this.keys()) yield this.map.get(key)!; + return undefined; + } + + *rkeys(): Generator { + for (const key of this.tree.reverseInOrder()) yield key; + return undefined; + } + + *rvalues(): Generator { + for (const key of this.rkeys()) yield this.map.get(key)!; + return undefined; + } +}