Ravvi89/leetcode-pattern-trainer
0
1// Data: Patterns and sample questions2const PATTERNS = [3 "Two Pointers",4 "Sliding Window",5 "Hash Map",6 "Prefix Sum",7 "Stack",8 "Queue",9 "Monotonic Stack/Queue",10 "Heap / Priority Queue",11 "Binary Search",12 "Backtracking",13 "Divide & Conquer",14 "Dynamic Programming",15 "Greedy",16 "Graph Traversal (BFS/DFS)",17 "Shortest Path (Dijkstra)",18 "Union-Find",19 "Trie",20 "Bit Manipulation",21 "Math / Number Theory",22 "Segment Tree / BIT",23 "Ordered Set (Map/Set)",24 "Two Heaps"25];26 27const QUESTIONS = [28 {29 id: 1,30 title: "Two Sum",31 difficulty: "easy",32 tags: ["Hash Map", "Two Pointers"],33 description: `Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.34 35You may assume that each input would have exactly one solution, and you may not use the same element twice.`,36 constraints: `2 ≤ nums.length ≤ 10^437-10^9 ≤ nums[i] ≤ 10^938-10^9 ≤ target ≤ 10^939Only one valid answer exists.`,40 approach: `Use a hash map to store (value -> index) while iterating.41For each number x, check if target - x exists in the map.42If not, store the current index and continue.43Time: O(n), Space: O(n).`,44 solution: `function twoSum(nums, target) {45 const seen = new Map(); // value -> index46 for (let i = 0; i < nums.length; i++) {47 const x = nums[i];48 const need = target - x;49 if (seen.has(need)) return [seen.get(need), i];50 seen.set(x, i);51 }52 return [];53}`,54 leetcodeUrl: "https://leetcode.com/problems/two-sum/"55},56 {57 id: 2,58 title: "Valid Parentheses",59 difficulty: "easy",60 tags: ["Stack"],61 description: `Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.62 63An input string is valid if:64Open brackets must be closed by the same type of brackets.65Open brackets must be closed in the correct order.`,66 constraints: `1 ≤ s.length ≤ 10^467s consists of parentheses only.`,68 approach: `Use a stack. Scan the string.69For each opening bracket, push its closing expected bracket.70For each closing bracket, check if stack top matches.71At the end, the stack must be empty.`,72 solution: `function isValid(s) {73 const map = { '(': ')', '{': '}', '[': ']' };74 const st = [];75 for (const ch of s) {76 if (map[ch]) st.push(map[ch]);77 else {78 if (st.length === 0 || st.pop() !== ch) return false;79 }80 }81 return st.length === 0;82}`,83 leetcodeUrl: "https://leetcode.com/problems/valid-parentheses/"84},85 {86 id: 3,87 title: "Merge Two Sorted Lists",88 difficulty: "easy",89 tags: ["Two Pointers"],90 description: `Merge two sorted linked lists and return it as a new sorted list. The new list should be made by splicing together the nodes of the first two lists.`,91 constraints: `The number of nodes in both lists is in the range [0, 50].92-100 ≤ Node.val ≤ 10093Both list1 and list2 are sorted in non-decreasing order.`,94 approach: `Use two pointers to iterate both lists.95Pick the smaller head each time and advance.96Attach remaining nodes when one list is done.`,97 solution: `function mergeTwoLists(l1, l2) {98 const dummy = { next: null };99 let tail = dummy, a = l1, b = l2;100 while (a && b) {101 if (a.val <= b.val) { tail.next = a; a = a.next; }102 else { tail.next = b; b = b.next; }103 tail = tail.next;104 }105 tail.next = a || b;106 return dummy.next;107}`,108 leetcodeUrl: "https://leetcode.com/problems/merge-two-sorted-lists/"109},110 {111 id: 4,112 title: "3Sum",113 difficulty: "medium",114 tags: ["Two Pointers", "Hash Map", "Sorting"],115 description: `Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, j != k, and nums[i] + nums[j] + nums[k] == 0.`,116 constraints: `0 ≤ nums.length ≤ 3000117-10^5 ≤ nums[i] ≤ 10^5`,118 approach: `Sort the array.119Fix one number, then use two pointers for the remaining.120Skip duplicates to avoid repeated triplets.121O(n^2) time after sorting.`,122 solution: `function threeSum(nums) {123 nums.sort((a,b) => a - b);124 const res = [];125 for (let i = 0; i < nums.length - 2; i++) {126 if (i === 0 || nums[i] !== nums[i - 1]) {127 let l = i + 1, r = nums.length - 1;128 while (l < r) {129 const sum = nums[i] + nums[l] + nums[r];130 if (sum === 0) {131 res.push([nums[i], nums[l], nums[r]]);132 while (l < r && nums[l] === nums[l + 1]) l++;133 while (l < r && nums[r] === nums[r - 1]) r--;134 l++; r--;135 } else if (sum < 0) l++;136 else r--;137 }138 }139 }140 return res;141}`,142 leetcodeUrl: "https://leetcode.com/problems/3sum/"143},144 {145 id: 5,146 title: "Longest Substring Without Repeating Characters",147 difficulty: "medium",148 tags: ["Sliding Window", "Hash Map"],149 description: `Given a string s, find the length of the longest substring without repeating characters.`,150 constraints: `0 ≤ s.length ≤ 5 * 10^4`,151 approach: `Sliding window with a hash map storing last seen index.152Expand right pointer; shrink left when a duplicate is found.153Track max length.`,154 solution: `function lengthOfLongestSubstring(s) {155 const last = new Map();156 let best = 0, start = 0;157 for (let i = 0; i < s.length; i++) {158 const ch = s[i];159 if (last.has(ch) && last.get(ch) >= start) {160 start = last.get(ch) + 1;161 }162 last.set(ch, i);163 best = Math.max(best, i - start + 1);164 }165 return best;166}`,167 leetcodeUrl: "https://leetcode.com/problems/longest-substring-without-repeating-characters/"168},169 {170 id: 6,171 title: "Binary Search",172 difficulty: "easy",173 tags: ["Binary Search"],174 description: `Given an array of integers nums which is sorted in ascending order, and an integer target, write a function to search target in nums.175 176If target exists, return its index. Otherwise, return -1.`,177 constraints: `1 ≤ nums.length ≤ 10^4178-10^4 ≤ nums[i], target ≤ 10^4179All numbers in nums are unique.`,180 approach: `Classic binary search. Compare mid to target.181Adjust low/high accordingly.`,182 solution: `function search(nums, target) {183 let lo = 0, hi = nums.length - 1;184 while (lo <= hi) {185 const mid = (lo + hi) >> 1;186 if (nums[mid] === target) return mid;187 if (nums[mid] < target) lo = mid + 1;188 else hi = mid - 1;189 }190 return -1;191}`,192 leetcodeUrl: "https://leetcode.com/problems/binary-search/"193},194 {195 id: 7,196 title: "Course Schedule",197 difficulty: "medium",198 tags: ["Graph Traversal (BFS/DFS)", "Topological Sort"],199 description: `There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1.200You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai.201 202Return true if you can finish all courses, otherwise false.`,203 constraints: `1 ≤ numCourses ≤ 10^52040 ≤ prerequisites.length ≤ 5 * 10^4`,205 approach: `Model as a directed graph.206Detect cycle using DFS or Kahn's algorithm (BFS topological sort).`,207 solution: `function canFinish(numCourses, prerequisites) {208 const adj = Array.from({ length: numCourses }, () => []);209 const indeg = Array(numCourses).fill(0);210 for (const [a, b] of prerequisites) {211 adj[b].push(a);212 indeg[a]++;213 }214 const q = [];215 for (let i = 0; i < numCourses; i++) if (indeg[i] === 0) q.push(i);216 let cnt = 0;217 while (q.length) {218 const v = q.shift();219 cnt++;220 for (const nx of adj[v]) {221 if (--indeg[nx] === 0) q.push(nx);222 }223 }224 return cnt === numCourses;225}`,226 leetcodeUrl: "https://leetcode.com/problems/course-schedule/"227},228 {229 id: 8,230 title: "Coin Change",231 difficulty: "medium",232 tags: ["Dynamic Programming"],233 description: `You are given an integer array coins representing coins of different denominations and an integer amount, representing a total amount of money.234 235Return the fewest number of coins that you need to make up that amount. If that amount cannot be made up by any combination of the coins, return -1.`,236 constraints: `1 ≤ coins.length ≤ 122371 ≤ coins[i] ≤ 2^31 - 12380 ≤ amount ≤ 10^4`,239 approach: `1D DP: dp[x] = min coins to make amount x.240Initialize dp[0] = 0, others = INF.241Transition: for each coin, for x from coin to amount: dp[x] = min(dp[x], dp[x-coin] + 1).242Time: O(n * amount), Space: O(amount).`,243 solution: `function coinChange(coins, amount) {244 const INF = 1e9;245 const dp = Array(amount + 1).fill(INF);246 dp[0] = 0;247 for (const c of coins) {248 for (let x = c; x <= amount; x++) {249 dp[x] = Math.min(dp[x], dp[x - c] + 1);250 }251 }252 return dp[amount] === INF ? -1 : dp[amount];253}`,254 leetcodeUrl: "https://leetcode.com/problems/coin-change/"255},256 {257 id: 9,258 title: "Maximum Subarray",259 difficulty: "medium",260 tags: ["Dynamic Programming", "Greedy"],261 description: `Given an integer array nums, find the contiguous subarray which has the largest sum and return its sum.`,262 constraints: `1 ≤ nums.length ≤ 10^5263-10^4 ≤ nums[i] ≤ 10^4`,264 approach: `Kadane's algorithm:265maxEndingHere = max(0, maxEndingHere + x)266ans = max(ans, maxEndingHere)267Handles all-negative case if we start ans = -Infinity.`,268 solution: `function maxSubArray(nums) {269 let best = nums[0], cur = nums[0];270 for (let i = 1; i < nums.length; i++) {271 cur = Math.max(nums[i], cur + nums[i]);272 best = Math.max(best, cur);273 }274 return best;275}`,276 leetcodeUrl: "https://leetcode.com/problems/maximum-subarray/"277},278 {279 id: 10,280 title: "Rotate Image",281 difficulty: "medium",282 tags: ["Math / Number Theory", "Matrix"],283 description: `You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).`,284 constraints: `1 ≤ n ≤ 20`,285 approach: `Perform on layers: reverse rows, then transpose.286Or for each layer i, perform a 4-way swap (top->right->bottom->left->top).287Time: O(n^2), Space: O(1).`,288 solution: `function rotate(matrix) {289 const n = matrix.length;290 // reverse rows291 for (let i = 0; i < Math.floor(n / 2); i++) {292 [matrix[i], matrix[n - 1 - i]] = [matrix[n - 1 - i], matrix[i]];293 }294 // transpose295 for (let i = 0; i < n; i++) {296 for (let j = i + 1; j < n; j++) {297 [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];298 }299 }300}`,301 leetcodeUrl: "https://leetcode.com/problems/rotate-image/"302},303 {304 id: 11,305 title: "Word Ladder",306 difficulty: "hard",307 tags: ["Graph Traversal (BFS/DFS)", "Hash Map"],308 description: `A transformation sequence from word beginWord to word endWord using a dictionary wordList.309Return the length of the shortest transformation sequence, or 0 if no such sequence exists.`,310 constraints: `1 ≤ beginWord.length ≤ 103111 ≤ wordList.length ≤ 5000`,312 approach: `BFS from beginWord, generating all intermediate states by changing one letter.313Use a set for fast lookup.314Return when we reach endWord.`,315 solution: `function ladderLength(beginWord, endWord, wordList) {316 const dict = new Set(wordList);317 if (!dict.has(endWord)) return 0;318 const q = [[beginWord, 1]];319 const visited = new Set([beginWord]);320 const a = 'a'.charCodeAt(0);321 while (q.length) {322 const [w, d] = q.shift();323 if (w === endWord) return d;324 const chars = w.split('');325 for (let i = 0; i < chars.length; i++) {326 const old = chars[i];327 for (let c = 0; c < 26; c++) {328 chars[i] = String.fromCharCode(a + c);329 const nxt = chars.join('');330 if (dict.has(nxt) && !visited.has(nxt)) {331 visited.add(nxt);332 q.push([nxt, d + 1]);333 }334 }335 chars[i] = old;336 }337 }338 return 0;339}`,340 leetcodeUrl: "https://leetcode.com/problems/word-ladder/"341},342 {343 id: 12,344 title: "K Closest Points to Origin",345 difficulty: "medium",346 tags: ["Heap / Priority Queue"],347 description: `Given an array of points where points[i] = [xi, yi] representing Cartesian coordinates, and an integer k, return the k closest points to the origin (0, 0).`,348 constraints: `1 ≤ points.length ≤ 10^4349-10^4 ≤ xi, yi ≤ 10^4`,350 approach: `Use a max-heap of size k storing distances.351Insert first k points; then compare remaining points with heap top.352Time: O(n log k).`,353 solution: `function kClosest(points, k) {354 const dist = (p) => p[0] * p[0] + p[1] * p[1];355 const maxHeap = [];356 for (const p of points) {357 const d = dist(p);358 if (maxHeap.length < k) {359 maxHeap.push({ d, p });360 if (maxHeap.length === k) maxHeap.sort((a,b) => b.d - a.d);361 } else if (d < maxHeap[0].d) {362 maxHeap[0] = { d, p };363 maxHeap.sort((a,b) => b.d - a.d);364 }365 }366 return maxHeap.map(x => x.p);367}`,368 leetcodeUrl: "https://leetcode.com/problems/k-closest-points-to-origin/"369},370 {371 id: 13,372 title: "LRU Cache",373 difficulty: "medium",374 tags: ["Ordered Set (Map/Set)", "Hash Map"],375 description: `Design a data structure that runs in O(1) average time for get and put operations.376 377Implement the LRUCache class:378- get(key): Return the value of the key if the key exists, otherwise return -1.379- put(key, value): If the key exists, update its value; otherwise add the key-value pair.380When the cache reaches capacity, remove the least recently used item.`,381 constraints: `0 ≤ capacity ≤ 10^43820 ≤ key, value ≤ 10^9383At most 2 * 10^5 calls will be made to get and put.`,384 approach: `Use a hash map for O(1) access to nodes, plus a doubly-linked list for LRU order.385On access/insert, move node to tail (most recent).386On evict, remove head (least recent).`,387 solution: `class LRUCache {388 constructor(capacity) {389 this.cap = capacity;390 this.map = new Map();391 this.head = { prev: null, next: null };392 this.tail = { prev: null, next: null };393 this.head.next = this.tail; this.tail.prev = this.head;394 }395 _remove(node) {396 node.prev.next = node.next;397 node.next.prev = node.prev;398 }399 _insertToTail(node) {400 node.prev = this.tail.prev;401 node.next = this.tail;402 this.tail.prev.next = node;403 this.tail.prev = node;404 }405 get(key) {406 const node = this.map.get(key);407 if (!node) return -1;408 this._remove(node); this._insertToTail(node);409 return node.value;410 }411 put(key, value) {412 if (this.map.has(key)) {413 const node = this.map.get(key);414 node.value = value;415 this._remove(node); this._insertToTail(node);416 } else {417 const node = { key, value, prev: null, next: null };418 this.map.set(key, node);419 this._insertToTail(node);420 if (this.map.size > this.cap) {421 const lru = this.head.next;422 this._remove(lru);423 this.map.delete(lru.key);424 }425 }426 }427}`,428 leetcodeUrl: "https://leetcode.com/problems/lru-cache/"429},430 {431 id: 14,432 title: "Reorganize String",433 difficulty: "medium",434 tags: ["Greedy", "Heap / Priority Queue"],435 description: `Given a string s, rearrange the characters so that no two adjacent characters are the same. Return any possible arrangement, or return "" if impossible.`,436 constraints: `1 ≤ s.length ≤ 1000`,437 approach: `Greedy with a max-heap of counts.438Always pick the most frequent remaining char that differs from the last picked.439If at any step no valid char exists, return "".`,440 solution: `function reorganizeString(s) {441 const cnt = {};442 for (const ch of s) cnt[ch] = (cnt[ch] || 0) + 1;443 const pq = Object.entries(cnt).sort((a,b) => b[1] - a[1]);444 let last = null, res = '';445 while (pq.length) {446 const [ch, c] = pq.shift();447 res += ch;448 if (last && last[1] > 0) pq.push(last);449 last = [ch, c - 1];450 }451 if (res.length !== s.length) return '';452 return res;453}`,454 leetcodeUrl: "https://leetcode.com/problems/reorganize-string/"455},456 {457 id: 15,458 title: "Top K Frequent Elements",459 difficulty: "medium",460 tags: ["Heap / Priority Queue", "Hash Map"],461 description: `Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.`,462 constraints: `1 ≤ nums.length ≤ 10^5463-10^4 ≤ nums[i] ≤ 10^4`,464 approach: `Count frequencies with a hash map.465Use a min-heap of size k to keep the top k frequent elements.466Time: O(n log k).`,467 solution: `function topKFrequent(nums, k) {468 const freq = new Map();469 for (const x of nums) freq.set(x, (freq.get(x) || 0) + 1);470 const heap = []; // [num, count]471 for (const [num, count] of freq) {472 heap.push([num, count]);473 heap.sort((a,b) => a[1] - b[1]);474 if (heap.length > k) heap.shift();475 }476 return heap.map(x => x[0]);477}`,478 leetcodeUrl: "https://leetcode.com/problems/top-k-frequent-elements/"479},480 {481 id: 16,482 title: "Task Scheduler",483 difficulty: "medium",484 tags: ["Greedy", "Heap / Priority Queue"],485 description: `Given a char array tasks and an integer n representing the cooldown period, find the least number of intervals needed to finish all tasks.`,486 constraints: `1 ≤ tasks.length ≤ 10^64870 ≤ n < 100`,488 approach: `Use the most frequent tasks to determine idle slots.489Total time = max(len(tasks), (maxFreq - 1) * (n + 1) + countMaxFreq).490Greedy arrangement leaves minimal idle time.`,491 solution: `function leastInterval(tasks, n) {492 const cnt = new Array(26).fill(0);493 for (const t of tasks) cnt[t.charCodeAt(0) - 65]++;494 const max = Math.max(...cnt);495 let maxCount = 0;496 for (const c of cnt) if (c === max) maxCount++;497 const part = (max - 1) * (n + 1) + maxCount;498 return Math.max(tasks.length, part);499}`,500 leetcodeUrl: "https://leetcode.com/problems/task-scheduler/"501},502 {503 id: 17,504 title: "Median of Two Sorted Arrays",505 difficulty: "hard",506 tags: ["Binary Search"],507 description: `Given two sorted arrays nums1 and nums2 of size m and n, return the median of the two sorted arrays.`,508 constraints: `0 ≤ m, n ≤ 1000509-10^6 ≤ nums1[i], nums2[i] ≤ 10^6`,510 approach: `Binary search partition on the shorter array.511Find left/right partitions such that max(left) ≤ min(right).512Handle odd/even total length accordingly.`,513 solution: `function findMedianSortedArrays(a, b) {514 if (a.length > b.length) [a, b] = [b, a];515 const m = a.length, n = b.length;516 let lo = 0, hi = m;517 while (lo <= hi) {518 const i = (lo + hi) >> 1;519 const j = ((m + n + 1) >> 1) - i;520 const Aleft = i === 0 ? -Infinity : a[i - 1];521 const Aright = i === m ? Infinity : a[i];522 const Bleft = j === 0 ? -Infinity : b[j - 1];523 const Bright = j === n ? Infinity : b[j];524 if (Aleft <= Bright && Bleft <= Aright) {525 const median = (m + n) % 2526 ? Math.max(Aleft, Bleft)527 : (Math.max(Aleft, Bleft) + Math.min(Aright, Bright)) / 2;528 return median;529 } else if (Aleft > Bright) hi = i - 1;530 else lo = i + 1;531 }532 return -1;533}`,534 leetcodeUrl: "https://leetcode.com/problems/median-of-two-sorted-arrays/"535},536 {537 id: 18,538 title: "Evaluate Reverse Polish Notation",539 difficulty: "medium",540 tags: ["Stack"],541 description: `Evaluate the value of an arithmetic expression in Reverse Polish Notation. Valid operators: +, -, *, /. Each operand may be an integer or another expression.`,542 constraints: `1 ≤ tokens.length ≤ 10^4543Tokens are valid operators and integers`,544 approach: `Scan tokens.545If number, push.546If operator, pop two numbers (right then left), apply op, push result.`,547 solution: `function evalRPN(tokens) {548 const st = [];549 const ops = { '+': (a,b)=>a+b, '-': (a,b)=>a-b, '*': (a,b)=>a*b, '/': (a,b)=> (a/b)|0 };550 for (const t of tokens) {551 if (!ops[t]) st.push(Number(t));552 else {553 const b = st.pop(), a = st.pop();554 st.push(ops[t](a, b));555 }556 }557 return st[0];558}`,559 leetcodeUrl: "https://leetcode.com/problems/evaluate-reverse-polish-notation/"560},561 {562 id: 19,563 title: "Daily Temperatures",564 difficulty: "medium",565 tags: ["Monotonic Stack/Queue", "Stack"],566 description: `Given an array of integers temperatures representing the daily temperatures, return an array such that answer[i] is the number of days you have to wait after the ith day to get a warmer temperature.`,567 constraints: `1 ≤ temperatures.length ≤ 10^556830 ≤ temperatures[i] ≤ 100`,569 approach: `Use a monotonic decreasing stack storing indices.570Pop while current temp > temp at stack top.571Assign answer for popped indices.`,572 solution: `function dailyTemperatures(temperatures) {573 const n = temperatures.length;574 const ans = Array(n).fill(0);575 const st = [];576 for (let i = 0; i < n; i++) {577 while (st.length && temperatures[i] > temperatures[st[st.length - 1]]) {578 const d = st.pop();579 ans[d] = i - d;580 }581 st.push(i);582 }583 return ans;584}`,585 leetcodeUrl: "https://leetcode.com/problems/daily-temperatures/"586}587];588 589// State and persistence590const STORAGE_KEYS = {591 solved: "lpt_solved",592 filters: "lpt_filters",593 theme: "lpt_theme"594};595 596const state = {597 search: "",598 difficulty: "all",599 status: "all",600 sort: "default", // default | title | difficulty601 pattern: null,602 solved: new Set(),603 currentQuestionId: null604};605 606// Utilities607const $ = (sel) => document.querySelector(sel);608const $$ = (sel) => Array.from(document.querySelectorAll(sel));609const difficultyClass = (d) => d === "easy" ? "badge-easy" : d === "medium" ? "badge-medium" : "badge-hard";610const formatTitle = (q) => q.title;611 612// Theme613function initTheme() {614 const saved = localStorage.getItem(STORAGE_KEYS.theme) || "system";615 const sysDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;616 const dark = saved === "dark" || (saved === "system" && sysDark);617 document.documentElement.classList.toggle("dark", dark);618 $("#themeToggleKnob").classList.toggle("translate-x-6", dark);619}620function toggleTheme() {621 const isDark = document.documentElement.classList.toggle("dark");622 localStorage.setItem(STORAGE_KEYS.theme, isDark ? "dark" : "light");623 $("#themeToggleKnob").classList.toggle("translate-x-6", isDark);624}625 626// Persistence627function loadSolved() {628 try {629 const raw = localStorage.getItem(STORAGE_KEYS.solved);630 if (raw) state.solved = new Set(JSON.parse(raw));631 } catch {}632}633function saveSolved() {634 localStorage.setItem(STORAGE_KEYS.solved, JSON.stringify(Array.from(state.solved)));635}636function loadFilters() {637 try {638 const raw = localStorage.getItem(STORAGE_KEYS.filters);639 if (raw) {640 const f = JSON.parse(raw);641 state.search = f.search || "";642 state.difficulty = f.difficulty || "all";643 state.status = f.status || "all";644 state.sort = f.sort || "default";645 }646 } catch {}647}648function saveFilters() {649 const { search, difficulty, status, sort } = state;650 localStorage.setItem(STORAGE_KEYS.filters, JSON.stringify({ search, difficulty, status, sort }));651}652 653// Rendering654function renderPatterns() {655 const cont = $("#patternList");656 cont.innerHTML = "";657 const allBtn = createPatternChip("All", null, state.pattern === null);658 cont.appendChild(allBtn);659 660 PATTERNS.forEach(p => {661 const btn = createPatternChip(p, p, state.pattern === p);662 cont.appendChild(btn);663 });664}665function createPatternChip(label, pattern, active=false) {666 const btn = document.createElement("button");667 btn.className = `w-full text-left px-3 py-2 rounded-lg text-sm border transition668 ${active ? 'bg-primary-50 border-primary-200 text-primary-700 dark:bg-primary-900/30 dark:border-primary-900/50 dark:text-primary-300'669 : 'bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/70'}`;670 btn.innerHTML = `<div class="flex items-center justify-between">671 <span>${label}</span>672 <span class="text-xs text-slate-400">${pattern ? countByPattern(pattern) : QUESTIONS.length}</span>673 </div>`;674 btn.onclick = () => {675 state.pattern = pattern;676 $("#clearPatternFilterBtn").disabled = state.pattern === null;677 saveFilters();678 renderPatterns();679 renderQuestions();680 };681 return btn;682}683function countByPattern(pattern) {684 return QUESTIONS.filter(q => q.tags.includes(pattern)).length;685}686function filteredQuestions() {687 let list = [...QUESTIONS];688 if (state.pattern) list = list.filter(q => q.tags.includes(state.pattern));689 if (state.search.trim()) {690 const s = state.search.trim().toLowerCase();691 list = list.filter(q => q.title.toLowerCase().includes(s) || q.tags.some(t => t.toLowerCase().includes(s)));692 }693 if (state.difficulty !== "all") list = list.filter(q => q.difficulty === state.difficulty);694 if (state.status !== "all") {695 const wantSolved = state.status === "solved";696 list = list.filter(q => state.solved.has(q.id) === wantSolved);697 }698 if (state.sort === "title") list.sort((a,b) => a.title.localeCompare(b.title));699 if (state.sort === "difficulty") {700 const rank = { easy: 1, medium: 2, hard: 3 };701 list.sort((a,b) => rank[a.difficulty] - rank[b.difficulty] || a.title.localeCompare(b.title));702 }703 return list;704}705function renderQuestions() {706 const grid = $("#questionsGrid");707 const list = filteredQuestions();708 grid.innerHTML = "";709 710 $("#emptyState").classList.toggle("hidden", list.length > 0);711 712 list.forEach(q => {713 const card = document.createElement("article");714 card.className = "rounded-xl border border-slate-200/70 dark:border-slate-800 bg-white dark:bg-slate-950 shadow-soft overflow-hidden";715 card.innerHTML = `716 <div class="p-4">717 <div class="flex items-start justify-between gap-2">718 <div class="flex items-center gap-2">719 <span class="px-2 py-1 text-xs rounded-full border ${difficultyClass(q.difficulty)}">${q.difficulty}</span>720 <div class="font-semibold text-sm">${formatTitle(q)}</div>721 </div>722 <div class="flex items-center gap-2">723 <button class="open-btn p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg" data-id="${q.id}">724 <i data-feather="external-link" class="h-4 w-4 text-slate-500"></i>725 </button>726 <button class="solve-btn p-2 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 rounded-lg" data-id="${q.id}">727 <i data-feather="${state.solved.has(q.id) ? 'check-circle' : 'circle'}" class="${state.solved.has(q.id) ? 'text-emerald-600' : 'text-slate-500'}"></i>728 </button>729 </div>730 </div>731 <div class="mt-3 flex flex-wrap gap-2">732 ${q.tags.map(t => `<span class="px-2 py-1 text-xs rounded-full border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300">${t}</span>`).join("")}733 </div>734 </div>735 `;736 grid.appendChild(card);737 });738 739 // Bind buttons740 $$(".open-btn").forEach(btn => btn.addEventListener("click", onOpenQuestion));741 $$(".solve-btn").forEach(btn => btn.addEventListener("click", onToggleSolved));742 743 // Re-render icons744 if (window.feather) feather.replace();745 746 updateStats(list);747}748function updateStats(list) {749 const total = list.length;750 const solvedCount = list.filter(q => state.solved.has(q.id)).length;751 $("#statTotal").textContent = total;752 $("#statSolved").textContent = solvedCount;753 $("#statUnsolved").textContent = total - solvedCount;754 $("#statAccuracy").textContent = total === 0 ? "0%" : `${Math.round((solvedCount / total) * 100)}%`;755}756 757// Events758function onSearch(e) {759 state.search = e.target.value;760 saveFilters();761 renderQuestions();762}763function onDifficulty(e) {764 state.difficulty = e.target.value;765 saveFilters();766 renderQuestions();767}768function onStatus(e) {769 state.status = e.target.value;770 saveFilters();771 renderQuestions();772}773function onSort() {774 const order = ["default", "title", "difficulty"];775 const next = order[(order.indexOf(state.sort) + 1) % order.length];776 state.sort = next;777 $("#sortLabel").textContent = `Sort: ${next[0].toUpperCase()}${next.slice(1)}`;778 saveFilters();779 renderQuestions();780}781function onClearPattern() {782 state.pattern = null;783 $("#clearPatternFilterBtn").disabled = true;784 saveFilters();785 renderPatterns();786 renderQuestions();787}788 789function onOpenQuestion(e) {790 const id = Number(e.currentTarget.dataset.id);791 openModal(id);792}793function onToggleSolved(e) {794 const id = Number(e.currentTarget.dataset.id);795 if (state.solved.has(id)) state.solved.delete(id);796 else state.solved.add(id);797 saveSolved();798 renderQuestions();799 showToast(state.solved.has(id) ? "Marked as solved" : "Marked as unsolved");800}801 802function openModal(id) {803 const q = QUESTIONS.find(x => x.id === id);804 if (!q) return;805 state.currentQuestionId = id;806 $("#modalTitle").textContent = q.title;807 const badge = $("#modalDifficultyBadge");808 badge.textContent = q.difficulty;809 badge.className = `px-2 py-1 text-xs rounded-full border ${difficultyClass(q.difficulty)}`;810 $("#modalDescription").textContent = q.description;811 $("#modalConstraints").textContent = q.constraints;812 813 // Add LeetCode link814 const leetcodeBtn = document.createElement("a");815 leetcodeBtn.href = q.leetcodeUrl || `https://leetcode.com/problems/${q.title.toLowerCase().replace(/\s+/g, '-')}/`;816 leetcodeBtn.target = "_blank";817 leetcodeBtn.rel = "noopener noreferrer";818 leetcodeBtn.className = "ml-2 px-2 py-1 text-xs rounded-lg border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800 flex items-center gap-1";819 leetcodeBtn.innerHTML = `<i data-feather="external-link" class="h-3 w-3"></i>LeetCode`;820 821 // Remove existing LeetCode link if any822 const existingLink = document.querySelector("#modal .leetcode-link");823 if (existingLink) existingLink.remove();824 825 leetcodeBtn.classList.add("leetcode-link");826 $("#modalTitle").parentNode.appendChild(leetcodeBtn);827 828 // Highlight code with proper formatting829 const approachCode = $("#approachCode");830 const solutionCode = $("#solutionCode");831 832 approachCode.textContent = q.approach;833 solutionCode.textContent = q.solution;834 835 if (window.hljs) {836 hljs.highlightElement(approachCode);837 hljs.highlightElement(solutionCode);838 }839// Solved button840 const solved = state.solved.has(id);841 const btn = $("#toggleSolvedBtn");842 btn.textContent = solved ? "Solved ✓" : "Mark as Solved";843 btn.className = `px-3 py-1.5 rounded-lg text-sm ${solved ? 'bg-emerald-600 hover:bg-emerald-700 text-white' : 'bg-slate-800 hover:bg-slate-700 text-white'}`;844 845 $("#modal").classList.remove("hidden");846}847function closeModal() {848 $("#modal").classList.add("hidden");849 state.currentQuestionId = null;850}851function onModalSolvedToggle() {852 const id = state.currentQuestionId;853 if (!id) return;854 if (state.solved.has(id)) state.solved.delete(id);855 else state.solved.add(id);856 saveSolved();857 renderQuestions();858 openModal(id); // refresh UI859}860 861function showToast(msg) {862 const t = $("#toast");863 t.textContent = msg;864 t.classList.remove("hidden");865 clearTimeout(showToast._timer);866 showToast._timer = setTimeout(() => t.classList.add("hidden"), 1400);867}868 869function copyToClipboard(text) {870 if (navigator.clipboard && window.isSecureContext) {871 return navigator.clipboard.writeText(text);872 } else {873 const ta = document.createElement("textarea");874 ta.value = text;875 document.body.appendChild(ta);876 ta.select();877 document.execCommand("copy");878 document.body.removeChild(ta);879 return Promise.resolve();880 }881}882 883function initEvents() {884 $("#searchInput").addEventListener("input", onSearch);885 $("#difficultyFilter").addEventListener("change", onDifficulty);886 $("#statusFilter").addEventListener("change", onStatus);887 $("#sortBtn").addEventListener("click", onSort);888 $("#clearPatternFilterBtn").addEventListener("click", onClearPattern);889 $("#themeToggle").addEventListener("click", toggleTheme);890 $("#resetProgress").addEventListener("click", () => {891 if (!confirm("Reset all solved progress?")) return;892 state.solved.clear();893 saveSolved();894 renderQuestions();895 showToast("Progress reset");896 });897 898 $("#modalCloseBtn").addEventListener("click", closeModal);899 $("#modal").addEventListener("click", (e) => {900 if (e.target.id === "modal") closeModal();901 });902 $("#toggleSolvedBtn").addEventListener("click", onModalSolvedToggle);903 904 $("#copyApproachBtn").addEventListener("click", () => {905 copyToClipboard($("#approachCode").textContent).then(() => showToast("Approach copied"));906 });907 $("#copySolutionBtn").addEventListener("click", () => {908 copyToClipboard($("#solutionCode").textContent).then(() => showToast("Solution copied"));909 });910 911 // Keyboard shortcuts912 window.addEventListener("keydown", (e) => {913 if (e.key === "Escape") closeModal();914 if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {915 e.preventDefault();916 $("#searchInput").focus();917 }918 });919}920 921function initUIFromFilters() {922 $("#searchInput").value = state.search;923 $("#difficultyFilter").value = state.difficulty;924 $("#statusFilter").value = state.status;925 $("#sortLabel").textContent = `Sort: ${state.sort[0].toUpperCase()}${state.sort.slice(1)}`;926 $("#clearPatternFilterBtn").disabled = state.pattern === null;927}928 929// Boot930(function main() {931 loadFilters();932 loadSolved();933 initTheme();934 initEvents();935 initUIFromFilters();936 937 // Initial render938 renderPatterns();939 renderQuestions();940})();