Skip to content

Commit 3a3230e

Browse files
committed
add python solutions for monotonic stack pattern
1 parent 0557c13 commit 3a3230e

File tree

4 files changed

+490
-0
lines changed

4 files changed

+490
-0
lines changed

python/daily-temperatures.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Daily Temperatures
2+
Given a list of daily temperatures temperatures, return a list answer such that for each day in the input, answer[i] is the number of days you would have to wait until a warmer temperature. If there is no future day for which this is possible, put 0 instead.
3+
4+
### Constraints:
5+
- 1 <= temperatures.length <= 10^5
6+
- 30 <= temperatures[i] <= 100
7+
8+
### Examples
9+
```javascript
10+
Input: temperatures = [73,74,75,71,69,72,76,73]
11+
Output: [1,1,4,2,1,1,0,0]
12+
13+
Input: temperatures = [30,40,50,60]
14+
Output: [1,1,1,0]
15+
16+
Input: temperatures = [30,60,90]
17+
Output: [1,1,0]
18+
```
19+
20+
## Approaches to Solve the Problem
21+
### Approach 1: Brute Force (Inefficient)
22+
##### Intuition:
23+
The brute-force approach involves checking every temperature for each day to find the next warmer day. For each day i, scan the remaining days to find the first day where the temperature is higher.
24+
25+
Steps:
26+
1. For each day i, start a nested loop to search through the following days j > i.
27+
2. If a day j is found where temperatures[j] > temperatures[i], calculate the difference j - i and store it in the result.
28+
3. If no warmer day is found, store 0 for that day.
29+
##### Time Complexity:
30+
O(n²), where n is the number of days. For each day, we search all the following days, making this approach inefficient for large inputs.
31+
##### Space Complexity:
32+
O(n) for storing the result array.
33+
##### Python Code:
34+
```python
35+
def dailyTemperatures(temperatures):
36+
n = len(temperatures)
37+
answer = [0] * n
38+
39+
for i in range(n):
40+
for j in range(i + 1, n):
41+
if temperatures[j] > temperatures[i]:
42+
answer[i] = j - i
43+
break
44+
45+
return answer
46+
```
47+
48+
### Approach 2: Stack (Optimal Solution)
49+
##### Intuition:
50+
A more efficient solution can be achieved using a monotonic decreasing stack. The idea is to traverse the array from left to right while maintaining a stack of indices of the temperatures. The stack helps keep track of temperatures that we haven't found a warmer day for yet. When we find a warmer temperature, we calculate the difference in days and store the result.
51+
52+
- The stack stores indices of temperatures in decreasing order.
53+
- When a warmer temperature is found (i.e., temperatures[i] > temperatures[stack[-1]]), we pop the stack, calculate the difference, and store it in the result array.
54+
55+
Steps:
56+
1. Initialize an empty stack and an array answer filled with 0's.
57+
2. Traverse the array from left to right.
58+
3. For each day i, check if the current temperature is higher than the temperature at the index stored in the stack.
59+
- If true, pop the index from the stack, calculate i - stack[-1] as the number of days until a warmer temperature, and update the result.
60+
- Repeat until the stack is empty or the temperature at the top of the stack is higher.
61+
4. Push the current index i onto the stack.
62+
5. Continue until the array is fully processed.
63+
### Visualization
64+
For temperatures = [73, 74, 75, 71, 69, 72, 76, 73]:
65+
66+
```rust
67+
Start with empty stack.
68+
69+
Step 1:
70+
Index 0 (73): No warmer day yetpush 0 onto the stack.
71+
Stack: [0]
72+
73+
Step 2:
74+
Index 1 (74): Warmer than 73pop 0, set answer[0] = 1push 1 onto the stack.
75+
Stack: [1]
76+
77+
Step 3:
78+
Index 2 (75): Warmer than 74pop 1, set answer[1] = 1push 2 onto the stack.
79+
Stack: [2]
80+
81+
...
82+
83+
Final result: [1, 1, 4, 2, 1, 1, 0, 0]
84+
```
85+
##### Time Complexity:
86+
O(n), where n is the number of days. Each index is pushed and popped from the stack at most once.
87+
##### Space Complexity:
88+
O(n), for the stack and the result array.
89+
##### Python Code:
90+
```python
91+
def dailyTemperatures(temperatures):
92+
n = len(temperatures)
93+
answer = [0] * n
94+
stack = []
95+
96+
for i, temp in enumerate(temperatures):
97+
# While the stack is not empty and we have found a warmer temperature
98+
while stack and temperatures[stack[-1]] < temp:
99+
prev_index = stack.pop()
100+
answer[prev_index] = i - prev_index
101+
102+
# Push the current index onto the stack
103+
stack.append(i)
104+
105+
return answer
106+
```
107+
### Edge Cases:
108+
1. Single Element: If the input array has only one element, the result should be [0], since there are no future days.
109+
2. All Decreasing Temperatures: If the temperatures are in strictly decreasing order, the result will be all 0's because there is no warmer day for any of the elements.
110+
3. All Increasing Temperatures: The result will be [1, 1, 1, ..., 0] since each day has the next day as a warmer day.
111+
## Summary
112+
113+
| Approach | Time Complexity | Space Complexity |
114+
|-----------------------------------|-----------------|------------------|
115+
| Brute Force | O(n²) | O(n) |
116+
| Stack (Optimal Solution) | O(n) | O(n) |
117+
118+
The Stack approach is the most efficient solution, leveraging a monotonic stack to keep track of indices and efficiently calculate the number of days until a warmer temperature. This solution runs in linear time and is optimal for the input size constraints.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Largest Rectangle in Histogram
2+
Given an array of integers heights representing the histogram's bar heights where the width of each bar is 1, return the area of the largest rectangle in the histogram.
3+
4+
### Constraints:
5+
- 1 <= heights.length <= 10^5
6+
- 0 <= heights[i] <= 10^4
7+
8+
### Examples
9+
```javascript
10+
Input: heights = [2,1,5,6,2,3]
11+
Output: 10
12+
Explanation: The largest rectangle is formed by the bars of heights [5,6] with width 2, giving area 5 * 2 = 10.
13+
14+
Input: heights = [2,4]
15+
Output: 4
16+
Explanation: The largest rectangle is the single bar of height 4 with width 1, giving area 4 * 1 = 4.
17+
```
18+
19+
## Approaches to Solve the Problem
20+
### Approach 1: Brute Force (Inefficient)
21+
##### Intuition:
22+
For every bar, we can try to expand the rectangle to the left and right until we hit a bar that is shorter. For each bar, calculate the maximum rectangle that can be formed using that bar as the shortest. This involves calculating the width that extends as far as we can to the left and right for each bar.
23+
24+
Steps:
25+
1. For each bar at index i, check how far left and right you can extend while maintaining the rectangle height.
26+
2. For every bar, compute the rectangle area, then return the maximum area found.
27+
##### Time Complexity:
28+
O(n²), where n is the number of bars. For each bar, we iterate left and right to find the largest rectangle.
29+
##### Space Complexity:
30+
O(1), because we only use a few variables to track the maximum area.
31+
##### Python Code:
32+
```python
33+
def largestRectangleArea(heights):
34+
n = len(heights)
35+
max_area = 0
36+
37+
for i in range(n):
38+
height = heights[i]
39+
left = i
40+
right = i
41+
42+
# Extend to the left
43+
while left > 0 and heights[left - 1] >= height:
44+
left -= 1
45+
46+
# Extend to the right
47+
while right < n - 1 and heights[right + 1] >= height:
48+
right += 1
49+
50+
# Calculate area with heights[i] as the smallest bar
51+
max_area = max(max_area, height * (right - left + 1))
52+
53+
return max_area
54+
```
55+
56+
### Approach 2: Stack-based Monotonic Approach (Optimal Solution)
57+
##### Intuition:
58+
The brute-force approach is inefficient because we repeatedly scan for the largest rectangle for each bar. Instead, we can optimize the process using a monotonic stack. The idea is to calculate the next smaller bar on both the left and right of each bar in the histogram, as this helps determine the largest possible rectangle that can be formed using each bar.
59+
60+
A monotonic stack can help efficiently keep track of indices of bars in the histogram. As we iterate through the bars:
61+
- If the current bar is shorter than the bar at the top of the stack, we can calculate the area with the bar at the top as the height.
62+
- We continue popping from the stack until we find a shorter bar or the stack becomes empty.
63+
64+
Steps:
65+
1. Initialize an empty stack to store indices of bars and a variable max_area to track the largest area.
66+
2. Traverse the array of heights.
67+
3. For each bar:
68+
- While the stack is not empty and the current bar is smaller than the bar at the top of the stack:
69+
- Pop the top index and calculate the area of the rectangle with the popped height as the smallest bar.
70+
- Update max_area with the calculated area.
71+
4. After the loop, pop remaining bars from the stack and calculate their areas.
72+
5. Return max_area.
73+
### Visualization
74+
For heights = [2, 1, 5, 6, 2, 3]:
75+
76+
```rust
77+
Stack process:
78+
- Day 0: 2push index 0 to the stack.
79+
- Day 1: 1 is smallerpop index 0 (height 2), area = 2 × 1max_area = 2push index 1.
80+
- Day 2: 5push index 2 to the stack.
81+
- Day 3: 6push index 3 to the stack.
82+
- Day 4: 2 is smallerpop index 3 (height 6), area = 6 × 1max_area = 6pop index 2 (height 5), area = 5 × 2max_area = 10push index 4.
83+
- Day 5: 3push index 5 to the stack.
84+
85+
Final pops calculate the remaining areas.
86+
```
87+
##### Time Complexity:
88+
O(n), where n is the number of bars. Each bar is pushed and popped from the stack at most once.
89+
##### Space Complexity:
90+
O(n), for the stack that stores the indices of bars.
91+
##### Python Code:
92+
```python
93+
def largestRectangleArea(heights):
94+
stack = []
95+
max_area = 0
96+
n = len(heights)
97+
98+
for i in range(n):
99+
while stack and heights[stack[-1]] > heights[i]:
100+
h = heights[stack.pop()]
101+
width = i if not stack else i - stack[-1] - 1
102+
max_area = max(max_area, h * width)
103+
stack.append(i)
104+
105+
# Calculate areas for remaining bars in the stack
106+
while stack:
107+
h = heights[stack.pop()]
108+
width = n if not stack else n - stack[-1] - 1
109+
max_area = max(max_area, h * width)
110+
111+
return max_area
112+
```
113+
### Edge Cases:
114+
1. Empty Array: If heights is empty, the largest rectangle area should be 0.
115+
2. Single Element: If heights has only one bar, the largest rectangle area is the height of that bar.
116+
3. All Bars of the Same Height: If all bars are the same height, the largest rectangle is the entire histogram with an area of height * number_of_bars.
117+
4. Decreasing Heights: If the heights are strictly decreasing, the stack will be emptied at each new bar, resulting in the largest rectangle being the first bar.
118+
## Summary
119+
120+
| Approach | Time Complexity | Space Complexity |
121+
|-----------------------------------|-----------------|------------------|
122+
| Brute Force | O(n²) | O(1) |
123+
| Stack-based Solution (Optimal) | O(n) | O(n) |
124+
125+
The Stack-based Monotonic approach is the most efficient solution. It processes the bars in linear time by leveraging the stack to compute the largest rectangle that includes each bar as the shortest.

python/next-greater-element-1.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Next Greater Element I
2+
You are given two arrays nums1 and nums2 where nums1 is a subset of nums2. For each element in nums1, find the next greater element in nums2. The Next Greater Element of a number x in nums1 is the first greater number to its right in nums2. If it does not exist, return -1 for this number.
3+
4+
### Constraints:
5+
- 1 <= nums1.length <= 1000
6+
- 1 <= nums2.length <= 1000
7+
- 0 <= nums1[i], nums2[i] <= 10^4
8+
- All elements in nums1 and nums2 are unique.
9+
- All the elements of nums1 also appear in nums2.
10+
11+
### Examples
12+
```javascript
13+
Input: nums1 = [4,1,2], nums2 = [1,3,4,2]
14+
Output: [-1,3,-1]
15+
Explanation:
16+
For 4, there is no next greater number in nums2.
17+
For 1, the next greater number is 3.
18+
For 2, there is no next greater number.
19+
20+
Input: nums1 = [2,4], nums2 = [1,2,3,4]
21+
Output: [3,-1]
22+
Explanation:
23+
For 2, the next greater number is 3.
24+
For 4, there is no next greater number.
25+
```
26+
27+
## Approaches to Solve the Problem
28+
### Approach 1: Brute Force
29+
##### Intuition:
30+
The brute-force solution involves iterating through each element in nums1 and searching for the next greater element in nums2. For each element in nums1, we find its position in nums2, then scan to the right to find the next greater element.
31+
32+
Steps:
33+
1. For each element in nums1, find its position in nums2.
34+
2. Start from that position in nums2 and search to the right for the next greater element.
35+
3. If no greater element is found, return -1 for that element.
36+
##### Time Complexity:
37+
O(m * n), where m is the length of nums1 and n is the length of nums2. For each element in nums1, we may have to search through all of nums2.
38+
##### Space Complexity:
39+
O(1), as we only need a few variables to track indices and the current next greater element.
40+
##### Python Code:
41+
```python
42+
def nextGreaterElement(nums1, nums2):
43+
result = []
44+
45+
for num in nums1:
46+
found = False
47+
for i in range(len(nums2)):
48+
if nums2[i] == num:
49+
for j in range(i + 1, len(nums2)):
50+
if nums2[j] > num:
51+
result.append(nums2[j])
52+
found = True
53+
break
54+
if not found:
55+
result.append(-1)
56+
break
57+
58+
return result
59+
```
60+
61+
### Approach 2: Stack with Hash Map (Optimal)
62+
##### Intuition:
63+
The brute-force approach is inefficient because it searches nums2 for the next greater element repeatedly. A more efficient approach is to use a monotonic stack and a hash map. The stack helps us keep track of elements in nums2 for which we haven't yet found the next greater element, and the hash map stores the next greater element for each number.
64+
65+
1. Traverse nums2 from right to left.
66+
2. Use the stack to find the next greater element for each number:
67+
- If the stack is non-empty and the top of the stack is less than or equal to the current number, pop the stack.
68+
- The current number's next greater element is at the top of the stack.
69+
Push the current number onto the stack.
70+
3. After processing nums2, use the hash map to quickly find the next greater element for each number in nums1.
71+
72+
Steps:
73+
1. Traverse nums2 from right to left.
74+
2. Use a stack to maintain the next greater element candidates.
75+
3. Use a hash map to store the next greater element for each number in nums2.
76+
4. For each element in nums1, look up the next greater element from the hash map.
77+
### Visualization
78+
For nums2 = [1, 3, 4, 2]:
79+
```rust
80+
Stack process:
81+
Start from the rightmost element: 2push 2 onto the stack.
82+
Move to 4no elements in the stack are greater than 4push 4 onto the stack.
83+
Move to 3next greater element is 4push 3 onto the stack.
84+
Move to 1next greater element is 3push 1 onto the stack.
85+
86+
Hash map after processing: {2: -1, 4: -1, 3: 4, 1: 3}
87+
```
88+
##### Time Complexity:
89+
O(m + n), where m is the length of nums1 and n is the length of nums2. We traverse nums2 once and process nums1 in constant time using the hash map.
90+
##### Space Complexity:
91+
O(n), where n is the length of nums2. The stack and hash map both use space proportional to the size of nums2.
92+
##### Python Code:
93+
```python
94+
def nextGreaterElement(nums1, nums2):
95+
# Dictionary to store the next greater element for each number in nums2
96+
next_greater = {}
97+
stack = []
98+
99+
# Traverse nums2 from right to left
100+
for num in reversed(nums2):
101+
# Pop elements from the stack until we find a greater element or the stack is empty
102+
while stack and stack[-1] <= num:
103+
stack.pop()
104+
105+
# If the stack is not empty, the top of the stack is the next greater element
106+
next_greater[num] = stack[-1] if stack else -1
107+
108+
# Push the current number onto the stack
109+
stack.append(num)
110+
111+
# Use the dictionary to find the next greater element for each number in nums1
112+
return [next_greater[num] for num in nums1]
113+
```
114+
### Edge Cases:
115+
1. Empty Input: If either nums1 or nums2 is empty, the result should also be an empty list.
116+
2. No Greater Element: If a number in nums2 does not have a greater element to its right, the result should be -1 for that number.
117+
3. Single Element: If nums1 or nums2 has a single element, the result will either be -1 or the only element itself.
118+
## Summary
119+
120+
| Approach | Time Complexity | Space Complexity |
121+
|-----------------------------------|-----------------|------------------|
122+
| Brute Force | O(m * n) | O(1) |
123+
| Stack with Hash Map (Optimal) | O(m + n) | O(n) |
124+
125+
The Stack with Hash Map approach is the most efficient and optimal solution. It uses a monotonic stack to keep track of elements that are waiting for their next greater element and processes the input arrays in linear time.

0 commit comments

Comments
 (0)