Place closest students as far as possible - algorithm

You are in charge of a classroom which has n seats in a single row, numbered 0 through n-1
During the day students enter and leave the classroom for the exam.
In order to minimize the cheating, your task is to efficiently seat all incoming students.
You're given 2 types of queries: add_student(student_id) -> seat index and remove_student(student_id) -> void
The rules for seating the student is the following:
The seat must be unoccupied
The closest student must be as far away as possible
Ties can be resolved by choosing the lowest-numbered seat.
My Approach is to use Hashtable considering we have to assign seat index to each student which we can do using hash function. Is this approach correct?
If hashtable is the right approach then for - 'the closest student must be as far away as possible', how should I design efficient hash function?
Is there any better way to solve this problem?

This seemed like an interesting problem to me and so here is an algorithm that I have come up with, after giving it some thought.
So as the question says, it's only a single row numbered from 1 to n.
I was thinking of a greedy approach to this problem.
So here how it goes.
Person coming first will be seated at 1.
Person coming second will be seated at n, as this will create the farthest distance between them.
Now we start keeping the subsets, so we have one subset as (1,n).
Now when the third person comes in, he gets seated at mid of first subset i.e (1+n)/2. And after this we will have two subsets - (1,n/2) and (n/2,n).
Now when the fourth person arrives he can either sit in the mid of (1,n/2) and (n/2,n).
And this flow will go on.
Now suppose when a person leaves his seat and goes out of the class, then our subset range will change and then for the next incoming person, we will calculate new set of subsets for him.
Hope this helps. For this approach, I think an array of size n will also work nicely.

Update: This solution does not work because of the order the students can leave and open gaps.
So here's the solution I came up with. I would create an initial position array. So the position of the incoming student is just position[current_number_of_students-1].
Creating the array is the tricky part.
def position(number_of_chairs)
# handle edge cases
return [] if number_of_chairs.nil? || number_of_chairs <= 0
return [0] if number_of_chairs == 1
# initialize with first chair and the last chair
#position = [0, number_of_chairs - 1]
# We want to start adding the middle of row segments
# but in the correct order.
# The first segment is going to be the entire row
#segments = [0, number_of_chairs - 1]
while !#segments.empty?
current_segment = #segments.shift
mid = (current_segment[0] + current_segment[1]) / 2
if (mid > current_segment[0] && mid < current_segment[1])
#position << mid
# add the bottom half to the queue
#segments.push([current_segment[0], mid])
# add the top half to the queue
#segments.push([mid, current_segment[1]])
end
end
#position
end

Related

Understanding the concise DP solution for best time to buy and sell stocks IV

The problem is the famous leetcode problem (or in similar other contexts), best to buy and sell stocks, with at most k transactions.
Here is the screenshot of the problem:
I am trying to make sense of this DP solution. You can ignore the first part of large k. I don't understand the dp part why it works.
class Solution(object):
def maxProfit(self, k, prices):
"""
:type k: int
:type prices: List[int]
:rtype: int
"""
# for large k greedy approach (ignore this part for large k)
if k >= len(prices) / 2:
profit = 0
for i in range(1, len(prices)):
profit += max(0, prices[i] - prices[i-1])
return profit
# Don't understand this part
dp = [[0]*2 for i in range(k+1)]
for i in reversed(range(len(prices))):
for j in range (1, k+1):
dp[j][True] = max(dp[j][True], -prices[i]+dp[j][False])
dp[j][False] = max(dp[j][False], prices[i]+dp[j-1][True])
return dp[k][True]
I was able to drive a similar solution, but that uses two rows (dp and dp2) instead of just one row (dp in the solution above). To me it looks like the solution is overwriting values on itself, which for this solution doesn't look right. However the answer works and passes leetcode.
Lets put words to it:
for i in reversed(range(len(prices))):
For each future price we already know in advance, after considering later prices.
for j in range (1, k+1):
For each possibility of considering this price as one of k two-price transactions.
dp[j][True] = max(dp[j][True], -prices[i]+dp[j][False])
If we consider this might be a purchase -- since we are going backwards in time, a purchase means a completed transaction -- we choose the best of (1) having considered the jth purchase already (dp[j][True]) or (2) subtract this price as a purchase and add the best result we have already that includes the jth sale (-prices[i] + dp[j][False]).
dp[j][False] = max(dp[j][False], prices[i]+dp[j-1][True])
Otherwise, we might consider this as a sale (the first half of a transaction since we're going backwards), so we choose the best of (1) the jth sale already considered (dp[j][False]), or (2) we add this price as a sale and add to that the best result we have so far for the first (j - 1) completed transactions (prices[i] + dp[j-1][True]).
Note that the first dp[j][False] is referring to the jth "half-transaction," the sale if you will, since we are going backwards in time, that would have been set in an earlier iteration on a later price. We then can possibly overwrite it with our consideration of this price as a jth sale.

Coin change recursive algorithm unwinding

I am trying to unwind the recursive function in this algorithm. Coin change problem: Given a target amount n and a list array of distinct coin values, what's the fewest coins needed to make the change amount.
def rec_coin(target,coins):
# Default to target value
min_coins = target
# Check to see if we have a single coin match (BASE CASE)
if target in coins:
return 1
else:
# for every coin value that is <= than target
for i in [c for c in coins if c <= target]:
# Recursive Call (add a count coin and subtract from the target)
num_coins = 1 + rec_coin(target-i,coins)
# Reset Minimum if we have a new minimum
if num_coins < min_coins:
min_coins = num_coins
return min_coins
# rec_coin(63,[1,5,10,25])
# 6
This is what I come up with after breaking it apart
1 + 63-1 coins + 62-1 + 61-1 and so on..
why do we need to add 1? What would be the correct way of unwiding the recursion
The code you present is very inefficient. For finding the solution for an amount of 63, imagine that it will first recurse to the target amount with steps of the smallest coin (i.e. 1). Then after a lot of backtracking and attempts with other coins, it finally backtracks to the outermost level and tries a coin with value 5. Now the recursion kicks in again, just like before, adding coins of value 1. But the problem is that this intermediate value (63-5) was already visited before (5 levels deep after picking coin 1), and it took a lot of function calls to get results for that value 58. And yet, the algorithm will just ignore that and do all of that work again.
A common solution to this is dynamic programming, i.e. memoizing earlier found solutions so they can be reused without extra work.
I will present here a bottom-up method: it first checks all amounts that can be achieved with just one coin. These amounts are put in a queue. If the target is among them then the answer is 1. If not, all amounts in the queue are processed by adding all possible coins to each of them. Sometimes a value will be found that was already visited before, and in that case it is not put in the next queue, but otherwise it is. If now the target value is in that queue, you know that target can be reached with just 2 coins.
This process continues in a loop, which is in fact just a Breadth-first-search in a tree where amounts are nodes, and edges represent that one amount can be reached from another by adding one coin to it. The search starts in the node that represents an amount of 0.
Here is the code for it:
def rec_coin(target, coins):
visited = set() # Amounts that we have already achieved with a minimal number of coins
amounts = [0] # The latest series of amounts all using an equal number of coins
for min_coins in range(1, target+1):
next_amounts = []
for amount in amounts:
for coin in coins:
added = amount + coin
if added == target:
return min_coins
if not added in visited:
visited.add(added)
next_amounts.append(added)
amounts = next_amounts
print (rec_coin(63,[1,5,10,25]))

How to improve this Dynamic programming solution(Optimisation in algorithm)

The Problem statement:
Assurance Company of Moving (ACM) is a company of moving things for people. Recently, some schools want to move their computers to another place. So they ask ACM to help them. One school reserves K trucks for moving, and it has N computers to move. In order not to waste the trucks, the school ask ACM to use all the trucks. That is to say, there must be some computers in each truck, and there are no empty trucks. ACM wants to know how many partition shemes exists with moving N computers by K trucks, the ACM ask you to compute the number of different shemes with given N and K. You needn't care with the order. For example N=7,K=3, the the following 3 partition instances are regarded as the same one and should be counted as one sheme: "1 1 5","1 5 1","5 1 1". Each truck can carry almost unlimited computers!!
Save Time :
You have to count how many sequences a[1..k] exist such that :
1) a[i] + a[2] + .... + a[k] = N such that permutations dont matter
My O(N*K^2) solution (Cannot figure out how to improve on it)
#include<assert.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
int DP[5001][5001];
void ini()
{
int i,j,k;
DP[0][0]=1;
for(k=1;k<=500;k++)
for(j=1;j<=500;j++)
for(i=1;i<=500;i++)
{
DP[i][j]+=j>=k?DP[i-1][j-k]:0;
DP[i][j]%=1988;
}
return ;
}
int main()
{
ini();
int N,K,i,j;
while(1)
{
scanf("%d%d",&N,&K);
if(N==0 && K==0)
return 0;
int i;
if(DP[K][N]==0)
{assert(0);}
printf("%d\n",DP[K][N]);
}
return 0;
}
Explanation of my solution DP[i][j] represents the number of ways I can have total j computers using i Trucks only.
The k represents the number of computers with which I am dealing with that means I am just avoiding permutations!
How can I improve it to O(N*K)?
Problem constraints
N (1<=N<=5000) and K(1<=K<=N)
Problem Link: Problem Spoj
Just say that you have K gift boxes and N chocolates.
I will start with a recursive and real easy to convert it to iterative solution.
The key to avoid repetitions is distributing chocolates in a ascending order (descending also works). So you 7 chocolates and I put 2 chocolate in the first box, I will put at least 2 in the second box. WHY? this helps in avoiding repetitions.
now onwards TCL = totalChocholatesLeft & TBL = totalBinsLeft
So S(TCL,TBL) = S(TCL-TBL,TBL) + S(TCL,TBL-1);
you have to call the above expression starting with S(n-k), k)
Why? because all boxes need at least one item so first put `1` each box.
Now you are left with only `n-k` chocolates.
That's all! that's the DP recursion.
How does it work?
So in order to remove repetitions we are maintaning the ascending order.
What is the easiest way to maintain the ascending order ?
If you put 1 chocolate in the ith box, put 1 in all boxes in front of it i+1, i++2 .....k.
So after keeping chocolate in a gift box, you have two choices :
Either you want to continue with current box :
S(TCL-TBL,TBL) covers this
or to move the next box just never consider this box again
S(TCL,TBL-1) covers this.
Equivalent DP would make have TC : O(NK)
This problem is equivalent to placing n-k identical balls (after already placing one ball in each cell to make sure it's not empty) in k identical cells.
This can be solved using the recurrence formula:
D(n,0) = 0 n > 0
D(n,k) = 0 n < 0
D(n,1) = 1 n >= 0
D(n,k) = D(n,k-1) + D(n-k,k)
Explanation:
Stop clauses:
D(n,0) - no way to put n>0 balls in 0 cells
D(n<0,k) - no way to put negative number of balls in k cells
D(n,1) - one way to put n balls in 1 cell: all in this cell
Recurrence:
We have two choices.
We either have one (or more) empty cell, so we recurse with the same problem, and one less cell: D(n,k-1)
Otherwise, we have no empty cells, so we put one ball in each cell, recurse with the same number of cells and k less balls, D(n-k,k)
The two possibilities are of disjoint sets, so the union of both sets is the summation of the two sizes, thus D(n,k) = D(n,k-1) + D(n-k,k)
The above recursive formula is easy to compute in O(1) (assuming O(1) arithmetics), if the "lower" problems are known, and the DP solution needs to fill a table of size (n+1)*(k+1), so this solution is O(nk)

Generating a unique ID with O(1) space?

We have a group of objects, let's call them Players. We can traverse through this group only with random order, e.g. there is no such thing as Players[0].
Each Player has a unique ID, with ID < len(Players). Player's can be added and removed to the group. When a Player gets removed it will free his ID, and if a Player gets added it will acquire an ID.
If we want to add a new Player to Players we have to generate a new unique ID. What is the fastest way to generate such ID in O(1) space?
O(n log n) is possible with binary search. Start with a = 0 and b = n. The invariant is that there exists a free id in the interval [a, b). Repeat the following until b - a = 1: let m = a + floor((b - a) / 2), count the number of ids in [a, m) and in [m, b). If [a, m) has fewer than m - a ids, then set b = m. Otherwise, set a = m.
I think you can use a Queue to enqueue the IDs that have been free'd up. Dequeue the queue to get free IDs once you have used up the highest possible ID. This will take O(1).
int highestIndex = 0;
Adding Players
if (highestIndex < len(Players)-1){
ID = ++highestIndex();
}
else if (!queue.isEmpty()){
ID = queue.dequeue();
} else{
// max players reached
}
Removing Players
queue.enqueue(ID);
Keep a boolean array. Construct a binary tree over this array, such that the leafs are the initial values in the array, and for items i, i+1 the parent is their logical AND (this means one of them is 0). When you want to insert traverse the tree from the root down to find the first empty slot (keep going left while one child is 0). This gives the first empty slot in O(log(n)). You can get O(log(log(n)) if you take each sqrt(n) group of bits and form an AND parent.
Based on question as first posed with a fixed maximum number of Players:
1) Technically the size of Players is O(1). Build a boolen array of 1000 slots, one per player, with TRUE meaning "ID is assigned". When a player dies, set the ID for his bit to false. When a new player arrives, search the bit array for a "false" bit; assign that ID to the player and set the bit.
Time is O(1), too with a big constant.
Based on question as revised with arbitrary N players:
2) Expanding Holzer's idea: keep a small fixed size array of size k < < N as a cache of free IDs. Use it the way TMJ described. [TMJ deleted his answer: it said in effect, "keep a stack of unused IDs, pop an unused one, push newly dead ones"] If the cache is empty when a new ID is needed, apply Holzer's scheme (one could even refill the small array while executing Holzer's scheme). [Sheesh, Holzer deleted his answer too, it said "try each ID in order and search the set; if nobody has that ID, use it" (O(N^2)] If the number of players arrives at more or less a steady state, this would be pretty fast because statistically there would always be some values in the fixed size array.
You can combine TMJ's idea with Per's idea, but you can't refill the array during Per's scan, only with dead player IDs.
You could put the players in a (cyclic) linked list. Deleting a player would involve cutting it out of the chain, and inserting it into another list (the "free" list). Allocating a player would cut (a random) one out of the "free" list and insert it into the "active" list.
UPDATE:
Since the array is fixed, you can use a watermark separating the allocated from the free players:
Initially {watermark = 0}
Free: {swap [this] <--> [watermark -1] ; decrement watermark; }
Allocate: {increment watermark; yield warermark-1; }
Voila!
Your question is ill-formed. The immediate answer is:
ID(newPlayer) = 1000
(You stated no requirement that the new player ID have to be less than 1000.)
More seriously, since O(1000) == O(1), you can create an array of id_seen[1000], mark all IDs you've seen so far in it, than select one you have not seen.
To make your question interesting, you have to formulate it carefully, e.g. "there are N players with IDs < K. You can only traverse the collection in unknown order. Add a new player with ID < K, using O(1) space."
One (inefficient) answer: select random number X < K. Traverse the collection. If you see a player with ID == X, restart. If you don't, use it as the new ID.
Evaluating efficiency of this algorithm for a given N and K is left as an exercise to the reader ;-)

Algorithm to establish ordering amongst a set of items

I have a set of students (referred to as items in the title for generality). Amongst these students, some have a reputation for being rambunctious. We are told about a set of hate relationships of the form 'i hates j'. 'i hates j' does not imply 'j hates i'. We are supposed to arrange the students in rows (front most row numbered 1) in a way such that if 'i hates j' then i should be put in a row that is strictly lesser numbered than that of j (in other words: in some row that is in front of j's row) so that i doesn't throw anything at j (Turning back is not allowed). What would be an efficient algorithm to find the minimum number of rows needed (each row need not have the same number of students)?
We will make the following assumptions:
1) If we model this as a directed graph, there are no cycles in the graph. The most basic cycle would be: if 'i hates j' is true, 'j hates i' is false. Because otherwise, I think the ordering would become impossible.
2) Every student in the group is at least hated by one other student OR at least hates one other student. Of course, there would be students who are both hated by some and who in turn hate other students. This means that there are no stray students who don't form part of the graph.
Update: I have already thought of constructing a directed graph with i --> j if 'i hates j and doing topological sorting. However, since the general topological sort would suit better if I had to line all the students in a single line. Since there is a variation of the rows here, I am trying to figure out how to factor in the change into topological sort so it gives me what I want.
When you answer, please state the complexity of your solution. If anybody is giving code and you don't mind the language, then I'd prefer Java but of course any other language is just as fine.
JFYI This is not for any kind of homework (I am not a student btw :)).
It sounds to me that you need to investigate topological sorting.
This problem is basically another way to put the longest path in a directed graph problem. The number of rows is actually number of nodes in path (number of edges + 1).
Assuming the graph is acyclic, the solution is topological sort.
Acyclic is a bit stronger the your assumption 1. Not only A -> B and B -> A is invalid. Also A -> B, B -> C, C -> A and any cycle of any length.
HINT: the question is how many rows are needed, not which student in which row. The answer to the question is the length of the longest path.
It's from a project management theory (or scheduling theory, I don't know the exact term). There the task is about sorting jobs (vertex is a job, arc is a job order relationship).
Obviously we have some connected oriented graph without loops. There is an arc from vertex a to vertex b if and only if a hates b. Let's assume there is a source (without incoming arcs) and destination (without outgoing arcs) vertex. If that is not the case, just add imaginary ones. Now we want to find length of a longest path from source to destination (it will be number of rows - 1, but mind the imaginary verteces).
We will define vertex rank (r[v]) as number of arcs in a longest path between source and this vertex v. Obviously we want to know r[destination]. Algorithm for finding rank:
0) r_0[v] := 0 for all verteces v
repeat
t) r_t[end(j)] := max( r_{t-1}[end(j)], r_{t-1}[start(j)] + 1 ) for all arcs j
until for all arcs j r_{t+1}[end(j)] = r_t[end(j)] // i.e. no changes on this iteration
On each step at least one vertex increases its rank. Therefore in this form complexity is O(n^3).
By the way, this algorithm also gives you student distribution among rows. Just group students by their respective ranks.
Edit: Another code with the same idea. Possibly it is better understandable.
# Python
# V is a list of vertex indices, let it be something like V = range(N)
# source has index 0, destination has index N-1
# E is a list of edges, i.e. tuples of the form (start vertex, end vertex)
R = [0] * len(V)
do:
changes = False
for e in E:
if R[e[1]] < R[e[0]] + 1:
changes = True
R[e[1]] = R[e[0]] + 1
while changes
# The answer is derived from value of R[N-1]
Of course this is the simplest implementation. It can be optimized, and time estimate can be better.
Edit2: obvious optimization - update only verteces adjacent to those that were updated on the previous step. I.e. introduce a queue with verteces whose rank was updated. Also for edge storing one should use adjacency lists. With such optimization complexity would be O(N^2). Indeed, each vertex may appear in the queue at most rank times. But vertex rank never exceeds N - number of verteces. Therefore total number of algorithm steps will not exceed O(N^2).
Essentailly the important thing in assumption #1 is that there must not be any cycles in this graph. If there are any cycles you can't solve this problem.
I would start by seating all of the students that do not hate any other students in the back row. Then you can seat the students who hate these students in the next row and etc.
The number of rows is the length of the longest path in the directed graph, plus one. As a limit case, if there is no hate relationship everyone can fit on the same row.
To allocate the rows, put everyone who is not hated by anyone else on the row one. These are the "roots" of your graph. Everyone else is put on row N + 1 if N is the length of the longest path from any of the roots to that person (this path is of length one at least).
A simple O(N^3) algorithm is the following:
S = set of students
for s in S: s.row = -1 # initialize row field
rownum = 0 # start from first row below
flag = true # when to finish
while (flag):
rownum = rownum + 1 # proceed to next row
flag = false
for s in S:
if (s.row != -1) continue # already allocated
ok = true
foreach q in S:
# Check if there is student q who will sit
# on this or later row who hates s
if ((q.row == -1 or q.row = rownum)
and s hated by q) ok = false; break
if (ok): # can put s here
s.row = rownum
flag = true
Simple answer = 1 row.
Put all students in the same row.
Actually that might not solve the question as stated - lesser row, rather than equal row...
Put all students in row 1
For each hate relation, put the not-hating student in a row behind the hating student
Iterate till you have no activity, or iterate Num(relation) times.
But I'm sure there are better algorithms - look at acyclic graphs.
Construct a relationship graph where i hates j will have a directed edge from i to j. So end result is a directed graph. It should be a DAG otherwise no solutions as it's not possible to resolve circular hate relations ship.
Now simply do a DFS search and during the post node callbacks, means the once the DFS of all the children are done and before returning from the DFS call to this node, simply check the row number of all the children and assign the row number of this node as row max row of the child + 1. Incase if there is some one who doesn't hate anyone basically node with no adjacency list simply assign him row 0.
Once all the nodes are processed reverse the row numbers. This should be easy as this is just about finding the max and assigning the row numbers as max-already assigned row numbers.
Here is the sample code.
postNodeCb( graph g, int node )
{
if ( /* No adj list */ )
row[ node ] = 0;
else
row[ node ] = max( row number of all children ) + 1;
}
main()
{
.
.
for ( int i = 0; i < NUM_VER; i++ )
if ( !visited[ i ] )
graphTraverseDfs( g, i );`enter code here`
.
.
}

Resources