#+title: Leetcode doc #+author: Aryadev Chavali #+description: Description #+date: 2024-04-29 * Prelude #+begin_src c++ :session c++ test #+end_src * Arrays ** DONE (#217) Contains duplicate If there are any duplicates, then when the list is sorted they would be consecutive. #+begin_src c++ :tangle 217.cpp :session c++ #include using namespace std; bool containsDuplicate(vector &nums) { std::sort(nums.begin(), nums.end()); const size_t n = nums.size() - 1; for (const size_t i = 0; i < n; ++i) if (nums[i] == nums[i + 1]) return true; return false; } #+end_src ** DONE (#242) Valid anagram If two things are anagrams of each other then they are equivalent disregarding ordering. *** Sorting the strings Sorting the two strings would force the same order for both strings. #+begin_src c++ :tangle 242-sort.cpp #include using namespace std; bool isAnagram(string s, string t) { std::sort(s.begin(), s.end()); std::sort(t.begin(), t.end()); return s == t; } #+end_src *** Simple table #+begin_src c++ :tangle 242-table.cpp #include using namespace std; bool isAnagram(string s, string t) { if (s.size() != t.size()) return false; int table[26] = {0}; for (size_t i = 0; i < s.size(); ++i) { table[s[i] - 'a'] += 1; table[t[i] - 'a'] -= 1; } return std::all_of(table, table + 26, [](int i) { return i == 0; }); } #+end_src ** DONE (#1) Two sum Given a target find two elements which sum to it. #+begin_src c++ :tangle 1.cpp #include using namespace std; vector twoSum(vector &nums, int target) { unordered_map diffs; for (int i = 0; i < nums.size(); ++i) { if (diffs.find(target - nums[i]) != diffs.end()) return {diffs[target-nums[i]], i}; else diffs[nums[i]] = i; } return {}; } #+end_src ** DONE (#49) Group anagrams #+begin_src c++ :tangle 49.cpp #include using namespace std; vector> groupAnagrams(vector &strs) { unordered_map> map; for (auto str : strs) { string s = str; sort(s.begin(), s.end()); map[s].push_back(str); } vector> ret; for (const auto &x : map) ret.push_back(x.second); return ret; } #+end_src ** DONE (#347) Top k frequent elements Get the top k most frequency elements in an array. Frequencies imply hash tables. *** Sorting the map Once you've generated the map, sort it by frequency (then by number) then take the top k off it. #+begin_src c++ :tangle 347-sort.cpp #include using namespace std; vector topKFrequent(vector &nums, int k) { unordered_map map; for (auto num : nums) map[num] += 1; vector> vec(map.begin(), map.end()); sort(vec.begin(), vec.end(), [](pair a, pair b) { if (a.second == b.second) return a.first > b.first; return a.second > b.second; }); vector topk; for (size_t i = 0; i < k; ++i) topk.push_back(vec[i].first); return topk; } #+end_src *** Using a queue Instead of sorting the map, we can use a priority queue. Remember that an item in a priority queue has two members: the priority and the value. The priority sorts the members. Hence, if we generate the map then place them all in a priority queue, we just need to get the k highest priority items to finish the problem. #+begin_src c++ :tangle 347-queue.cpp #include using namespace std; vector topKFrequent(vector &nums, int k) { unordered_map map; for (const auto num : nums) map[num] += 1; priority_queue> queue; for (const auto &pair : map) // priority=frequency, value=value queue.push({pair.second, pair.first}); vector topk; for (size_t i = 0; i < k; ++i) { topk.push_back(pq.top().second); pq.pop(); } return topk; } #+end_src ** DONE (#238) Product of array except self Return an array where at each index i the element is simply the product of all elements except the input@i. You cannot use division and it has to be O(n). Mathematically this is asking for the product [nums[x] | x from 0 to n] / nums[i]. We can split this into [x from 0 to i - 1] and [x from i to n] and consider them. These products work linearly as we iterate i: [x from 0 to i - 1] * i is the product [x from 0 to i]. Same the other way. So we can iterate once to produce the products before i and once for the products after i while only doing a set once! #+begin_src c++ :tangle 238.cpp #include using namespace std; vector productexceptself(vector &nums) { const int n = nums.size(); vector out(n, 0); out[0] = 1; for (int i = 0; i < n - 1; ++i) out[i + 1] = out[i] * nums[i]; int postfix = 1; for (int i = n - 1; i >= 0; --i) out[i] *= postfix; postfix *= nums[i]; return out; } #+end_src ** DONE (#36) Valid sudoku Determine if a 9x9 grid is a valid sudoku. You can use a set of hashsets (where membership testing is O(1)) to just check if you've seen a number before. In this case, since we know the exact input data and the properties we want we can use boolean tables to speed up the process. I also use a little maths technique to produce a correct ordering of the boxes via integer division. #+begin_src c++ :tangle 36.cpp #include using namespace std; constexpr size_t box_count(const size_t row, const size_t col) { return (3 * (row / 3)) + (col / 3); } bool isValidSudoku(vector> &board) { bool row_digits[9][9] = { 0 }; bool col_digits[9][9] = { 0 }; bool box_digits[9][9] = { 0 }; for (size_t i = 0; i < 9; ++i) { for (size_t j = 0; j < 9; ++j) { if (board[i][j] == '.') continue; const auto val = board[i][j] - '0' - 1; if (box_digits[box_count(i, j)][val] || col_digits[j][val] || row_digits[i][val]) return false; box_digits[box_count(i, j)][val] = true; col_digits[j][val] = true; row_digits[i][val] = true; } } return true; } #+end_src ** DONE (#128) Longest consecutive sequence #+begin_src c++ :tangle 128.cpp #include using namespace std; int longestConsecutive(vector &nums) { unordered_set s{nums.begin(), nums.end()}; size_t size = 0; for (auto num : nums) { if (s.find(num - 1) != s.end()) continue; size_t seq = 1; for (size_t x = num; s.find(x + 1) != s.end(); ++x, ++seq) continue; size = size < seq ? seq : size; } return size; } #+end_src * Stack ** DONE (#20) Valid Parentheses #+begin_src c++ :tangle 20.cpp #include using namespace std; #define check_top(COUNTER, WANTED) !(COUNTER.size() != 0 && COUNTER.top() == WANTED) bool isValid(string s) { stack counter; for (const auto x : s) { switch (x) { case '(': case '{': case '[': counter.push(x); break; case ')': if (check_top(counter, '(')) return false; counter.pop(); break; case '}': if (check_top(counter, '{')) return false; counter.pop(); break; case ']': if (check_top(counter, '[')) return false; counter.pop(); break; } } return counter.size() == 0; } #+end_src ** DONE (#155) Min Stack Make a minimal implementation of a stack which does all its main functions in O(1) (what?!). In exchange for O(n) memory we can implement most of them. Unfortunately all the high performance implementations also use stacks (what's the point!). #+begin_src c++ :tangle 155.cpp #include using namespace std; class MinStack { private: stack ordered; size_t ptr; vector data; public: MinStack(): data{}, ptr{0} { } void push(int val) { if (ptr == data.size()) data.push_back(val); else data[ptr] = val; if (ordered.size() == 0 || ordered.top() >= val) ordered.push(val); ++ptr; } void pop() { --ptr; if (data[ptr] == ordered.top()) ordered.pop(); } int top() { return data[ptr - 1]; } int getMin() { return ordered.top(); } }; #+end_src ** DONE (#150) Evaluate RPN You're given an RPN expression in tokens e.g. ["2", "3", "+"], run it. Encountered an issue with C/C++'s semantics when it comes prefix decrement: it doesn't have an effect until after the statement so the other side of the assignment still considers ~ptr~ to be the same value. Bit interesting, good to note. #+begin_src c++ :tangle 150.cpp #include using namespace std; int evalRPN(vector& tokens) { vector stack{}; size_t ptr = 0; for (const auto &token : tokens) { if (token == "+") stack[(--ptr) - 1] += stack[ptr - 1]; else if (token == "-") stack[(--ptr) - 1] -= stack[ptr - 1]; else if (token == "*") stack[(--ptr) - 1] *= stack[ptr - 1]; else if (token == "/") stack[(--ptr) - 1] /= stack[ptr - 1]; else { int n = stoi(token); if (stack.size() <= ptr) stack.push_back(n); else stack[ptr] = n; ++ptr; } } return stack[0]; } #+end_src ** DONE (#22) Generate Parentheses Given n, generate all possible well formed combinations of parentheses. *** Naive solution I generate all 2^n bit strings (where n = 2 * number of pairs) then filer all the invalid bit strings. #+begin_src c++ :tangle 22-naive.cpp #include using namespace std; unordered_map>> memo; bool is_valid(size_t size, bitset<16> n) { stack s; for (size_t i = 0; i < size; ++i) { if (n[i]) s.push(true); else { if (s.size() == 0) return false; s.pop(); } } return s.size() == 0; } string bits_to_string(size_t size, bitset<16> n) { stringstream ss; for (size_t i = 0; i < size; ++i) ss << (n[i] ? "(" : ")"); return ss.str(); } unordered_set> recursive(int n) { if (memo.find(n) != memo.end()) return memo[n]; auto prev = recursive(n - 1); unordered_set> res{}; for (const auto &map : prev) { bitset<16> has_1 = map; has_1[n - 1] = true; res.insert(has_1); res.insert(map); } memo[n] = res; return res; } vector generateParenthesis(int n) { memo[1].insert(0); memo[1].insert(1); unordered_set> sets = recursive(2 * n); vector strings; for (const auto &set : sets) if (is_valid(2 * n, set)) strings.push_back(bits_to_string(2 * n, set)); return strings; } #+end_src *** Backtracking Instead of generating all bit strings and then filtering out, let's just build the strings bit by bit? Here we use a queue (though we could use recursion) to generate bigger and bigger paren strings. We pop off the queue and check three things: if the string is the right size then push it onto the vector. If the string has insufficient number of open braces, add an open brace. If the string has insufficient closed braces in comparison to open braces add a close brace. Notice the heuristic: we generate both combinatorial paths for the string (add an open brace or add a closed brace) but only generate the second one based on a condition on the first. As we do these simultaneously we can generate the two possible next steps given an input string. #+begin_src c++ :tangle 22-backtrack.cpp #include using namespace std; struct Work { int open_braces, close_braces; string str; }; vector generateParenthesis(int n) { vector results; queue queue; queue.push({0, 0, ""}); while (queue.size() != 0) { auto res = queue.front(); queue.pop(); if (res.str.size() == (size_t)2 * n) results.push_back(res.str); if (res.open_braces < n) queue.push({res.open_braces + 1, res.close_braces, res.str + "("}); if (res.close_braces < res.open_braces) queue.push({res.open_braces, res.close_braces + 1, res.str + ")"}); } return results; } #+end_src ** TODO (#739) Daily temperatures Given an array of temperatures t[i], generate an array /a/ where a[i] is the number of days it takes to surpass t[i]. If there is no temperature which surpasses t[i], a[i] = 0. #+begin_src c++ :tangle 739.cpp :comments link #include using namespace std; vector dailyTemperatures(vector &temperatures) { stack stack; vector days{(int)temperatures.size()}; for (size_t i = 0; i < temperatures.size(); ++i) { while (!stack.empty() && temperatures[stack.top()] < temperatures[i]) { int prev = stack.top(); days[prev] = i - prev; stack.pop(); } stack.push(i); } return days; } #+end_src