2280. Minimum Lines to Represent a Line Chart
Problem Description
We have a 2D integer array named stockPrices
, where each element consists of two integers representing a day and the corresponding stock price on that day. The task is to determine the minimum number of straight lines required to connect all the points plotted on an XY plane, with the day as the X-axis and the price as the Y-axis, in the given order after sorting the data by the day. Essentially, we want to find the fewest lines that connect each adjacent point (each day to the next) to form a line chart.
Intuition
To solve this problem, the solution leverages the concept of slopes of lines. When connecting points to draw the line chart, we can continue using the same line as long as consecutive segments share the same slope. When the slopes differ, we need a new line.
To find the slope of the line segment between two points (x, y) and (x1, y1), we calculate (y1 - y) / (x1 - x)
, which gives us the rate of change in price with respect to days. Now, if we compare this slope with the next segment's slope between the points (x1, y1) and (x2, y2), we would normally compute (y2 - y1) / (x2 - x1)
and compare. To avoid division and possible precision issues with floating-point numbers, we instead compare cross-products: (y1 - y) * (x2 - x1)
and (y2 - y1) * (x1 - x)
. If these two products are not equal, the slope has changed, and we need an additional line.
The approach first sorts the stockPrices
based on the day to ensure the points are in the correct order. Then, iterating through each pair of adjacent points, we calculate the aforementioned cross-product to decide whether the current segment can be connected with the prior one or if a new line has to start. The variable ans
keeps track of the number of lines needed.
Remember, we increment the number of lines when a change in slope is detected between segments, and after calculating the slopes, we update the dx
and dy
to be the differences between the current and next points to be ready for the next iteration.
Solution Approach
The implementation given in the reference solution approach follows these steps:
-
Sort the Input: We start by sorting the
stockPrices
array. This ensures that the stock prices are ordered by day, which is essential as we need to connect points from one day to the next. -
Initialize Variables: Two variables
dx
anddy
are initialized to store the differences in thex
(day) andy
(price) coordinates of successive points. Additionally,ans
is initialized to count the number of lines required. -
Iterate Through Points: We use a loop to go through each pair of adjacent points in the sorted array. To do this efficiently, the code leverages the
pairwise
utility, which is not a standard Python function but likely a user-defined or library function that yields pairs of elements from the input list. -
Calculate and Compare Slopes: For each pair
(x, y)
and(x1, y1)
, we calculate the change inx
asdx1 = x1 - x
and the change iny
asdy1 = y1 - y
. We then compare the products of cross-multiplying these with the previous segment'sdx
anddy
to check if the slopes match. Ifdy * dx1
does not equaldx * dy1
, the slope has changed, indicating a need for a new line. -
Increment Line Count: When a slope change is detected, we increment
ans
by 1. -
Update Differences: Whether a new line is added or not, we update
dx
anddy
with the differencesdx1
anddy1
, to use them in the next iteration for slope comparison. -
Return Result: After all points have been processed, the variable
ans
is returned. This gives us the minimum number of lines needed to represent the chart.
Here is the explanation of the algorithm, data structure, or pattern used in each step:
- Sorting: It's the first step and is critical for comparing the slopes of segments in the correct order.
- Looping and Iteration: The use of a loop along with pairwise comparison is a common pattern when we need to check adjacent elements in a sequence.
- Slope Comparison: This step avoids division by comparing slopes using cross-multiplication, which is a mathematical technique to avoid floating-point precision errors and ensure that comparisons are accurate when checking for collinearity between points.
By systematically comparing each pair of points, the solution efficiently calculates the number of lines needed to form the chart without redundant calculations or comparisons.
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 walk through a small example to illustrate the solution approach.
Suppose we have the following 2D array stockPrices
:
stockPrices = [[1, 100], [2, 200], [3, 300], [4, 200], [5, 100], [6, 200]]
-
Sort the Input:
- Since
stockPrices
is already sorted by the day (the first element of each sub-array), we don't need to sort it again.
- Since
-
Initialize Variables:
- We set
dx
anddy
to 0 as there is no previous point to compare with yet. ans
is initialized to 1 because we need at least one line to connect the first point to the next one.
- We set
-
Iterate Through Points:
- Imagine comparing the first point
[1, 100]
with the second[2, 200]
. This sets our initialdx
anddy
to[2-1, 200-100] = [1, 100]
.
- Imagine comparing the first point
-
Calculate and Compare Slopes:
- On the next iteration, we compare the point
[2, 200]
with[3, 300]
. We calculate the changesdx1 = 3 - 2
anddy1 = 300 - 200
, resulting in[1, 100]
. - Now we compare the slopes. Since
dy * dx1
=dx * dy1
(100 * 1 = 1 * 100), the slope hasn't changed, and we stay on the same line.
- On the next iteration, we compare the point
-
Increment Line Count:
- We don't increment
ans
as the line didn't change.
- We don't increment
-
Update Differences:
- We set
dx
anddy
todx1
anddy1
respectively, for the next comparison.
- We set
Continue this process for the remaining points:
-
Comparing
[3, 300]
with[4, 200]
, we calculatedx1 = 4 - 3
anddy1 = 200 - 300
, resulting in[1, -100]
.- Slopes differ (
dy * dx1
=100 * 1
!=-1 * 100
=dx * dy1
), so we incrementans
to 2 and updatedx
anddy
.
- Slopes differ (
-
Comparing
[4, 200]
with[5, 100]
,dx1 = 5 - 4
anddy1 = 100 - 200
, resulting in[1, -100]
.- Slopes match (as they're both
[1, -100]
), so we don't incrementans
and just updatedx
anddy
.
- Slopes match (as they're both
-
Finally, comparing
[5, 100]
with[6, 200]
,dx1 = 6 - 5
anddy1 = 200 - 100
, resulting in[1, 100]
.- Slopes differ again (
dy * dx1
=-100 * 1
!=1 * 100
=dx * dy1
), so we incrementans
to 3.
- Slopes differ again (
-
Return Result:
- After all points have been processed, we conclude that we need
ans
= 3 lines to connect all the points.
- After all points have been processed, we conclude that we need
With this hands-on example, we've illustrated how the solution approach can be applied to determine the minimum number of lines required to connect a series of points in a line chart.
Solution Implementation
1from itertools import pairwise
2from typing import List
3
4class Solution:
5 def minimumLines(self, stock_prices: List[List[int]]) -> int:
6 # Sort the stock prices based on the days
7 stock_prices.sort()
8
9 # Initialize the previous differences in x and y to 0 and 1 respectively as placeholders.
10 prev_diff_x, prev_diff_y = 0, 1
11
12 # Initialize the count of lines needed to 0
13 num_lines = 0
14
15 # Iterate over each pair of adjacent stock prices
16 for (current_x, current_y), (next_x, next_y) in pairwise(stock_prices):
17 # Compute the differences in x and y for the current pair
18 diff_x, diff_y = next_x - current_x, next_y - current_y
19
20 # If the cross product of the differences does not equal to zero,
21 # then the points are not on the same line. Increment the number of lines.
22 if prev_diff_y * diff_x != prev_diff_x * diff_y:
23 num_lines += 1
24
25 # Update the previous differences for the next iteration
26 prev_diff_x, prev_diff_y = diff_x, diff_y
27
28 # Return the total number of lines needed
29 return num_lines
30
1class Solution {
2 public int minimumLines(int[][] stockPrices) {
3 // Sort the stock prices array based on the day (the 0th element of each sub-array);
4 // if the days are equal, sort based on the price (the 1st element of each sub-array)
5 Arrays.sort(stockPrices, (a, b) -> Integer.compare(a[0], b[0]));
6
7 // Initialize variables to keep track of previous slope components
8 int prevDeltaX = 0, prevDeltaY = 1; // Initiated to 0 and 1 to handle the starting calculation
9 int lineCount = 0; // Counter for the minimum lines needed
10
11 // Iterate over the stock prices starting from the second price
12 for (int i = 1; i < stockPrices.length; ++i) {
13 // Get the previous day's stock price
14 int previousX = stockPrices[i - 1][0], previousY = stockPrices[i - 1][1];
15 // Get the current day's stock price
16 int currentX = stockPrices[i][0], currentY = stockPrices[i][1];
17
18 // Calculate the change in x and y (i.e., the slope components) for the current line
19 int deltaX = currentX - previousX, deltaY = currentY - previousY;
20
21 // Check if the current line has a different slope than the previous one
22 // by comparing cross products of the slope components (to avoid floating-point division)
23 if (prevDeltaY * deltaX != prevDeltaX * deltaY) {
24 ++lineCount; // A different slope means a new line is needed
25 }
26
27 // Update previous slope components for the next iteration
28 prevDeltaX = deltaX;
29 prevDeltaY = deltaY;
30 }
31
32 // Return the total number of lines needed to connect all points with straight lines
33 return lineCount;
34 }
35}
36
1#include <vector>
2#include <algorithm>
3
4class Solution {
5public:
6 int minimumLines(std::vector<std::vector<int>>& stockPrices) {
7 // Sort stockPrices by their x-value (date)
8 std::sort(stockPrices.begin(), stockPrices.end());
9
10 // Initialize previous difference in x and y to compare slopes.
11 // Starting with an impossible value for the very first comparison.
12 int prevDeltaX = 0, prevDeltaY = 1;
13
14 // Begin with no lines drawn
15 int numLines = 0;
16
17 // Iterate through stockPrices starting from the second point
18 for (int i = 1; i < stockPrices.size(); ++i) {
19 // Get x and y of the current and previous points
20 int currX = stockPrices[i - 1][0], currY = stockPrices[i - 1][1];
21 int nextX = stockPrices[i][0], nextY = stockPrices[i][1];
22
23 // Calculate differences in x and y for the current segment
24 int deltaX = nextX - currX, deltaY = nextY - currY;
25
26 // If the cross product of the two segments is not zero,
27 // they are not collinear, so the slope has changed and a new line is needed.
28 if (static_cast<long long>(prevDeltaY) * deltaX != static_cast<long long>(prevDeltaX) * deltaY) {
29 ++numLines;
30 // Update previous differences to current one for next iteration comparison.
31 prevDeltaX = deltaX;
32 prevDeltaY = deltaY;
33 }
34 }
35
36 // Return the total number of lines needed
37 return numLines;
38 }
39};
40
1function minimumLines(stockPrices: number[][]): number {
2 // Get the number of stock price entries
3 const stockPricesCount = stockPrices.length;
4
5 // Sort the stock prices based on the first value of each pair (the dates are sorted)
6 stockPrices.sort((a, b) => a[0] - b[0]);
7
8 // Initialize the number of lines needed to connect all points
9 let linesRequired = 0;
10
11 // Initialize a variable to store the previous slope as a pair of BigInts for accurate calculation
12 let previousSlope: [BigInt, BigInt] = [BigInt(0), BigInt(0)];
13
14 // Start from the second point and compare slopes of consecutive points
15 for (let i = 1; i < stockPricesCount; i++) {
16 // Get the coordinates of the current point and the previous point
17 const [previousX, previousY] = stockPrices[i - 1];
18 const [currentX, currentY] = stockPrices[i];
19
20 // Calculate the difference in x and y using BigInt for accuracy (to handle very large numbers)
21 const differenceX = BigInt(currentX - previousX);
22 const differenceY = BigInt(currentY - previousY);
23
24 // If it's the first line or the slope differs from the previous slope, increment the line count
25 if (i == 1 || differenceX * previousSlope[1] !== differenceY * previousSlope[0]) {
26 linesRequired++;
27 }
28
29 // Update the previous slope to the current slope for the next iteration
30 previousSlope = [differenceX, differenceY];
31 }
32
33 // Return the total number of lines required
34 return linesRequired;
35}
36
Time and Space Complexity
Time Complexity
The given Python code first sorts the stockPrices
. Sorting takes O(n log n)
time where n
is the number of stockPrices
.
Then, it iterates through the sorted prices to determine the number of lines required. This iteration involves a single pass through the stockPrices
list, which takes O(n)
time.
Inside the loop, it performs constant-time arithmetic operations to determine if a new line segment is required. Therefore, these operations do not increase the overall complexity beyond O(n)
.
Since sorting is the most time-consuming operation and is followed by a linear pass, the total time complexity of this algorithm is O(n log n) + O(n)
, which simplifies to O(n log n)
.
Space Complexity
The space complexity is determined by the additional space required by the algorithm besides the input.
Here, the algorithm uses a fixed amount of space to store temporary values like dx
, dy
, dx1
, and dy1
, so it uses O(1)
additional space.
Sorting is typically done in-place (especially if the Python sort()
method is used), therefore, the space complexity remains O(1)
.
In summary, the space complexity of the algorithm is O(1)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Want a Structured Path to Master System Design Too? Don’t Miss This!