Files
algorithms/leetcode/solutions.org

675 lines
16 KiB
Org Mode

#+title: Leetcode doc
#+author: Aryadev Chavali
#+description: Description
#+date: 2024-04-29
* 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++ :comments link
#include <bits/stdc++.h>
using namespace std;
bool containsDuplicate(vector<int> &nums)
{
std::sort(nums.begin(), nums.end());
const size_t n = nums.size() - 1;
for (const size_t i = 0; i < n - 1; ++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 :comments link
#include <bits/stdc++.h>
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 :comments link
#include <bits/stdc++.h>
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 :comments link
#include <bits/stdc++.h>
using namespace std;
vector<int> twoSum(vector<int> &nums, int target)
{
unordered_map<int, int> 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 :comments link
#include <bits/stdc++.h>
using namespace std;
vector<vector<string>> groupAnagrams(vector<string> &strs)
{
unordered_map<string, vector<string>> map;
for (auto str : strs)
{
string s = str;
sort(s.begin(), s.end());
map[s].push_back(str);
}
vector<vector<string>> 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 :comments link
#include <bits/stdc++.h>
using namespace std;
vector<int> topKFrequent(vector<int> &nums, int k)
{
unordered_map<int, int> map;
for (auto num : nums)
map[num] += 1;
vector<pair<int, int>> vec(map.begin(), map.end());
sort(vec.begin(), vec.end(),
[](pair<int, int> a, pair<int, int> b)
{
if (a.second == b.second)
return a.first > b.first;
return a.second > b.second;
});
vector<int> 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 :comments link
#include <bits/stdc++.h>
using namespace std;
vector<int> topKFrequent(vector<int> &nums, int k)
{
unordered_map<int, int> map;
for (const auto num : nums)
map[num] += 1;
priority_queue<pair<int, int>> queue;
for (const auto &pair : map)
// priority=frequency, value=value
queue.push({pair.second, pair.first});
vector<int> 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 :comments link
#include <bits/stdc++.h>
using namespace std;
vector<int> productexceptself(vector<int> &nums)
{
const int n = nums.size();
vector<int> 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 arrays which are smaller and easier for the CPU to work with.
#+begin_src c++ :tangle 36.cpp :comments link
#include <bits/stdc++.h>
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<vector<char>> &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 :comments link
#include <bits/stdc++.h>
using namespace std;
int longestConsecutive(vector<int> &nums)
{
unordered_set<int> 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
** DONE (#53) Maximum Subarray
Find the maximal sum of a subarray in some array. This is actually a
dynamic programming problem.
*** Brute force
If there's only one element then that is obviously the maximal sum.
A brute force approach would compute it every subarray sum and then
return the best one.
#+begin_src c++ :tangle 53-bf.cpp :comments link
#include <bits/stdc++.h>
using namespace std;
int maxSubArray(vector<int> &nums)
{
int max_sum = nums[0];
for (int i = 1; i < nums.size(); ++i)
{
// Is nums[i] alone better than what we've made so far?
max_sum = std::max(nums[i], max_sum);
int cur_sum = 0;
for (int j = 0; j <= i; ++j)
cur_sum += nums[i];
// Is the sum nums[0..i] better than what we've done so far?
max_sum = std::max(std::max(cur_sum, max_sum));
}
return max_sum;
}
#+end_src
O(n^2) but can we do better?
*** Dynamic programming
Look at the tree of cur_sum's we need to
compute:
+ at i = 1, cur_sum is sum[0..1]
+ at i = 2, cur_sum is sum[0..2]
+ at i = k, cur_sum is sum[0..k] = sum[0..k-1] + k
At each iteration we're essentially checking if nums[i] is better or
the sum is better than what we've seen before.
Obviously the sum is stupidly expensive. Furthermore, we may not even
need to do the entire sum; if the best sum starts at k and ends at k'
there's literally no point in summing numbers before k in cur_sum. So
we may as well introduce that optimisation.
#+begin_src c++ :tangle 53-dp.cpp :comments link
#+end_src
*** Kadane's formulation
#+begin_src c++ :tangle 53-kadane.cpp :comments link
#include <bits/stdc++.h>
using namespace std;
int maxSubArray(vector<int> &nums)
{
if (nums.size() == 1)
return nums[0];
int max_sum = nums[0], cur_sum = nums[0];
for (size_t i = 1; i < nums.size(); ++i)
{
cur_sum = std::max(cur_sum + nums[i], nums[i]);
max_sum = std::max(cur_sum, max_sum);
}
return max_sum;
}
#+end_src
* Stack
** DONE (#20) Valid Parentheses
#+begin_src c++ :tangle 20.cpp :comments link
#include <bits/stdc++.h>
using namespace std;
#define check_top(COUNTER, WANTED) !(COUNTER.size() != 0 && COUNTER.top() == WANTED)
bool isValid(string s)
{
stack<char> 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 :comments link
#include <bits/stdc++.h>
using namespace std;
class MinStack
{
private:
stack<int> ordered;
size_t ptr;
vector<int> 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 :comments link
#include <bits/stdc++.h>
using namespace std;
int evalRPN(vector<string>& tokens)
{
vector<int> 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 :comments link
#include <bits/stdc++.h>
using namespace std;
unordered_map<int, unordered_set<bitset<16>>> memo;
bool is_valid(size_t size, bitset<16> n)
{
stack<bool> 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<bitset<16>> recursive(int n)
{
if (memo.find(n) != memo.end())
return memo[n];
auto prev = recursive(n - 1);
unordered_set<bitset<16>> 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<string> generateParenthesis(int n)
{
memo[1].insert(0);
memo[1].insert(1);
unordered_set<bitset<16>> sets = recursive(2 * n);
vector<string> 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 :comments link
#include <bits/stdc++.h>
using namespace std;
struct Work
{
int open_braces, close_braces;
string str;
};
vector<string> generateParenthesis(int n)
{
vector<string> results;
queue<Work> 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 <bits/stdc++.h>
using namespace std;
vector<int> dailyTemperatures(vector<int> &temperatures)
{
stack<int> stack;
vector<int> 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
* Linked Lists
** TODO (#2) Add two numbers
Given two linked lists representing the digits of a number in reverse
order, return their sum.
#+begin_src c++ :tangle 2.cpp :comments link
#include <bits/stdc++.h>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode() : val{0}, next{nullptr}
{
}
ListNode(int val) : val{val}, next{nullptr}
{
}
ListNode(int val, ListNode *next) : val{val}, next{next}
{
}
~ListNode()
{
if (next)
delete next;
}
};
ListNode *addTwoNumbers(ListNode *l1, ListNode *l2)
{
if (!(l1 && l2) ||
(l1->val == l2->val && l1->val == 0 && !(l1->next || l2->next)))
return l1;
int carry = 0;
int sum = l1->val + l2->val;
ListNode *root = new ListNode(sum % 10);
ListNode *end = root;
carry = sum / 10;
l1 = l1->next;
l2 = l2->next;
for (; l1 && l2; l1 = l1->next, l2 = l2->next)
{
sum = l1->val + l2->val + carry;
carry = sum / 10;
sum %= 10;
auto node = new ListNode(sum, nullptr);
end->next = node;
end = node;
}
if (l1 || l2)
{
auto remaining = l1 ? l1 : l2;
for (; remaining; remaining = remaining->next)
{
remaining->val += carry;
carry = remaining->val / 10;
remaining->val %= 10;
end->next = remaining;
end = remaining;
}
}
if (carry != 0)
{
auto node = new ListNode(carry, nullptr);
end->next = node;
}
return root;
}
#+end_src
* Blind 75