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:
- Distance: The number of steps from the start. A shorter distance means a higher ranking.
- Price: A lower price means a higher ranking, but it must be within your specified range.
- Row Number: Items in a lower row are ranked higher.
- 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.
Flowchart Walkthrough
Let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough for LeetCode 2146. K Highest Ranked Items Within a Price Range:
Is it a graph?
- Yes: The grid provided in the problem can be treated as a graph, where each cell in the store grid is a node and adjacent cells are connected.
Is it a tree?
- No: The grid structure is not inherently hierarchical or branch-based like a tree.
Is the problem related to directed acyclic graphs (DAGs)?
- No: The problem is about exploring a grid and finding items within a price range based on specific conditions, not about directed acyclic graph properties.
Is the problem related to shortest paths?
- Yes: The problem deals with finding the shortest path to items within certain conditions (not just straight-line distance), regarding how far they can be explored from a starting position.
Is the graph weighted?
- No: Moving from one cell to another has uniform cost; there are no different weights applied to moving through the grid cells other than the conditions on item prices and rankings.
Conclusion: According to the flowchart, Breadth-First Search (BFS) is recommended for exploring this problem. It is ideal because BFS explores layer by layer, which is perfect for determining the shortest path in an unweighted grid while meeting the specific conditions of the item ranking and price.
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.
Solution Approach
The provided solution implements the BFS (Breadth-First Search) strategy using the following steps:
-
Initialization: Before starting the BFS, the initial setup is done by defining the size of the
grid
(m x n
), and combining thestart
andpricing
arrays to obtain the starting row (row
), column (col
), and thelow
andhigh
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 toitems
if so. -
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. -
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
). -
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. -
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 insideitems
. 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. -
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 thank
items found. We only return the row and column positions of these items, hence we map theitem[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.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small grid to illustrate the solution approach:
Suppose our grid
looks like this, where S
denotes the starting position:
S 1 4 1 0 2 5 1 3
We're given the following input parameters:
start
= [0, 0] (the position ofS
)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:
-
Initialization: We set
row
= 0 andcol
= 0 as our starting point. The price limits arelow
= 2 andhigh
= 5. Theitems
list is created and starts off empty, asS
is not an item. -
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 to0
. -
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. -
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)
toitems
(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 toitems
.(1, 1)
is a wall, thus ignored.
The queue now contains
[(0, 2, 1), (2, 0, 1), (1, 0, 1)]
. - At
Continue BFS until the queue is empty, adding discovered items within the price range to the items
list.
-
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.
-
Result Formation: We want the top
k=2
ranked items, so we take the first 2 elements initems
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
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.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
https algomonster s3 us east 2 amazonaws com cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https algomonster s3 us east 2 amazonaws com cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue
Want a Structured Path to Master System Design Too? Don’t Miss This!