Files
algorithms/leetcode/solutions.org

16 KiB

Leetcode doc

Arrays

DONE (#217) Contains duplicate

If there are any duplicates, then when the list is sorted they would be consecutive.

#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;
}

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.

#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;
}

Simple table

#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; });
}

DONE (#1) Two sum

Given a target find two elements which sum to it.

#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 {};
}

DONE (#49) Group anagrams

#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;
}

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.

#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;
}

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.

#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;
}

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!

#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;
}

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.

#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;
}

DONE (#128) Longest consecutive sequence

#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;
}

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.

#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;
}

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.

Kadane's formulation

#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;
}

Stack

DONE (#20) Valid Parentheses

#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;
}

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!).

#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();
  }
};

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.

#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];
}

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.

#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;
}

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.

#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;
}

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.

#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;
}

Linked Lists

TODO (#2) Add two numbers

Given two linked lists representing the digits of a number in reverse order, return their sum.

#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;
}

Blind 75