lazy bartender problem solution

Daily Coding Problem: Problem #297 [Medium]

This problem was asked by Amazon.

At a popular bar, each customer has a set of favorite drinks, and will happily accept any drink among this set. For example, in the following situation, customer 0 will be satisfied with drinks 013, or 6.

preferences = {
    0: [0, 1, 3, 6],
    1: [1, 4, 7],
    2: [2, 4, 7, 5],
    3: [3, 2, 5],
    4: [5, 8]
}

A lazy bartender working at this bar is trying to reduce his effort by limiting the drink recipes he must memorize. Given a dictionary input such as the one above, return the fewest number of drinks he must learn in order to satisfy all customers.

For the input above, the answer would be 2, as drinks 1 and 5 will satisfy everyone.

Solution 1

Dynamic Programming using Recursion and Memoization

Solution written in c++
  • First i arrange the given input into recipes and the count of customers interested in each recipe using c++ stl map – which i call cnt. Each recipe form the key of the map, while the count of customers interested in each recipe is assigned as the value for each key.
  • Then i call Calc_Min_Recipe function passing two parameters: the map and the size of the given input.
  • Calc_Min_Recipe is the recursive function. Here, what i want to achieve is find all possible combination of recipe set that satisfy all customers – note that each value of my map represent that set. For instance the given input gives me this map:
cnt[0] = 1 //only customer 0 interested in recipe 0
cnt[1] = 2 //only customers 0 and 1 interested in recipe 1
cnt[2] = 2 //only customers 2 and 3 interested in recipe 2
cnt[3] = 2 //only customers 0 and 3 interested in recipe 3
cnt[4] = 2 //only customers 1 and 2 interested in recipe 4
cnt[5] = 3 //only customers 2, 3 and 4 interested in recipe 5
cnt[6] = 1 //only customer 0 interested in recipe 6
cnt[7] = 2 //only customers 1 and 2 interested in recipe 7
cnt[8] = 1 //only customer 4 interested in recipe 4

All set sizes discovered must total to the no. of customers (and set elements must not repeat) – for our given input, all set sizes added together must equal 5. Possible combinations are: cnt[1], cnt[2] and cnt[8] i.e values: 2, 2, 1 totals 5 for the set elements (0, 1), (2, 3) and (4); another combination is: cnt[1] and cnt[5] i.e values: 2, 3 totals 5 for the set elements (0, 1) and (2, 3, 4).

int Calc_Min_Recipe(map<int, int> &m, int sz){
    static map<int, int> memo;//initialize memoization map

    //Our base cases//////
    if(m.empty()) return 1;//no particular reference, you can make 1 recipe for all custormers
    if(memo.count(sz)) return memo[sz];
    if(sz == 0) return 0;
    if(sz < 0) return sz;
    //base cases end here//

    int min_recipe = sz;//initialize to this incase every customers preference is unique

    for(auto x: m){
        int bal = sz - x.second;
        int result = Calc_Min_Recipe(m, bal);
        memo[bal] = result;//store result in memo map (used for memoization)
        int curr_min_recipe = result;
        if(curr_min_recipe >= 0) curr_min_recipe += 1;
        if(curr_min_recipe >= 0 && curr_min_recipe < min_recipe){
            min_recipe = curr_min_recipe;
        }
    }

    return min_recipe;
}

int Lazy_bartender(vector<vector<int>> &pref){
    map<int, int> cnt;
    int n = pref.size();

    /*This block of code arranges preferences into
    count of customers which each recipe satisfies*/
    for(int r=0;r<pref.size();r++){
        for(int c=0;c<pref[r].size();c++){
            cnt[pref[r][c]]++;
        }
    }

    int res = Calc_Min_Recipe(cnt, n);

    return res;
}
int main(){
    vector<vector<int>> preferences = {//returns 2
        {0, 1, 3, 6},
        {1, 4, 7},
        {2, 4, 7, 5},
        {3, 2, 5},
        {5, 8}
    };
    /*vector<vector<int>> preferences = {//returns 1
        {0, 1},
        {1}
    };*/
    /*vector<vector<int>> preferences = {//returns 2
        {5, 3},
        {5, 4},
        {5, 4},
        {5, 4},
        {5, 3},
        {6, 4},
        {7, 3}
    };*/
    /*vector<vector<int>> preferences = {//same preferences - returns 1
        {5, 3, 4, 6, 7},
        {5, 3, 4, 6, 7},
        {5, 3, 4, 6, 7},
        {5, 3, 4, 6, 7},
        {5, 3, 4, 6, 7},
        {5, 3, 4, 6, 7},
        {5, 3, 4, 6, 7}
    };*/
    /*vector<vector<int>> preferences = {//unique preferences - returns 5
        {5},
        {3},
        {4},
        {6},
        {7}
    };*/
    /*vector<vector<int>> preferences = {//no preferences - returns 1
        {},
        {},
        {},
        {},
        {}
    };*/
    int result = Lazy_bartender(preferences);
    cout<<"The minimum recipe required is : "<<result;
    return 0;
}

For our given input, the answer is 2 i.e cnt[1] and cnt[5]; values: 2, 3 with the set elements (0, 1) and (2, 3, 4).

Solution 2

Solution written in c++
  • Start by arranging customer preferences into recipes and customers which each recipe satisfies using a map.
  • Get map key/value pair with the maximum value repeatedly – this is the recipe with the maximum customers preference.
  • In each pass, skip, if a customer from the previous max set belongs to new max set
  • Otherwise, increment count of minimum_recipe required because you just found a recipe satisfying most customers
  • Keep track of what i call final_max in each pass. Once final_max equals the input size, a set of recipe satisfying all customers has been found.
bool does_cust_belong_to_prev_recipe_set(int c, vector<int> &old_set){
    if(old_set.size() == 0) return false;//base case
    for(int x: old_set){//go through old_set and compare with c
        if(c == x){
            return true;
        }
    }
    return false;
}
int Lazy_bartender(vector<vector<int>> &pref){
    map<int, int> recipes;
    map<int, vector<int>> recipe_satisfies;
    map<int, int> cnt;
    vector<int> v;
    vector<int> cust_bal;
    int result = 0;
    priority_queue<int> q;

    /*This block of code arranges preferences into
    recipes and the customers which each recipe satisfies*/
    for(int r=0;r<pref.size();r++){
        for(int c=0;c<pref[r].size();c++){
            if(recipes.count(pref[r][c])){//if recipe exist
                v = recipe_satisfies[pref[r][c]];
                v.push_back(r);
                recipe_satisfies[pref[r][c]] = v;
                //increment cnt of customers to each recipe
                cnt[pref[r][c]]++;
            }else{//recipe does not exist
                recipes[pref[r][c]] = 1;
                v = recipe_satisfies[pref[r][c]];
                v.push_back(r);
                recipe_satisfies[pref[r][c]] = v;
                //increment cnt of customers to each recipe
                cnt[pref[r][c]]++;
            }
        }
    }

    if(cnt.empty()) return 1;//no particular reference, you can make 1 recipe for all custormers

    int max = 0;
    int recipe_max_cust;
    int recipe_max_cust_old = -1;
    bool res = false;
    vector<int> c_old;
    static int final_max;
    map<int, int> cnt_left;
    while(!cnt.empty()){//as far as map is not empty
        for(auto x: cnt){//get recipe set with max customers
            if(x.second > max){
                max = x.second;
                recipe_max_cust = x.first;
            }
        }
        //skip, if a customer from the previous max set belongs to new max set
        for(auto c: recipe_satisfies[recipe_max_cust]){
            if(recipe_satisfies.count(recipe_max_cust_old)){//if key exist
                c_old = recipe_satisfies[recipe_max_cust_old];
            }
            res = does_cust_belong_to_prev_recipe_set(c, c_old);
            if(res == true){
                cnt_left[recipe_max_cust] = cnt[recipe_max_cust];
                break;
            }
        }

        if(res == true){
            cnt.erase(recipe_max_cust);//delete key that has been considered
            max = 0;//reset max
            continue;//don't consider this set
        }
        //otherwise, recipe satisfying most customers has been found, increment count of minimum_recipe required
        if(recipe_max_cust_old == -1){
            recipe_max_cust_old = recipe_max_cust;
        }
        //increment result by 1
        result += 1;
        final_max += max;
        if(final_max == pref.size()){//A set of recipe satisfying all customers has been found
            //reset all variables
            cnt = cnt_left;//reset cnt
            //reset cnt_left (by deleting all items in the map)
            cnt_left.clear();
            final_max = 0;
            c_old = {};
            max = 0;//reset max
            recipe_max_cust_old = -1;//reset
            q.push(-result);//store result for the completed set of minimum recipe that bartender has to learn
            result = 0;
            //recipe mix is completed for 1 set - see if you can get another set
            continue;
        }
        cnt.erase(recipe_max_cust);//delete key that has been considered
        max = 0;//reset max
        //recipe_satisfies(recipe_satisfies);
    }

    result = abs(q.top());//get set count with the minimum - this is the minimum recipe mix that bartender has to learn to satisfy every customer
    return result;
}
Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

You May Also Like