Minimum Lines to Represent a Line Chart
MedPrompt
You are given a 2D integer array stockPrices where stockPrices[i] = [dayi, pricei] indicates the price of the stock on day dayi is pricei. A line chart is created from the array by plotting the points on an XY plane with the X-axis representing the day and the Y-axis representing the price and connecting adjacent points. One such example is shown below:
Return the minimum number of lines needed to represent the line chart.
Example 1:
Input: stockPrices = [[1,7],[2,6],[3,5],[4,4],[5,4],[6,3],[7,2],[8,1]]
Output: 3
Explanation:
The diagram above represents the input, with the X-axis representing the day and Y-axis representing the price.
The following 3 lines can be drawn to represent the line chart:
- Line 1 (in red) from (1,7) to (4,4) passing through (1,7), (2,6), (3,5), and (4,4).
- Line 2 (in blue) from (4,4) to (5,4).
- Line 3 (in green) from (5,4) to (8,1) passing through (5,4), (6,3), (7,2), and (8,1).
It can be shown that it is not possible to represent the line chart using less than 3 lines.Example 2:
Input: stockPrices = [[3,4],[1,2],[7,8],[2,3]]
Output: 1
Explanation:
As shown in the diagram above, the line chart can be represented with a single line.
Constraints:
1 <= stockPrices.length <= 105stockPrices[i].length == 21 <= dayi, pricei <= 109- All
dayiare distinct.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves sorting the stock prices by day and then iterating through the points to count the number of lines. It identifies new lines by comparing the slopes between consecutive segments. The slope calculation is done using floating-point division, which is intuitive but can be problematic.
Algorithm
- Handle base cases: If
n <= 1points, return 0. - Sort the
stockPricesarray by day in ascending order. - If
n == 2, return 1. - Initialize
lines = 1andcurrentSlopeusing the first two points. - Iterate from the third point (
i = 2) to the end. - Calculate
newSlopebetween pointi-1andiusing floating-point division. - If
newSlopeis not equal tocurrentSlope, incrementlinesand updatecurrentSlope. - Return
lines.
Walkthrough
The algorithm begins by handling the trivial cases where the number of points is less than two. For two or more points, the stockPrices array is first sorted chronologically based on the day. We initialize a line counter to 1, as at least one line is required to connect the first two points. We then calculate the slope of this initial line. The algorithm then iterates from the third point onwards, calculating the slope of the new segment (from the previous point to the current point) and comparing it with the slope of the preceding segment. If the slopes differ, it indicates a 'bend' in the chart, meaning a new line is required. We then increment our line counter and update the current slope for the next comparison. A key drawback of this method is its reliance on floating-point arithmetic (double), which can suffer from precision errors when comparing values for equality.
import java.util.Arrays; class Solution { public int minimumLines(int[][] stockPrices) { if (stockPrices.length <= 1) { return 0; } Arrays.sort(stockPrices, (a, b) -> Integer.compare(a[0], b[0])); if (stockPrices.length == 2) { return 1; } int lines = 1; // Calculate the initial slope using floating-point division double currentSlope = (double)(stockPrices[1][1] - stockPrices[0][1]) / (stockPrices[1][0] - stockPrices[0][0]); for (int i = 2; i < stockPrices.length; i++) { double newSlope = (double)(stockPrices[i][1] - stockPrices[i-1][1]) / (stockPrices[i][0] - stockPrices[i-1][0]); // Comparing floating-point numbers for exact equality is unreliable if (newSlope != currentSlope) { lines++; currentSlope = newSlope; } } return lines; }}Complexity
Time
O(N log N), where N is the number of stock prices. The sorting step dominates the time complexity. The subsequent loop runs in O(N) time.
Space
O(log N) to O(N), depending on the implementation of the sorting algorithm. In Java, `Arrays.sort` for object arrays uses Timsort, which requires O(N) space in the worst case.
Trade-offs
Pros
Conceptually straightforward and easy to map from the geometric definition of a slope.
Cons
Using floating-point numbers (
double) for slope calculation and comparison is prone to precision errors. Two slopes that should be identical might be represented by slightly different binary values, leading to incorrect comparisons.Floating-point arithmetic can be slower than integer arithmetic.
This optimal approach also sorts the points by day first. However, to avoid the precision issues and potential performance overhead of floating-point arithmetic, it compares slopes using integer cross-multiplication. This method is both robust and efficient.
Algorithm
- Handle base cases: If
n <= 1points, return 0. - Sort the
stockPricesarray by day in ascending order. - Initialize
lines = 1. - Iterate from the third point (
i = 2) to the end. - For each set of three consecutive points
p_{i-2},p_{i-1},p_i, calculate the differences:dx1 = p_{i-1}[0] - p_{i-2}[0],dy1 = p_{i-1}[1] - p_{i-2}[1],dx2 = p_i[0] - p_{i-1}[0],dy2 = p_i[1] - p_{i-1}[1]. - Using
longto prevent overflow, check ifdy2 * dx1 != dy1 * dx2. - If the cross-products are not equal, increment
lines. - Return
lines.
Walkthrough
The algorithm's strength lies in how it checks for collinearity. Three points (x0, y0), (x1, y1), and (x2, y2) are collinear if the slope of the segment (x0, y0)-(x1, y1) is equal to the slope of (x1, y1)-(x2, y2). Mathematically, this is (y1 - y0) / (x1 - x0) == (y2 - y1) / (x2 - x1). To avoid division and floating-point numbers, we can rewrite this using cross-multiplication: (y1 - y0) * (x2 - x1) == (y2 - y1) * (x1 - x0). This check can be performed entirely with integers. The algorithm proceeds as follows:
- Handle the base case: if there are one or zero points, no lines are needed.
- Sort the
stockPricesarray by day. - Initialize a line counter to 1 (for the first segment).
- Iterate from the third point (
i = 2) to the end. In each step, compare the slope of the line(p_{i-2}, p_{i-1})with the slope of(p_{i-1}, p_i)using the cross-multiplication technique. - Since the coordinates can be up to
10^9, their differences can also be large. The product of two differences can exceed the capacity of a 32-bit integer, so we must use a 64-bit integer (longin Java) for the multiplication to prevent overflow. - If the cross-products are not equal, the three points are not collinear, so we increment the line counter.
import java.util.Arrays; class Solution { public int minimumLines(int[][] stockPrices) { int n = stockPrices.length; if (n <= 1) { return 0; } // Sort the stock prices by day Arrays.sort(stockPrices, (a, b) -> Integer.compare(a[0], b[0])); int lines = 1; for (int i = 2; i < n; i++) { // Get the three consecutive points int[] p0 = stockPrices[i - 2]; int[] p1 = stockPrices[i - 1]; int[] p2 = stockPrices[i]; // Calculate differences for cross-multiplication // (y2 - y1) * (x1 - x0) == (y1 - y0) * (x2 - x1) // Use long to prevent overflow, as coordinates can be up to 10^9 long x0 = p0[0], y0 = p0[1]; long x1 = p1[0], y1 = p1[1]; long x2 = p2[0], y2 = p2[1]; long val1 = (y2 - y1) * (x1 - x0); long val2 = (y1 - y0) * (x2 - x1); if (val1 != val2) { lines++; } } return lines; }}Complexity
Time
O(N log N), where N is the number of stock prices. The performance is bottlenecked by the sorting step. The loop for checking slopes runs in O(N).
Space
O(log N) to O(N), which is the space required by the sorting algorithm. For instance, Java's `Arrays.sort` for objects (Timsort) can use up to O(N) space.
Trade-offs
Pros
Numerically stable and exact, as it completely avoids floating-point arithmetic.
Generally faster than the floating-point approach due to using integer operations.
Robust against all valid inputs within the given constraints.
Cons
Requires awareness of potential integer overflow and the need to use a larger data type (like
long) for intermediate calculations.
Solutions
Solution
class Solution {public int minimumLines(int[][] stockPrices) { Arrays.sort(stockPrices, (a, b)->a[0] - b[0]); int dx = 0, dy = 1; int ans = 0; for (int i = 1; i < stockPrices.length; ++i) { int x = stockPrices[i - 1][0], y = stockPrices[i - 1][1]; int x1 = stockPrices[i][0], y1 = stockPrices[i][1]; int dx1 = x1 - x, dy1 = y1 - y; if (dy * dx1 != dx * dy1) { ++ans; } dx = dx1; dy = dy1; } return ans; }}class Solution {public: int minimumLines(vector<vector<int>> &stockPrices) { sort(stockPrices.begin(), stockPrices.end()); int dx = 0, dy = 1; int ans = 0; for (int i = 1; i < stockPrices.size(); ++i) { int x = stockPrices[i - 1][0], y = stockPrices[i - 1][1]; int x1 = stockPrices[i][0], y1 = stockPrices[i][1]; int dx1 = x1 - x, dy1 = y1 - y; if ((long long)dy * dx1 != (long long)dx * dy1) ++ans; dx = dx1; dy = dy1; } return ans; }};class Solution: def minimumLines(self, stockPrices: List[List[int]]) -> int: stockPrices . sort() dx, dy = 0, 1 ans = 0 for (x, y), (x1, y1) in pairwise(stockPrices): dx1, dy1 = x1 - x, y1 - y if dy * dx1 != dx * dy1: ans += 1 dx, dy = dx1, dy1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.