|
| 1 | +/** |
| 2 | + * Title: Sequentially Ordinal Rank Tracker |
| 3 | + * Description: A scenic location is represented by its name and attractiveness score, where name is a unique string among all locations and score is an integer. Locations can be ranked from the best to the worst. The higher the score, the better the location. If the scores of two locations are equal, then the location with the lexicographically smaller name is better. |
| 4 | + * Author: Hasibul Islam |
| 5 | + * Date: 28/04/2023 |
| 6 | + */ |
| 7 | + |
| 8 | +class AVLNode { |
| 9 | + constructor(val) { |
| 10 | + this.val = val; |
| 11 | + this.left = null; |
| 12 | + this.right = null; |
| 13 | + this.height = 1; |
| 14 | + this.cnt = 1; |
| 15 | + this.SubTreeNodes = 1; |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +const lexical_smallest_comp = (x, y) => (x < y ? -1 : x > y ? 1 : 0); |
| 20 | + |
| 21 | +class AVLTree { |
| 22 | + constructor() { |
| 23 | + this.root = null; |
| 24 | + this.nodeCount = 0; |
| 25 | + this.tot = 0; |
| 26 | + } |
| 27 | + cmp(x, y) { |
| 28 | + // compare nodes: x is inserted item |
| 29 | + if (x == null || y == null) return 0; |
| 30 | + if (Array.isArray(x) || Number.isInteger(x)) x = new AVLNode(x); |
| 31 | + if (Array.isArray(y) || Number.isInteger(y)) y = new AVLNode(y); |
| 32 | + if (Array.isArray(x.val) || Array.isArray(y.val)) { |
| 33 | + // array compare (can change like PQ) |
| 34 | + // this problem comparator |
| 35 | + if (Array.isArray(x.val) && Array.isArray(y.val)) { |
| 36 | + if (x.val[1] != y.val[1]) return y.val[1] - x.val[1]; // first priority: larger score comes first |
| 37 | + return lexical_smallest_comp(x.val[0], y.val[0]); // second priority: lexical smaller comes first |
| 38 | + } else { |
| 39 | + return 0; |
| 40 | + } |
| 41 | + } else if (Number.isInteger(x.val) || Number.isInteger(y.val)) { |
| 42 | + // number compare |
| 43 | + if (Number.isInteger(x.val) && Number.isInteger(y.val)) { |
| 44 | + return x.val - y.val; |
| 45 | + } else { |
| 46 | + return 0; |
| 47 | + } |
| 48 | + } |
| 49 | + return 0; |
| 50 | + } |
| 51 | + getHeight(node) { |
| 52 | + return node != null ? node.height : 0; |
| 53 | + } |
| 54 | + getBalance(node) { |
| 55 | + return node != null |
| 56 | + ? this.getHeight(node.left) - this.getHeight(node.right) |
| 57 | + : 0; |
| 58 | + } |
| 59 | + update(node) { |
| 60 | + let leftHeight = this.getHeight(node.left), |
| 61 | + rightHeight = this.getHeight(node.right); |
| 62 | + node.height = 1 + Math.max(leftHeight, rightHeight); |
| 63 | + node.SubTreeNodes = |
| 64 | + 1 + |
| 65 | + (node.left != null ? node.left.SubTreeNodes : 0) + |
| 66 | + (node.right != null ? node.right.SubTreeNodes : 0); |
| 67 | + } |
| 68 | + LR(z) { |
| 69 | + let y = z.right; |
| 70 | + let T2 = y.left; |
| 71 | + y.left = z; |
| 72 | + z.right = T2; |
| 73 | + this.update(z); |
| 74 | + this.update(y); |
| 75 | + return y; |
| 76 | + } |
| 77 | + RR(z) { |
| 78 | + let y = z.left; |
| 79 | + let T3 = y.right; |
| 80 | + y.right = z; |
| 81 | + z.left = T3; |
| 82 | + this.update(z); |
| 83 | + this.update(y); |
| 84 | + return y; |
| 85 | + } |
| 86 | + insert(item) { |
| 87 | + this.root = this.insertUtil(this.root, item); |
| 88 | + } |
| 89 | + insertUtil(node, item) { |
| 90 | + if (node == null) { |
| 91 | + // find place to insert |
| 92 | + this.nodeCount++; |
| 93 | + this.tot++; |
| 94 | + return new AVLNode(item); |
| 95 | + } else if (this.cmp(item, node) < 0) { |
| 96 | + node.left = this.insertUtil(node.left, item); |
| 97 | + } else if (this.cmp(item, node) > 0) { |
| 98 | + node.right = this.insertUtil(node.right, item); |
| 99 | + } else { |
| 100 | + node.cnt++; |
| 101 | + this.tot++; |
| 102 | + return node; |
| 103 | + } |
| 104 | + this.update(node); |
| 105 | + return this.rebalanceAfterInsert(node, item); |
| 106 | + } |
| 107 | + remove(v) { |
| 108 | + this.root = this.removeUtil(this.root, v); |
| 109 | + } |
| 110 | + removeUtil(node, item) { |
| 111 | + if (node == null) { |
| 112 | + return node; |
| 113 | + } else if (this.cmp(item, node) < 0) { |
| 114 | + node.left = this.removeUtil(node.left, item); |
| 115 | + } else if (this.cmp(item, node) > 0) { |
| 116 | + node.right = this.removeUtil(node.right, item); |
| 117 | + } else { |
| 118 | + // find node |
| 119 | + if (node.cnt > 1) { |
| 120 | + // current node > 1, remove 1, tree size keep the same |
| 121 | + node.cnt--; |
| 122 | + this.tot--; |
| 123 | + return node; |
| 124 | + } else { |
| 125 | + // current node == 1, delete, tree size-- |
| 126 | + this.nodeCount--; |
| 127 | + this.tot--; |
| 128 | + } |
| 129 | + // delete process |
| 130 | + if (node.left == null) { |
| 131 | + let tmp = node.right; |
| 132 | + node = null; |
| 133 | + return tmp; |
| 134 | + } else if (node.right == null) { |
| 135 | + let tmp = node.left; |
| 136 | + node = null; |
| 137 | + return tmp; |
| 138 | + } |
| 139 | + let tmp = this.findMin(node.right); |
| 140 | + node.val = tmp.val; |
| 141 | + node.right = this.removeUtil(node.right, tmp.val); |
| 142 | + } |
| 143 | + if (node == null) return node; |
| 144 | + this.update(node); |
| 145 | + return this.rebalanceAfterDeletion(node, item); |
| 146 | + } |
| 147 | + rebalanceAfterInsert(node, item) { |
| 148 | + let bal = this.getBalance(node); |
| 149 | + if (bal > 1 && this.cmp(item, node.left) < 0) return this.RR(node); |
| 150 | + if (bal < -1 && this.cmp(item, node.right) > 0) return this.LR(node); |
| 151 | + if (bal > 1 && this.cmp(item, node.left) > 0) { |
| 152 | + node.left = this.LR(node.left); |
| 153 | + return this.RR(node); |
| 154 | + } |
| 155 | + if (bal < -1 && this.cmp(item, node.right) < 0) { |
| 156 | + node.right = this.RR(node.right); |
| 157 | + return this.LR(node); |
| 158 | + } |
| 159 | + return node; |
| 160 | + } |
| 161 | + rebalanceAfterDeletion(node) { |
| 162 | + let bal = this.getBalance(node); |
| 163 | + if (bal > 1 && this.getBalance(node.left) >= 0) return this.RR(node); |
| 164 | + if (bal < -1 && this.getBalance(node.right) <= 0) return this.LR(node); |
| 165 | + if (bal > 1 && this.getBalance(node.left) < 0) { |
| 166 | + node.left = this.LR(node.left); |
| 167 | + return this.RR(node); |
| 168 | + } |
| 169 | + if (bal < -1 && this.getBalance(node.right) > 0) { |
| 170 | + node.right = this.RR(node.right); |
| 171 | + return this.LR(node); |
| 172 | + } |
| 173 | + return node; |
| 174 | + } |
| 175 | + find(item) { |
| 176 | + return this.findFirstOf(item); |
| 177 | + } |
| 178 | + findFirstOf(item) { |
| 179 | + let node = this.root, |
| 180 | + res = null; |
| 181 | + while (node != null) { |
| 182 | + if (this.cmp(item, node) < 0) { |
| 183 | + node = node.left; |
| 184 | + } else if (this.cmp(item, node) > 0) { |
| 185 | + node = node.right; |
| 186 | + } else { |
| 187 | + res = node; |
| 188 | + node = node.left; |
| 189 | + } |
| 190 | + } |
| 191 | + return res; |
| 192 | + } |
| 193 | + higher(item) { |
| 194 | + // > upper_bound |
| 195 | + let node = this.findSuccessorOf(item); |
| 196 | + return node == null ? null : node.val; |
| 197 | + } |
| 198 | + findSuccessorOf(item) { |
| 199 | + let node = this.root, |
| 200 | + res = null; |
| 201 | + while (node != null) { |
| 202 | + if (this.cmp(item, node) < 0) { |
| 203 | + res = node; |
| 204 | + node = node.left; |
| 205 | + } else { |
| 206 | + node = node.right; |
| 207 | + } |
| 208 | + } |
| 209 | + return res; |
| 210 | + } |
| 211 | + lower(item) { |
| 212 | + // < |
| 213 | + let node = this.findPrecursorOf(item); |
| 214 | + return node == null ? null : node.val; |
| 215 | + } |
| 216 | + findPrecursorOf(item) { |
| 217 | + let node = this.root, |
| 218 | + res = null; |
| 219 | + while (node != null) { |
| 220 | + if (this.cmp(item, node) > 0) { |
| 221 | + res = node; |
| 222 | + node = node.right; |
| 223 | + } else { |
| 224 | + node = node.left; |
| 225 | + } |
| 226 | + } |
| 227 | + return res; |
| 228 | + } |
| 229 | + findKth(k) { |
| 230 | + // (1-indexed) unique |
| 231 | + let res = this.findKthNode(k); |
| 232 | + return res == null ? null : res.val; |
| 233 | + } |
| 234 | + findKthNode(k) { |
| 235 | + return this.size() < k ? null : this.KthUtil(this.root, k); |
| 236 | + } |
| 237 | + KthUtil(node, k) { |
| 238 | + let leftCount = node.left ? node.left.SubTreeNodes : 0; |
| 239 | + if (leftCount + 1 === k) return node; |
| 240 | + if (leftCount + 1 < k) return this.KthUtil(node.right, k - leftCount - 1); |
| 241 | + return this.KthUtil(node.left, k); |
| 242 | + } |
| 243 | + rankOf(item) { |
| 244 | + // unique value treeset total elements in tree with val < item |
| 245 | + let x = this.findPrecursorOf(item); |
| 246 | + return x == null ? 0 : this.findRankOf(x, this.root) + 1; |
| 247 | + } |
| 248 | + findRankOf(item, node) { |
| 249 | + let rank = 0; |
| 250 | + while (node != null) { |
| 251 | + let leftSubtreeNodes = node.left != null ? node.left.SubTreeNodes : 0; |
| 252 | + if (this.cmp(item, node) < 0) { |
| 253 | + node = node.left; |
| 254 | + } else if (this.cmp(item, node) > 0) { |
| 255 | + rank += leftSubtreeNodes + 1; |
| 256 | + node = node.right; |
| 257 | + } else { |
| 258 | + return rank + leftSubtreeNodes; |
| 259 | + } |
| 260 | + } |
| 261 | + return 0; |
| 262 | + } |
| 263 | + has(item) { |
| 264 | + return this.count(item) > 0; |
| 265 | + } |
| 266 | + count(item) { |
| 267 | + let node = this.find(item); |
| 268 | + return node == null ? 0 : node.cnt; |
| 269 | + } |
| 270 | + maxx() { |
| 271 | + let node = this.findMax(this.root); |
| 272 | + return node == null ? null : node.val; |
| 273 | + } |
| 274 | + minx() { |
| 275 | + let node = this.findMin(this.root); |
| 276 | + return node == null ? null : node.val; |
| 277 | + } |
| 278 | + findMin(node) { |
| 279 | + return node == null || node.left == null ? node : this.findMin(node.left); |
| 280 | + } |
| 281 | + findMax(node) { |
| 282 | + return node == null || node.right == null ? node : this.findMax(node.right); |
| 283 | + } |
| 284 | + size() { |
| 285 | + return this.nodeCount; |
| 286 | + } |
| 287 | + total() { |
| 288 | + return this.tot; |
| 289 | + } |
| 290 | + isEmpty() { |
| 291 | + return this.root == null; |
| 292 | + } |
| 293 | + show() { |
| 294 | + // inorder |
| 295 | + let res = []; |
| 296 | + const dfs = (x) => { |
| 297 | + if (x == null) return; |
| 298 | + dfs(x.left); |
| 299 | + res.push(x.val); |
| 300 | + dfs(x.right); |
| 301 | + }; |
| 302 | + dfs(this.root); |
| 303 | + return res; |
| 304 | + } |
| 305 | + showAll() { |
| 306 | + let d = this.show(), |
| 307 | + res = []; |
| 308 | + for (const x of d) { |
| 309 | + for (let i = 0; i < this.count(x); i++) res.push(x); |
| 310 | + } |
| 311 | + return res; |
| 312 | + } |
| 313 | +} |
| 314 | + |
| 315 | +function SORTracker() { |
| 316 | + let tree = new AVLTree(), |
| 317 | + i = 1; |
| 318 | + return { add, get }; |
| 319 | + function add(name, score) { |
| 320 | + tree.insert([name, score]); |
| 321 | + } |
| 322 | + function get() { |
| 323 | + let node = tree.findKthNode(i++); |
| 324 | + return node.val[0]; |
| 325 | + } |
| 326 | +} |
| 327 | + |
| 328 | +/** |
| 329 | + * Your SORTracker object will be instantiated and called as such: |
| 330 | + * var obj = new SORTracker() |
| 331 | + * obj.add(name,score) |
| 332 | + * var param_2 = obj.get() |
| 333 | + */ |
0 commit comments