algoadvance

Leetcode 638. Shopping Offers

Problem Statement

In the 638. Shopping Offers problem, you are given several items that you want to buy, and you have multiple offers that you can use to purchase these items at a discount. Each offer specifies how many of each type of item you receive and the total price of the offer. You need to determine the minimum cost to buy a specified quantity of each item.

Here is the formal problem statement:

You have n items that you want to buy, and you are given an integer array price where price[i] is the price of the i-th item.

You need to return the minimum cost to satisfy all of the given needs.

Clarifying Questions

  1. Can we assume that the prices, special prices, and needs are non-negative integers?
  2. Is it possible for the needs array to contain zeros indicating we don’t need some items at all?
  3. Can a special offer reduce the amount of an item to zero or negative?
  4. Can an offer be used more than once?
  5. What are the constraints on the size of the input arrays?

Code

#include <vector>
#include <unordered_map>

using namespace std;

class Solution {
public:
    int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
        // Memoization cache
        unordered_map<string, int> memo;
        return dfs(price, special, needs, memo);
    }
    
private:
    int dfs(vector<int>& price, vector<vector<int>>& special, vector<int>& needs, unordered_map<string, int>& memo) {
        string key = getKey(needs);
        if (memo.find(key) != memo.end()) {
            return memo[key];
        }
        
        // Calculate the original price without any offers
        int minCost = 0;
        for (int i = 0; i < needs.size(); ++i) {
            minCost += needs[i] * price[i];
        }
        
        // Try to apply each special offer
        for (auto &offer : special) {
            vector<int> remainingNeeds = needs;
            bool valid = true;
            for (int i = 0; i < needs.size(); ++i) {
                remainingNeeds[i] -= offer[i];
                if (remainingNeeds[i] < 0) {
                    valid = false;
                    break;
                }
            }
            if (valid) {
                minCost = min(minCost, offer.back() + dfs(price, special, remainingNeeds, memo));
            }
        }
        
        memo[key] = minCost;
        return minCost;
    }
    
    string getKey(vector<int>& needs) {
        string key;
        for (int need : needs) {
            key += to_string(need) + ",";
        }
        return key;
    }
};

Strategy

  1. Recursive Depth-First Search (DFS) with Memoization:
    • We use DFS to explore all possible ways to buy the items using the given offers and without offers.
    • For each state represented by needs, we try to apply each offer if valid and then recursively solve the subproblem with the remaining needs.
    • The use of memoization helps to store previously computed results for specific needs states to avoid redundant calculations.
  2. Base Cost Calculation:
    • Start by calculating the cost without any offers for each state.
    • This serves as the upper bound for the minimum cost of that state.
  3. Offer Application:
    • Try to subtract the quantities specified by each offer from the needs.
    • If applying an offer leads to negative quantities, discard that offer for the current recursive branch.
  4. String Key for Memoization:
    • Convert the current state of needs to a string to use it as a key for memoization. This helps in efficiently caching and retrieving the previously calculated costs.

Time Complexity

Cut your prep time in half and DOMINATE your interview with AlgoAdvance AI