1912. Design Movie Rental System

Leetcode link

题目简介

这是一道困难题,而且是比较有意思的系统设计题

题目要求我们设计一个电影租赁系统,题目要求我们实现以下方法:

  • MovieRentingSystem(int n, int[][] entries):构造函数,参数 n 代表我们有 n 家店、参数 entries 代表 [shop, movie, price] 三元组,表示在 shop 中有 movie 可以租借,价格为 money
  • List<Integer> search(int movie):搜索所有有 movie 且还未出租的店家,找到 price 最低的前 5 家店返回,如果 price 一样,则 shop 低的优先返回
  • void rent(int shop, int movie):将 movie 从 shop 中租出去
  • void drop(int shop, int movie):把 movie 还到 shop(只能还回原 shop)
  • List<List<Integer>> report():从所有租出去的电影中,返回最便宜的前五个电影的 [shop, movie] 二元组,如果 price 一致,返回 shop 低的,如果 shop 也一致,返回 movie 低的

解题思路

这题的思路有两个:

  1. 排序数组
  2. 小顶堆

前者在数据量大的情况下性能表现不如后者,但是在 leetcode 的测试 case 中性能会比后者好一些,我直接放代码,不赘述,后面主要讲解小顶堆的思路

这道题的关键在于 search 与 report 方法,要想办法减少这两个方法的复杂度

search 方法我们可以用一个 movie => heapmovieHeap 来保存从 map 到小顶堆的映射,此处的 heap 只需要保存 [shop, price] 就好;除了 map 之外,我们还需要一个 Set unRentedMovie 来保存还未出租的 movie,用来筛选 map 返回

report 方法我们可以直接维护一个小顶堆 rentedHeap 来保存所有已经出租出去的电影,这个 heap 我们保存 [shop, movie, price]

最后在 rent 的时候,题目给的参数没有给 price,所以我们还需要一个 map priceMap 来保存从 [shop, movie] 到 price 的映射

Javascript(小顶堆)

/**
 * @param {number} n
 * @param {number[][]} entries
 */
var MovieRentingSystem = function (n, entries) {
    this.resultLimit = 5
    // movie => Heap([shop, price]) for search
    this.movieHeap = new Map()
    // shop#movie
    this.unRentedMovie = new Set()
    // Heap([shop, movie, price]) for report
    this.rentedHeap = new MyHeap(([sA, mA, pA], [sB, mB, pB]) => {
        return pA - pB || sA - sB || mA - mB
    })
    // shop#movie => price
    this.priceMap = new Map()

    for (const [shop, movie, price] of entries) {
        const key = getKey(shop, movie)
        this.unRentedMovie.add(key)
        this.priceMap.set(key, price)

        if (!this.movieHeap.has(movie)) {
            this.movieHeap.set(movie, new MyHeap(([sA, pA], [sB, pB]) => {
                return pA - pB || sA - sB
            }))
        }
        this.movieHeap.get(movie).push([shop, price])
    }
};

/** 
 * @param {number} movie
 * @return {number[]}
 */
MovieRentingSystem.prototype.search = function (movie) {
    if (!this.movieHeap.has(movie)) {
        return []
    }

    const res = []
    const retain = []
    const heap = this.movieHeap.get(movie)
    const seen = new Set()

    while (!heap.isEmpty() && res.length < this.resultLimit) {
        const [shop, price] = heap.pop()
        const key = getKey(shop, movie)
        // only pick unrented movie
        if (this.unRentedMovie.has(key) && !seen.has(key)) {
            res.push(shop)
            // only keep unrented movie to prevent worse case(keep searching rented movie)
            retain.push([shop, price])
            seen.add(key)
        }
    }

    // push all back to the heap
    retain.forEach(item => heap.push(item))

    return res
};

/** 
 * @param {number} shop 
 * @param {number} movie
 * @return {void}
 */
MovieRentingSystem.prototype.rent = function (shop, movie) {
    const key = getKey(shop, movie)
    if (this.unRentedMovie.has(key)) {
        this.unRentedMovie.delete(key)
    }
    // test case ensure to have shop and movie in shop's unrent list
    const price = this.priceMap.get(key)
    this.rentedHeap.push([shop, movie, price])
};

/** 
 * @param {number} shop 
 * @param {number} movie
 * @return {void}
 */
MovieRentingSystem.prototype.drop = function (shop, movie) {
    const key = getKey(shop, movie)
    this.unRentedMovie.add(key)
    if (!this.movieHeap.has(movie)) {
        this.movieHeap.set(movie, new MyHeap(([sA, pA], [sB, pB]) => {
            return pA - pB || sA - sB
        }))
    }
    const price = this.priceMap.get(key)
    this.movieHeap.get(movie).push([shop, price])
    // we will adjust the rentedHeap later
};

/**
 * @return {number[][]}
 */
MovieRentingSystem.prototype.report = function () {
    const res = []
    const retain = []
    // prevent duplicate data
    const seen = new Set()
    while (!this.rentedHeap.isEmpty() && res.length < this.resultLimit) {
        const [shop, movie, price] = this.rentedHeap.pop()
        const key = getKey(shop, movie)
        // only keep rented movie
        if (!this.unRentedMovie.has(key) && !seen.has(key)) {
            res.push([shop, movie])
            retain.push([shop, movie, price])
            seen.add(key)
        }
    }

    retain.forEach(item => this.rentedHeap.push(item))

    return res
};

const getKey = (shop, movie) => {
    return `${shop}#${movie}`
}

class MyHeap {
    constructor(compare) {
        this.heap = []
        this.compare = compare
    }

    peek() {
        return this.heap[0]
    }

    isEmpty() {
        return this.heap.length === 0
    }

    push(item) {
        this.heap.push(item)
        this.heapifyUp(this.heap.length - 1)
    }

    pop() {
        if (this.heap.length <= 1) {
            return this.heap.pop()
        }

        const res = this.heap[0]
        this.heap[0] = this.heap.pop()
        this.heapifyDown(0)
        return res
    }

    heapifyUp(index) {
        let parent = (index - 1) >> 1

        if (parent >= 0 && this.compare(this.heap[index], this.heap[parent]) < 0) {
            // let temp = this.heap[index]
            // this.heap[index] = this.heap[parent]
            // this.heap[parent] = temp
            [this.heap[index], this.heap[parent]] = [this.heap[parent], this.heap[index]]
            this.heapifyUp(parent)
        }
    }

    heapifyDown(index) {
        const left = index * 2 + 1
        const right = index * 2 + 2
        let competitive = index

        if (left < this.heap.length && this.compare(this.heap[left], this.heap[competitive]) < 0) {
            competitive = left
        }
        if (right < this.heap.length && this.compare(this.heap[right], this.heap[competitive]) < 0) {
            competitive = right
        }

        if (competitive !== index) {
            [this.heap[competitive], this.heap[index]] = [this.heap[index], this.heap[competitive]]
            this.heapifyDown(competitive)
        }
    }
}

Javascript(排序数组)

/**
 * @param {number} n
 * @param {number[][]} entries
 */
var MovieRentingSystem = function (_n, entries) {
    this.maxNumSearchResults = 5;

    let sorted = [...entries]
        .sort(([shop1, _1, price1], [shop2, _2, price2]) => {
            let priceDiff = price1 - price2;

            return priceDiff? priceDiff: shop1 - shop2;
        });

    //{ movie: [ [shop1, priceLowest] ... [shopN, priceHighest] ] }
    this.movies = sorted
        .reduce((movies, [shop, movie]) => {
            let shops = movies[movie];

            if(shops == undefined)
                shops = movies[movie] = [];

            shops.push(shop);

            return movies;
        }, {});

    /*
        {
            shop: { movie: { price } }
        }
    */
    this.shops = sorted
        .reduce((shops, [shop, movie, price]) => {
            let data = shops[shop];

            if(data == undefined)
                data = shops[shop] = {};

            data[movie] = {price};

            return shops;
        }, {});

    //i = [shop, movie]
    this.rented = [];
};

/** 
 * @param {number} movie
 * @return {number[]}
 */
MovieRentingSystem.prototype.search = function (movie) {
    let results = [],
        shops = this.movies[movie];

    if (shops) {
        for (let i = 0, l = shops.length; results.length < this.maxNumSearchResults && i < l; i++) {
            let shop = shops[i];

            if (!this.shops[shop][movie].rented) results.push(shop);
        }
    }

    return results;
};

/** 
 * @param {number} shop 
 * @param {number} movie
 * @return {void}
 */
MovieRentingSystem.prototype.rent = function (shop, movie) {
    if (this.shops[shop][movie].rented == undefined) {
        let rentData = [shop, movie];

        this.shops[shop][movie].rented = rentData;
        this.rented.push(rentData);
    }
};

/** 
 * @param {number} shop 
 * @param {number} movie
 * @return {void}
 */
MovieRentingSystem.prototype.drop = function (shop, movie) {
    let movieData = this.shops[shop]?.[movie].rented;

    if (movieData) {
        this.rented.splice(this.rented.indexOf(movieData), 1);
        delete this.shops[shop][movie].rented;
    }
};

/**
 * @return {number[][]}
 */
MovieRentingSystem.prototype.report = function () {
    return this.rented
        .sort(([shop1, movie1], [shop2, movie2]) => {
            let priceDiff = this.shops[shop1][movie1].price - this.shops[shop2][movie2].price;

            if(!priceDiff)
                return shop1 == shop2? movie1 - movie2 : shop1 - shop2;

            return priceDiff;
        })
        .slice(0, this.maxNumSearchResults);
};

/** 
 * Your MovieRentingSystem object will be instantiated and called as such:
 * var obj = new MovieRentingSystem(n, entries)
 * var param_1 = obj.search(movie)
 * obj.rent(shop,movie)
 * obj.drop(shop,movie)
 * var param_4 = obj.report()
 */

results matching ""

    No results matching ""