Team Ai
Apppublic

Ravvi89/leetcode-pattern-trainer

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
script.js940 linesDownload Raw Back to root
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})();