-1

I am learning ruby and have started practicing problems from leetcode, yesterday I have a problem which I am not able to solve since yesterday.

I tried hard doing that in ruby, but not able to do yet.

I tried this

def give_chair(a)
    u = a.uniq
    d = []
    u.each do |i|
        d << i if a.count(i) == 1
    end
    d
end

def smallest_chair(times, target_friend)
  friend = times[target_friend]
  sorted_arrival_times = times.sort
  leave_time_chair = {}
  chair = 0
  chairs_array = []
  uniq_chars_array = []
  sorted_arrival_times.each do |i|
    if leave_time_chair.keys.select { |k| i[0] > k }.empty?
      leave_time_chair[i[1]] = chair
      chair+=1
    else
      all_keys = leave_time_chair.keys.select { |k|  k <= i[0] }
      chairs_array = leave_time_chair.values
      p chairs_array
      if give_chair(chairs_array).empty?
        leave_time_chair[i[1]] = chairs_array.sort.first
      else
        leave_time_chair[i[1]] = give_chair(chairs_array).sort.first
      end
    end
    if i == friend
      p leave_time_chair
      return leave_time_chair[i[1]]
    end
  end
end

# a = [[33889,98676],[80071,89737],[44118,52565],[52992,84310],[78492,88209],[21695,67063],[84622,95452],[98048,98856],[98411,99433],[55333,56548],[65375,88566],[55011,62821],[48548,48656],[87396,94825],[55273,81868],[75629,91467]]
# b = 6
# p smallest_chair(a, b)

but it is failing for some test cases.

I am not able to create an algorithm for it.

Question = https://leetcode.com/problems/the-number-of-the-smallest-unoccupied-chair

My approach:

  1. First I sort the times array according to arrival times.

  2. Then I iterate over each array element

  3. Now if the arrival time is greater than all the previous leaving time (I am creating key, value pair of leaving time and chair given) then I add a new key=> value pair in leave_time_chair (which is hash) and where key is the leaving time of current array and value is the chair given to it.

  4. Then I increment the chair (chair+=1)

  5. Else I get all those leaving time which are equal or less than the current arrival time (all_keys = leave_time_chair.keys.select { |k| k <= i[0] })

  6. Then I get all the chairs of those times

  7. Now I have all the chairs like this => [0, 0, 1, 2] so I wrote one function [ give_chair(a) ] which gives me those elements which are not repeated. like this => [1, 2] and then I assign the shortest number (chair) to the leaving time of current array. and so on...

  8. Then if my current array is equal to the friend I return the chair of it. by extracting it from a hash (leave_time_chair) return leave_time_chair[i[1]]

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 1
    Could you explain your approach in words? I am not really a Ruby dev, so unable to comprehend the code. I did solve this yesterday. Instead of times.sort initially, you can only collect those times which have shorter arrival time than targetFriend and sort them. This way, sorting cost would be minimal. Also, priority queues(min heap) is useful in deciding smallest room index available. – nice_dev Jul 25 '21 at 08:36
  • @nice_dev my approach 1.) first i sort the times array according to arrival times. 2.) then i iterate on each array. 3.) now if the arrival time is greater than all the previous leaving time (i am creating key, value pair of leaving time and chair given) then i add a new key=> value pair in leave_time_chair (which is hash) and where key is the leaving time of current array and value is the chair given to it. 4.) then i increament the chair (chair+=1) – Prateek Vyas Jul 25 '21 at 08:47
  • @nice_dev 5.) Else i get all those leaving time which are equal or less than the current arrival time (all_keys = leave_time_chair.keys.select { |k| k <= i[0] }) 6.) Then i get all the chairs of those times 7.) Now i have all the chairs like this => [0, 0, 1, 2] so i wrote one function [ give_chair(a) ] which gives me those elements which are not repeated. like this => [1, 2] and then i assign the shortest number (chair) to the leaving time of current array. and so on.... – Prateek Vyas Jul 25 '21 at 08:51
  • 8.) then if my current array is equal to the friend i return the chair of it. by extracting it from a hash (leave_time_chair) return leave_time_chair[i[1]] – Prateek Vyas Jul 25 '21 at 08:51
  • Add all these points in your post itself so that it is visible to all. Comments are not usually read for getting clarity in the question. I am still confused a bit, let me see if I can come up with a case to check for incorrectness. – nice_dev Jul 25 '21 at 09:47
  • 1
    @PrateekVyas Please edit your question and add a clear and specific problem statement and what exactly is unclear to you. Try to reduce your code to a minimal example demonstrating your question. Please see [How to create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) for details on how to ask a good question. – Holger Just Jul 25 '21 at 12:15
  • @HolgerJust I just update my approach on the question. i am unable to reduce my code actuallly – Prateek Vyas Jul 25 '21 at 12:25
  • @nice_dev updated my approach in the questions – Prateek Vyas Jul 25 '21 at 12:25

1 Answers1

1

my naive solution (not optimize yet), basically my idea that i flat-map the input array into an array with each element is a pair [time arrive/leave, friend index], then i will sort that array base on time (don't care arrive or leave), if both pair have same time, then i'll compare the arrive time of fiend index. Finally i loop through the sorted array and evaluate minimum free chair index each step, whenever i meet the targetFriend i return that minimum free chair index.

# @param {Integer[][]} times
# @param {Integer} target_friend
# @return {Integer}
def smallest_chair(times, target_friend)
  # times = [[1,2],[4,7],[2,4]]
  # targetFriend = 1

  sit_times = times.each_with_index.inject([]) { |combi, (time, index)| 
    combi += [[time.first, index], [time.last, index]]
  }
  # [[1, 0], [2, 0], [4, 1], [7, 1], [2, 2], [4, 2]]

  sit_times.sort! {|x, y| 
    c = x[0] <=> y[0]
    # [[1, 0], [2, 0], [2, 2], [4, 1], [4, 2], [7, 1]]
    c = times[x[1]][0] <=> times[y[1]][0] if c == 0
    # [[1, 0], [2, 0], [2, 2], [4, 2], [4, 1], [7, 1]]
    c
  }

  chairs = {} # to mark time of friend
  occupied = Array.new(times.size, 0) # occupied chair: 1, otherwise: 0
  min_free = 0 # current minimum not occupied chair
  sit_times.each do |time, friend_index|
    if target_friend == friend_index # check
      return min_free
    end

    sit = chairs[friend_index]
    if sit # leave
      occupied[sit] = 0
      chairs[friend_index] = nil
      min_free = sit if min_free > sit
    else # arrive
      chairs[friend_index] = min_free
      occupied[min_free] = 1
      min_free += 1 until occupied[min_free] == 0 # re-calculate
    end
  end
end

Note: the code pass test cases on leetcode but the performance is not good.

update here is the better version, using 3 priority queues, one for arrive times, one for leave times and the last for chair.

PriorityQueue class

class PriorityQueue
  attr_reader :length

  def initialize(opts={}, &comparator)
    order_opt = opts.fetch(:order, :asc)
    @order = order_opt == :asc ? -1 : 1
    @comparator = comparator
    @items = [nil]
    @length = 0
  end

  def push(item)
    @items << item
    @length += 1
    swim(@length)
    true
  end

  def pop
    return nil if empty?
    swap(1, @length) if @length > 1
    @length -= 1
    sink(1) if @length > 0
    @items.pop
  end

  def empty?
    @length == 0
  end

  def swap(i, j)
    temp =  @items[i]
    @items[i] = @items[j]
    @items[j] = temp
  end

  def in_order?(i, j)
    x = @items[i]
    y = @items[j]
    order = @comparator.nil? ? (x <=> y) : @comparator.call(x, y)
    order == @order
  end

  def swim(from)
    while (up = from / 2) >= 1
      break if in_order?(up, from)
      swap(up, from)
      from = up
    end
  end

  def sink(from)
    while (down = from * 2) <= @length
      down += 1 if down < @length && in_order?(down + 1, down)
      break if in_order?(from, down)
      swap(down, from)
      from = down
    end
  end
end

smallest_chair with priority queues (note that i found using sort is faster than a queue for arrive times, but basically the idea is same)

def smallest_chair_pq(times, target_friend)
  # a_pq = PriorityQueue.new { |x, y|
  #   x[0] <=> y[0]
  # }
  #
  # times.each do |t|
  #   a_pq.push(t)
  # end

  # sort arrive times is faster than a priority queue
  a_pq = times.sort_by(&:first).reverse

  # leave times queue
  l_pq = PriorityQueue.new { |x, y|
    c = x[0] <=> y[0]
    c = x[1] <=> y[1] if c == 0
    c
  }
  # chair-indexes queue
  # consider case a friend come in at arrive-time at1
  # and there's a range chairs with leave times in range lm <= at1 <= ln
  # that mean that friend could pick one of those chairs
  # and according this problem requirement, should pick the minimun chair index
  c_pq = PriorityQueue.new

  target_time = times[target_friend][0]
  last_chair_index = 0
  until a_pq.empty?
    a_top = a_pq.pop
    arrive_time = a_top.first
    if l_pq.empty?
      return 0 if arrive_time == target_time
      l_pq.push([a_top.last, 0])
    else
      l_top = l_pq.pop
      if l_top.first <= arrive_time
        c_pq.push(l_top.last)
        until (l_ntop = l_pq.pop).nil? || arrive_time < l_ntop.first
          c_pq.push(l_ntop.last)
        end
        l_pq.push(l_ntop) unless l_ntop.nil?

        min_chair_index = c_pq.pop
        return min_chair_index if arrive_time == target_time
        l_pq.push([a_top.last, min_chair_index])
      else
        unless c_pq.empty?
          chair_index = c_pq.pop
          return chair_index if arrive_time == target_time
          l_pq.push([a_top.last, chair_index])
        else
          last_chair_index += 1
          return last_chair_index if arrive_time == target_time
          l_pq.push([a_top.last, last_chair_index])
        end
        l_pq.push(l_top)
      end
    end
  end
end
Lam Phan
  • 3,405
  • 2
  • 9
  • 20