2146. K Highest Ranked Items Within a Price Range


Problem Description

You are presented with a scenario where you're inside a shop represented by a 2D integer array called grid. Each cell in this array can be:

  • A 0 indicating a wall, through which you cannot pass.
  • A 1 indicating an empty space, through which you can move freely.
  • Any other positive integer representing the price of an item located at that cell, which you can also move to and from.

Your movement is constrained such that you can only travel to adjacent cells horizontally or vertically, not diagonally, and each move counts as 1 step.

You have specific requirements for the items you are interested in: they must fall within a certain price range, [low, high], based on the pricing array. The starting point of your search is given by the start array [row, col], signifying the grid cell where you begin.

Your task is to find the k highest-ranked items based on the following criteria in order of priority:

  1. Distance: The number of steps from the start. A shorter distance means a higher ranking.
  2. Price: A lower price means a higher ranking, but it must be within your specified range.
  3. Row Number: Items in a lower row are ranked higher.
  4. Column Number: Among items in the same row, those in a lower column are ranked higher.

Your objective is to return the top k items' positions, ranked from highest to lowest based on these criteria. However, if there are less than k items that are reachable and within the price range, you must return the positions of all these items.

Intuition

To solve this problem, a Breadth-First Search (BFS) approach is intuitive because we're looking for the shortest path from our starting point to each item, fulfilling the first and most critical ranking criterion: distance.

BFS allows us to explore the grid level by level, starting from the initial position. As we move from one cell to its adjacent cells, we keep track of the distance we've covered in steps. We can then simultaneously check each newly discovered cell to determine whether it contains an item within our price range.

While performing BFS, for each item we encounter that fits within our budget, we store its distance from the start, its price, and its coordinates. We then mark the cell as visited (i.e., set it to 0 to indicate a wall) to avoid revisiting it.

After the BFS is completed and weโ€™ve explored all the reachable cells, we have a list of items, each with its distance, price, and position. We sort this list according to our ranking criteria. First by distance, then by price, then by row, and finally by column number.

Once sorted, we slice the list to retrieve the top k ranked items. If there were fewer than k items available, we would end up with a list of all applicable items. Only the row and column positions of these top k ranked items are returned, as specified by the problem description.

The key to making this approach work is to prioritize our sorting according to the stated ranking criteria and to carefully implement BFS to ensure that we do not miss any items or consider any twice.

Learn more about Breadth-First Search, Sorting and Heap (Priority Queue) patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece๏ผš

How does merge sort divide the problem into subproblems?

Solution Approach

The provided solution implements the BFS (Breadth-First Search) strategy using the following steps:

  1. Initialization: Before starting the BFS, the initial setup is done by defining the size of the grid (m x n), and combining the start and pricing arrays to obtain the starting row (row), column (col), and the low and high price limits. An empty list is created to store details of the items found (items), and an initial check is performed to see if the starting position itself is an item within the price range and gets added to items if so.

  2. BFS Queue: A queue data structure is used to perform the BFS. This is initialized with the starting position and an initial depth (distance) of 0. The value of the starting position in the grid is set to 0 which both marks it as visited and prevents it from being returned more than once.

  3. BFS Execution: The BFS is then carried out until the queue is empty. Each element in the queue is a tuple with the current cell's coordinates and the distance from the start. In the loop, adjacent cells are calculated and checked against grid boundaries and whether they've been visited (not a 0).

  4. Item Discovery & Queue Update: For every unvisited and valid adjacent cell that lies within the specified price range, its details are appended to items (including its distance, price, and coordinates). Regardless of whether the cell has an item or is just an empty cell, it's marked as visited, and the new cell with the updated distance is added to the queue for further exploration.

  5. Sorting: After BFS concludes, we have potentially discovered all items in the grid. This list of items is then sorted by the sort() method of the list, which applies the sorting based on the tuples inside items. The list is sorted by the distance first (since the distances are the first element of the tuples), then by price (second element), row number (third element), and column number (fourth element). This automated sorting by tuple comparison naturally sorts according to all the ranking criteria specified.

  6. Result Formation: Finally, we slice the sorted list to only take the first k elements with [:k]. The list slicing accommodates the situation where there may be fewer than k items found. We only return the row and column positions of these items, hence we map the item[2:] (row and column) of each item to the output list.

This solution approach ensures that we respect the priority of the ranking criteria, beginning with finding the shortest paths using BFS, then selecting and ordering the target items as needed. The BFS strategy effectively handles the grid exploration, while Python's built-in sorting mechanisms and list operations allow us to efficiently organize and return the final results.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

What is the space complexity of the following code?

1int sum(int n) {
2  if (n <= 0) {
3    return 0;
4  }
5  return n + sum(n - 1);
6}

Example Walkthrough

Let's consider a small grid to illustrate the solution approach:

Suppose our grid looks like this, where S denotes the starting position:

1S 1 4
21 0 2
35 1 3

We're given the following input parameters:

  • start = [0, 0] (the position of S)
  • pricing = [2, 5] (the price range)
  • k = 2 (we are interested in the top 2 items)

Let's walk through the solution steps with this grid:

  1. Initialization: We set row = 0 and col = 0 as our starting point. The price limits are low = 2 and high = 5. The items list is created and starts off empty, as S is not an item.

  2. BFS Queue: We initialize our BFS queue with [(0, 0, 0)] where the tuple represents (row, col, distance). The grid's starting position is marked as visited by setting it to 0.

  3. BFS Execution: We begin BFS, taking items from the queue one by one. We first dequeue the starting point (0, 0, 0) and look at its adjacent open cells (0, 1) and (1, 0) since only horizontal and vertical moves are allowed.

  4. Item Discovery & Queue Update:

    • At (0, 1), there's a 1, which is an open space but not an item within the price range. Mark it visited and enqueue (0, 1, 1) for distance 1.
    • At (1, 0), there's also a 1, treated similarly as above.

    Now our queue has two positions: [(0, 1, 1), (1, 0, 1)].

    Dequeue (0, 1, 1), check its adjacent cells:

    • (0, 2) has an item priced 4, which is within the range. We add (1, 4, 0, 2) to items (distance, price, row, col) and enqueue the position for further BFS.

    Do the same for (1, 0, 1):

    • (2, 0) contains an item priced 5, within range. (1, 5, 2, 0) is added to items.
    • (1, 1) is a wall, thus ignored.

    The queue now contains [(0, 2, 1), (2, 0, 1), (1, 0, 1)].

Continue BFS until the queue is empty, adding discovered items within the price range to the items list.

  1. Sorting: After BFS, we have items = [(1, 4, 0, 2), (1, 5, 2, 0)]. We sort this list:

    • First by distance: Both items have the same distance, so no change.
    • Then by price: Both items are also priced within our range, so no change again.
    • Then by row number: The item at (0, 2) is in a higher row (closer to the top) than the item at (2, 0), so the order remains the same.
    • Lastly, by column number: No need to sort since they are in different rows.
  2. Result Formation: We want the top k=2 ranked items, so we take the first 2 elements in items and map only their row and column positions to the output format: The final output is [ [0, 2], [2, 0] ] representing the grid positions of the items.

These steps concatenate to provide a solution that respects the priority of distance, price, row and column positions while seeking the top k items within the specified range using Breadth-First Search.

Solution Implementation

1from collections import deque
2from typing import List
3
4class Solution:
5    def highestRankedKItems(self, grid: List[List[int]], pricing: List[int], start: List[int], k: int) -> List[List[int]]:
6        # dimensions of the grid
7        num_rows, num_cols = len(grid), len(grid[0])
8        # unpack start and pricing into usable variables
9        start_row, start_col, price_low, price_high = start + pricing
10      
11        # list to collect items that meet criteria
12        items = []
13      
14        # check and add the starting position if it meets the pricing criteria
15        if price_low <= grid[start_row][start_col] <= price_high:
16            items.append([0, grid[start_row][start_col], start_row, start_col])
17      
18        # initialize queue for BFS with the starting position
19        queue = deque([(start_row, start_col, 0)])
20      
21        # mark the starting position as visited by setting it to zero
22        grid[start_row][start_col] = 0
23      
24        # perform BFS from the starting position
25        while queue:
26            row, col, distance = queue.popleft()
27          
28            # explore the neighbors (in 4 directions)
29            for delta_row, delta_col in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
30                new_row, new_col = row + delta_row, col + delta_col
31              
32                # continue only if the next cell is within the grid bounds and is not visited
33                if 0 <= new_row < num_rows and 0 <= new_col < num_cols and grid[new_row][new_col]:
34                    # if the cell value is within the price range, add to the items list
35                    if price_low <= grid[new_row][new_col] <= price_high:
36                        items.append([distance + 1, grid[new_row][new_col], new_row, new_col])
37                  
38                    # add the cell to the queue and mark as visited
39                    queue.append((new_row, new_col, distance + 1))
40                    grid[new_row][new_col] = 0
41      
42        # sort the items based on the given criteria: distance, price, row, and column
43        items.sort()
44      
45        # only take the top k items and remove the distance and price before returning
46        return [item[2:] for item in items][:k]
47
48# Example usage:
49# solution = Solution()
50# result = solution.highestRankedKItems([[1,2,0,1],[1,3,0,1],[0,2,5,1]], [2,5], [0,0], 3)
51# print(result)  # [[0,1],[1,1],[2,1]] (if within pricing)
52
1class Solution {
2    public List<List<Integer>> highestRankedKItems(
3        int[][] grid, int[] pricing, int[] start, int k) {
4      
5        // Grid dimensions
6        int rows = grid.length, columns = grid[0].length;
7      
8        // Starting position
9        int startRow = start[0], startColumn = start[1];
10      
11        // Pricing range
12        int lowPrice = pricing[0], highPrice = pricing[1];
13      
14        // List to store items that fall within the price range
15        List<int[]> validItems = new ArrayList<>();
16      
17        // Check if starting position has an item within the price range
18        if (lowPrice <= grid[startRow][startColumn] && grid[startRow][startColumn] <= highPrice) {
19            validItems.add(new int[] {0, grid[startRow][startColumn], startRow, startColumn});
20        }
21      
22        // Mark the starting grid cell as visited by setting it to 0
23        grid[startRow][startColumn] = 0;
24      
25        // Queue for BFS traversal
26        Deque<int[]> queue = new ArrayDeque<>();
27        queue.offer(new int[] {startRow, startColumn, 0});
28      
29        // Directions for traversal: up, right, down, left
30        int[] directions = {-1, 0, 1, 0, -1};
31      
32        // Perform BFS to find all items within the price range
33        while (!queue.isEmpty()) {
34            int[] position = queue.poll();
35          
36            // Current cell position and distance from start
37            int currentRow = position[0], currentColumn = position[1], distance = position[2];
38          
39            // Explore neighbors
40            for (int index = 0; index < 4; ++index) {
41                int newRow = currentRow + directions[index], newColumn = currentColumn + directions[index + 1];
42                // Validate cell position and check if not visited (grid value greater than 0)
43                if (newRow >= 0 && newRow < rows && newColumn >= 0 && newColumn < columns && grid[newRow][newColumn] > 0) {
44                    // Check if the item is within the price range
45                    if (lowPrice <= grid[newRow][newColumn] && grid[newRow][newColumn] <= highPrice) {
46                        validItems.add(new int[] {distance + 1, grid[newRow][newColumn], newRow, newColumn});
47                    }
48                    // Mark as visited
49                    grid[newRow][newColumn] = 0;
50                    // Add to queue for further BFS traversal
51                    queue.offer(new int[] {newRow, newColumn, distance + 1});
52                }
53            }
54        }
55      
56        // Sort the valid items based on the given criteria: distance, price, row, column
57        validItems.sort((item1, item2) -> {
58            // Compare distances
59            if (item1[0] != item2[0]) {
60                return item1[0] - item2[0];
61            }
62            // Compare prices
63            if (item1[1] != item2[1]) {
64                return item1[1] - item2[1];
65            }
66            // Compare row positions
67            if (item1[2] != item2[2]) {
68                return item1[2] - item2[2];
69            }
70            // Compare column positions
71            return item1[3] - item2[3];
72        });
73      
74        // Prepare the final list of highest-ranked items
75        List<List<Integer>> highestRankedItems = new ArrayList<>();
76        for (int i = 0; i < validItems.size() && i < k; ++i) {
77            int[] item = validItems.get(i);
78            highestRankedItems.add(Arrays.asList(item[2], item[3]));
79        }
80      
81        // Return the list of highest-ranked items
82        return highestRankedItems;
83    }
84}
85
1#include <vector>
2#include <queue>
3#include <tuple>
4#include <algorithm>
5
6class Solution {
7public:
8    vector<vector<int>> highestRankedKItems(vector<vector<int>>& grid, vector<int>& pricing, vector<int>& start, int k) {
9        // Dimensions of the grid
10        int rows = grid.size(), cols = grid[0].size();
11        // Start position
12        int startRow = start[0], startCol = start[1];
13        // Pricing range
14        int lowPrice = pricing[0], highPrice = pricing[1];
15        // Items that are within the price range
16        vector<tuple<int, int, int, int>> validItems;
17      
18        // Check if the start position item is within the price range
19        if (lowPrice <= grid[startRow][startCol] && grid[startRow][startCol] <= highPrice)
20            validItems.emplace_back(0, grid[startRow][startCol], startRow, startCol);
21      
22        // Queue for BFS (Breadth-First Search)
23        queue<tuple<int, int, int>> bfsQueue;
24        bfsQueue.emplace(startRow, startCol, 0);
25      
26        // Mark the starting position as visited by setting its value to 0
27        grid[startRow][startCol] = 0;
28      
29        // Directions for the BFS (up, right, down, left)
30        vector<int> directions = {-1, 0, 1, 0, -1};
31      
32        // Perform BFS
33        while (!bfsQueue.empty()) {
34            auto [currentRow, currentCol, distance] = bfsQueue.front();
35            bfsQueue.pop();
36            for (int i = 0; i < 4; ++i) {
37                int newRow = currentRow + directions[i], newCol = currentCol + directions[i + 1];
38                // Check if the new position is within bounds and has not been visited
39                if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && grid[newRow][newCol]) {
40                    // Check if item is within the price range and add it to valid items
41                    if (lowPrice <= grid[newRow][newCol] && grid[newRow][newCol] <= highPrice) 
42                        validItems.emplace_back(distance + 1, grid[newRow][newCol], newRow, newCol);
43                    // Mark the item as visited
44                    grid[newRow][newCol] = 0;
45                    // Add the position to the queue for the next level of BFS
46                    bfsQueue.emplace(newRow, newCol, distance + 1);
47                }
48            }
49        }
50      
51        // Sort the valid items according to defined sorting criteria
52        sort(validItems.begin(), validItems.end());
53      
54        // Collect the first k items or the total number of valid items if it is less than k
55        vector<vector<int>> result;
56        for (int i = 0; i < validItems.size() && i < k; ++i) {
57            auto [distance, price, x, y] = validItems[i];
58            result.push_back({x, y});
59        }
60        return result;
61    }
62};
63
1// The following function finds the highest ranked k items within a grid,
2// based on certain criteria such as price range and distance from a starting point.
3
4// Initialize the grid's rows and columns variables
5let rows: number;
6let cols: number;
7
8// Function to find the highest ranked items
9function highestRankedKItems(grid: number[][], pricing: number[], start: number[], k: number): number[][] {
10    rows = grid.length;
11    cols = grid[0].length;
12
13    // Extracting the start positions and pricing range.
14    const [startRow, startCol] = start;
15    const [lowPrice, highPrice] = pricing;
16
17    // Define a tuple type for the items with a distance, price, row, and column.
18    type ItemTuple = [distance: number, price: number, row: number, col: number];
19
20    // Array to store the valid items that are within the price range.
21    let validItems: ItemTuple[] = [];
22  
23    // Check if the starting item is within the price range.
24    if (lowPrice <= grid[startRow][startCol] && grid[startRow][startCol] <= highPrice) {
25        validItems.push([0, grid[startRow][startCol], startRow, startCol]);
26    }
27
28    // Queue for performing BFS
29    let bfsQueue: Array<[row: number, col: number, distance: number]> = [];
30    bfsQueue.push([startRow, startCol, 0]);
31
32    // Mark the starting position as visited by setting its value to 0
33    grid[startRow][startCol] = 0;
34
35    // Directions for BFS: up, right, down, left
36    const directions: number[] = [-1, 0, 1, 0, -1];
37
38    // Perform BFS
39    while (bfsQueue.length > 0) {
40        const [currentRow, currentCol, distance] = bfsQueue.shift()!;
41        for (let i = 0; i < 4; ++i) {
42            const newRow = currentRow + directions[i];
43            const newCol = currentCol + directions[i + 1];
44
45            // Check if new position is in bounds and not visited
46            if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && grid[newRow][newCol] > 0) {
47                // Check if item is within the price range and add to valid items
48                if (lowPrice <= grid[newRow][newCol] && grid[newRow][newCol] <= highPrice) {
49                    validItems.push([distance + 1, grid[newRow][newCol], newRow, newCol]);
50                }
51                // Mark as visited
52                grid[newRow][newCol] = 0;
53                // Enqueue the new position for the next iteration
54                bfsQueue.push([newRow, newCol, distance + 1]);
55            }
56        }
57    }
58
59    // Sort the valid items based on criteria (distance, price, row, col)
60    validItems.sort((a, b) => {
61        if (a[0] !== b[0]) return a[0] - b[0]; // Sort by distance
62        if (a[1] !== b[1]) return a[1] - b[1]; // Then by price
63        if (a[2] !== b[2]) return a[2] - b[2]; // Then by row
64        return a[3] - b[3];                    // Finally by column
65    });
66
67    // Collect up to k items based on sorted criteria
68    const result: number[][] = [];
69    for (let i = 0; i < Math.min(validItems.length, k); ++i) {
70        const [_, __, x, y] = validItems[i];
71        result.push([x, y]);
72    }
73  
74    return result;
75}
76
Not Sure What to Study? Take the 2-min Quiz๏ผš

A heap is a ...?

Time and Space Complexity

The given Python code performs a breadth-first search (BFS) on the given grid to find items within the specified pricing range that are reachable from the start position, then ranks those items and returns up to k of the highest-ranked items.

Time Complexity:

The BFS algorithm traverses each cell of the grid at most once. Therefore, the time complexity of the BFS traversal is O(m*n), where m is the number of rows and n is the number of columns in the grid, because it needs to process every cell. In addition to the traversal, the algorithm appends items to the items list if they fall within the pricing range and are not already visited.

In the worst-case scenario, the number of items appended to the items list can be O(m*n) if every cell falls within the pricing range. Sorting these items contributes to an additional time complexity of O((m*n)*log(m*n)) due to the use of the sort function on the items list.

Hence, the time complexity of this algorithm is dominated by the sorting operation, resulting in an overall time complexity of O((m*n)*log(m*n)).

Space Complexity:

Space complexity consists of the space required to store the q (queue), the items list, and the modifications to the grid. The queue, in the worst case, can contain O(min(m*n, k)) elements, when the BFS has found k items and they are pending to be processed or until all the cells are visited in a case where k is large.

The items list also can take up to O(m*n) space in the worst case, when every cell falls within the pricing range. The grid is modified in-place without using additional space, except for storing the modified values which do not take extra space beyond what is given as input.

Considering both the queue and the items list, the overall space complexity is O(m*n) because the space complexity does not depend on k. The largest area of space is taken by the items list which can potentially hold an item for every cell in the grid.

In conclusion, the time complexity of the code is O((m*n)*log(m*n)), and the space complexity is O(m*n).

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.


Recommended Readings


Got a question?ย Ask the Teaching Assistantย anything you don't understand.

Still not clear? Ask in the Forum, ย Discordย orย Submitย the part you don't understand to our editors.

โ†
โ†‘TA ๐Ÿ‘จโ€๐Ÿซ