LeetCode

Some solutions coded in Ruby for the Grind 75 list of LeetCode problems grouped by topics.

Array

Two Sum (Easy)

Find two numbers in an array that add up to a specific target and return their indices.

# Time: O(n)
# Space: O(n)
def two_sum(a, t)
  h = {}
  a.each_with_index do |x, i|
    y = t - x
    return [h[y], i] if h[y]
    h[x] = i
  end
end

Best Time to Buy and Sell Stock (Easy)

Find the maximum profit that can be achieved by buying and then selling a stock given its prices over time.

# Time: O(n)
# Space: O(1)
def max_profit(prices)
  max_profit = 0
  min_price = prices[0]
  prices.each do |price|
    if min_price > price
      min_price = price
    else
      cur_profit = price - min_price
      max_profit = [max_profit, cur_profit].max
    end
  end
  max_profit
end

Majority Element (Easy)

Find the element that appears more than half the time in an array.

# Time: O(n)
# Space: O(1)
# Boyer-Moore majority vote algorithm
def majority_element(a)
  m = nil
  c = 0
  a.each do |x|
    if c == 0
      m = x
      c = 1
    elsif m == x
      c += 1
    else
      c -= 1
    end
  end
  m
end

Contains Duplicate (Easy)

Determine if an array contains any duplicates by checking if any value appears at least twice.

# Time: O(n)
# Space: O(n)
def contains_duplicate?(a)
  s = Set[]
  a.any? { |x| s.add?(x).nil? }
end

Stack

Valid Parentheses (Easy)

Determine if a string containing only parentheses is valid by ensuring that every opening bracket has a corresponding closing bracket in the correct order.

# Time: O(n)
# Space: O(n)
def valid_parentheses?(s)
  h = { ")" => "(", "]" => "[", "}" => "{" }
  a = [] # Used as a stack
  s.each_char do |c|
    if h[c]
      return false if a.empty? || a.last != h[c]
      a.pop
    else
      a.push(c)
    end
  end
  a.empty?
end

Implement Queue using Stacks (Easy)

Simulate a queue's behavior using two stacks to manage the order of items.

class MyQueue
  def initialize
    @a = []
    @b = []
  end

  def push(x)
    @a.push(x)
  end

  def pop
    if @b.empty?
      @b.push(@a.pop) until @a.empty?
    end
    @b.pop
  end

  def peek
    if @b.empty?
      @b.push(@a.pop) until @a.empty?
    end
    @b.last
  end

  def empty?
    @a.empty? && @b.empty?
  end
end

Linked List

Merge Two Sorted Lists (Easy)

Merge two sorted linked lists into a single sorted linked list.

# Time: O(n)
# Space: O(n)
# Recursive version
def merge_two_sorted_lists(a, b)
  return a if b.nil?
  return b if a.nil?
  if a.val < b.val
    a.next = merge_two_sorted_lists(a.next, b)
    a
  else
    b.next = merge_two_sorted_lists(b.next, a)
    b
  end
end
# Time: O(n)
# Space: O(1)
# Iterative version
def merge_two_sorted_lists(a, b)
  # TODO
end

Linked List Cycle (Easy)

Detect if a linked list contains a cycle, where a node points back to a previous node.

# Time: O(n)
# Space: O(1)
def has_cycle(head)
  slow = head
  fast = head
  while fast && fast.next
    slow = slow.next
    fast = fast.next.next
    return true if slow == fast
  end
  false
end

Reverse Linked List (Easy)

Reverse a singly linked list so that the nodes are reversed in order.

# Time: O(n)
# Space: O(1)
def reverse_list(head)
  new_head = head
  if head
    if head.next
      new_head = reverse_list(head.next)
      head.next.next = head
    end
    head.next = nil
  end
  new_head
end

Middle of the Linked List (Easy)

Return the middle node of a singly linked list, or the second middle node if the list has an even number of nodes.

# Time: O(n)
# Space: O(1)
def middle_node(head)
  slow = head
  fast = head
  while fast && fast.next
    slow = slow.next
    fast = fast.next.next
  end
  slow
end

String

Valid Palindrome (Easy)

Check if a given string, ignoring spaces and cases, reads the same forward and backward.

def alnum?(c)
  case c
  when "0".."9", "A".."Z", "a".."z"
    true
  else
    false
  end
end

# Time: O(n)
# Space: O(1)
def palindrome?(s)
  l = 0
  r = s.size - 1
  while l < r
    if !alnum? s[l]
      l += 1
    elsif !alnum? s[r]
      r -= 1
    elsif s[l].downcase == s[r].downcase
      l += 1
      r -= 1
    else
      return false
    end
  end
  true
end

Valid Anagram (Easy)

Determine if two strings are anagrams by checking if they can be rearranged to form each other.

# Time: O(n)
# Space: O(n)
def anagram?(s, t)
  return false if s.size != t.size
  h = Hash.new(0)
  s.each_char { |c| h[c] += 1 }
  t.each_char { |c| h[c] -= 1 }
  h.values.all?(&:zero?)
end
# Time: O(n)
# Space: O(n)
def anagram?(s, t)
  s.chars.tally == t.chars.tally
end
# Time: O(n log n)
def anagram?(s, t)
  s.chars.sort == t.chars.sort
end
# Time: O(n log n)
def anagram?(s, t) # Unicode aware
  s.grapheme_clusters.sort == t.grapheme_clusters.sort
end

Longest Palindrome (Easy)

Find the length of the longest palindrome that can be constructed from the characters in a given string.

# Time: O(n)
# Space: O(1)
def longest_palindrome(s)
  n = 0
  h = Set[]
  s.each_char { |c| h.delete?(c) ? n += 2 : h.add(c) }
  h.empty? ? n : n + 1
end

Binary Search

Binary Search (Easy)

Implement binary search to find the position of a target value within a sorted array.

# Time: O(log n)
# Space: O(1)
def binary_search(a, t)
  l = 0
  r = a.size - 1
  while l <= r
    m = (l + r) / 2 # Avoid overflow with `l + (r - l) / 2`
    if a[m] < t
      l = m + 1
    elsif a[m] > t
      r = m - 1
    else
      return m
    end
  end
  -1
end

First Bad Version (Easy)

Find the first bad version in a series of versions (from 1 to n), given an API that returns whether a version is bad.

# Time: O(log n)
# Space: O(1)
def first_bad_version(n)
  l = 1
  r = n
  while l < r
    m = (l + r) / 2
    if is_bad_version(m)
      r = m
    else
      l = m + 1
    end
  end
  l # Stop when `l == r`
end

Binary Tree

Invert Binary Tree (Easy)

Invert a binary tree by swapping the left and right children of all nodes.

# Time: O(n)
# Space: O(n)
def invert_tree(root)
  return if root.nil?
  root.left, root.right = invert_tree(root.right), invert_tree(root.left)
  root
end

Maximum Depth of Binary Tree (Easy)

Determine the maximum depth (or height) of a binary tree, which is the number of nodes along the longest path from the root node down to the farthest leaf node.

# Time: O(n)
# Space: O(n)
# Recursive Depth First Search (DFS)
def max_depth(root)
  root ? [max_depth(root.left), max_depth(root.right)].max + 1 : 0
end
# Time: O(n)
# Space: O(n)
# Iterative Depth First Search (DFS)
def max_depth(root)
  res = 0
  stack = [[root, 1]]
  until stack.empty?
    node, depth = stack.pop
    if node
      res = [res, depth].max
      stack.push([node.left, depth + 1])
      stack.push([node.right, depth + 1])
    end
  end
  res
end
# Time: O(n)
# Space: O(n)
# Iterative Breath First Search (BFS)
def max_depth(root)
  depth = 0
  queue = []
  queue.push(root) if root
  until queue.empty?
    queue.size.times do
      node = queue.shift
      queue.push(node.left) if node.left
      queue.push(node.right) if node.right
    end
    depth += 1
  end
  depth
end

Balanced Binary Tree (Easy)

Check if a binary tree is height-balanced, where the depth of two subtrees of every node never differs by more than one.

# Time: O(n)
# Space: O(n)
def balanced?(root)
  dfs(root)[0]
end

def dfs(root)
  if root.nil?
    [true, 0]
  else
    r1, d1 = dfs(root.left)
    r2, d2 = dfs(root.right)
    [r1 && r2 && ((d1 - d2).abs < 2), [d1, d2].max + 1]
  end
end

Diameter of Binary Tree (Easy)

Find the length of the longest path between any two nodes in a binary tree, which may or may not pass through the root.

# Time: O(n)
# Space: O(n)
def diameter_of_binary_tree(root)
  dfs(root)[0]
end

def dfs(root)
  if root.nil?
    [0, 0] # diameter, depth/height
  else
    d1, h1 = dfs(root.left)
    d2, h2 = dfs(root.right)
    [[d1, d2, h1 + h2].max, [h1, h2].max + 1]
  end
end