1 介绍
本博客用来记录灵神力扣题单之单调栈算法。
2 训练
2.1 单调栈
题目1:739. 每日温度
解题思路:单调栈,下一个更大的数字。
C++代码如下,
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& a) {
//下一个更大的数字
int n = a.size();
vector<int> res(n, 0);
stack<int> stk;
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && a[stk.top()] <= a[i]) {
stk.pop();
}
if (!stk.empty()) {
res[i] = stk.top() - i;
}
stk.push(i);
}
return res;
}
};
python3代码如下,
class Solution:
def dailyTemperatures(self, a: List[int]) -> List[int]:
#下一个更大的数字
n = len(a)
res = [0] * n
stk = []
for i in range(n-1,-1,-1):
while len(stk) > 0 and a[stk[-1]] <= a[i]:
del stk[-1]
if len(stk) > 0:
res[i] = stk[-1] - i
stk.append(i)
return res
题目2:1475. 商品折扣后的最终价格
解题思路:单调栈。
C++代码如下,
class Solution {
public:
vector<int> finalPrices(vector<int>& a) {
//下一个更小的数
int n = a.size();
vector<int> b(n, 0);
stack<int> stk;
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && a[stk.top()] > a[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = a[stk.top()];
}
stk.push(i);
}
vector<int> res(n, 0);
for (int i = 0; i < n; ++i) {
res[i] = a[i] - b[i];
}
return res;
}
};
python3代码如下,
class Solution:
def finalPrices(self, a: List[int]) -> List[int]:
#下一个更小的数
n = len(a)
b = [0] * n
stk = []
for i in range(n-1,-1,-1):
while len(stk) > 0 and a[stk[-1]] > a[i]:
del stk[-1]
if len(stk) > 0:
b[i] = a[stk[-1]]
stk.append(i)
res = [0] * n
for i in range(n):
res[i] = a[i] - b[i]
return res
#另一种解法
class Solution:
def dailyTemperatures(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [0] * n
stk = []
for i in range(n):
while stk and nums[stk[-1]] < nums[i]:
j = stk[-1]
res[j] = i - j #nums[j]的下一个更大的温度是nums[i]
del stk[-1]
stk.append(i)
return res
题目3:496. 下一个更大元素 I
解题思路:单调栈。
C++代码如下,
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
//nums2中下一个更大的元素
int n = nums2.size();
vector<int> a(n, -1);
stack<int> stk;
for (int i = n - 1; i >= 0; --i) {
while (!stk.empty() && nums2[stk.top()] <= nums2[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = nums2[stk.top()];
}
stk.push(i);
}
unordered_map<int, int> map_val_idx;
for (int i = 0; i < n; ++i) {
map_val_idx[nums2[i]] = i;
}
int m = nums1.size();
vector<int> res(m, -1);
for (int i = 0; i < m; ++i) {
int idx = map_val_idx[nums1[i]];
res[i] = a[idx];
}
return res;
}
};
python3代码如下,
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
#nums2中下一个更大的元素
n = len(nums2)
a = [-1] * n
stk = []
for i in range(n-1,-1,-1):
while len(stk) > 0 and nums2[stk[-1]] <= nums2[i]:
del stk[-1]
if len(stk) > 0:
a[i] = nums2[stk[-1]]
stk.append(i)
map_val_idx = collections.defaultdict(int)
for i in range(n):
map_val_idx[nums2[i]] = i
m = len(nums1)
res = [-1] * m
for i in range(m):
idx = map_val_idx[nums1[i]]
res[i] = a[idx]
return res
题目4:503. 下一个更大元素 II
解题思路:单调栈。
C++代码如下,
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
vector<int> a = nums;
a.insert(a.end(), nums.begin(), nums.end());
int n = a.size();
vector<int> res(n, -1);
stack<int> stk;
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && a[stk.top()] <= a[i]) {
stk.pop();
}
if (!stk.empty()) {
res[i] = a[stk.top()];
}
stk.push(i);
}
vector<int> ans;
for (int i = 0; i < n/2; ++i) {
ans.push_back(res[i]);
}
return ans;
}
};
python3代码如下,
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
nums += nums
n = len(nums)
res = [-1] * n
stk = []
for i in range(n-1,-1,-1):
while len(stk) > 0 and nums[stk[-1]] <= nums[i]:
del stk[-1]
if len(stk) > 0:
res[i] = nums[stk[-1]]
stk.append(i)
return res[0:n//2]
解题思路:单调栈。
C++代码如下,
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector<int> nextLargerNodes(ListNode* head) {
vector<int> nums;
ListNode* node = head;
while (node != nullptr) {
nums.push_back(node->val);
node = node->next;
}
int n = nums.size();
vector<int> res(n, 0);
stack<int> stk;
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] <= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
res[i] = nums[stk.top()];
}
stk.push(i);
}
return res;
}
};
python3代码如下,
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def nextLargerNodes(self, head: Optional[ListNode]) -> List[int]:
nums = []
node = head
while node is not None:
nums.append(node.val)
node = node.next
n = len(nums)
res = [0] * n
stk = []
for i in range(n-1,-1,-1):
while len(stk) > 0 and nums[stk[-1]] <= nums[i]:
del stk[-1]
if len(stk) > 0:
res[i] = nums[stk[-1]]
stk.append(i)
return res
题目6:962. 最大宽度坡
解题思路:只求解最大宽度,并不能求解出每个i
对应的最优的j
。这是,先按照值严格单调递减的要求,将下标存入stk
中。然后逆序遍历,更新答案res
。
C++代码如下,
class Solution {
public:
int maxWidthRamp(vector<int>& nums) {
int n = nums.size();
stack<int> stk;
stk.push(0);
for (int i = 1; i < n; ++i) {
if (nums[stk.top()] > nums[i]) {
stk.push(i);
}
}
int res = 0;
for (int j = n-1; j >= 0; --j) {
while (!stk.empty() && nums[stk.top()] <= nums[j]) {
res = max(res, j-stk.top());
stk.pop();
}
}
return res;
}
};
python3代码如下,
class Solution:
def maxWidthRamp(self, nums: List[int]) -> int:
n = len(nums)
stk = [0]
for i in range(1,n):
if len(stk) == 0 or nums[stk[-1]] > nums[i]:
stk.append(i)
res = 0
for j in range(n-1,-1,-1):
while len(stk) > 0 and nums[stk[-1]] <= nums[j]:
res = max(res, j-stk[-1])
del stk[-1]
return res
题目7:853. 车队
解题思路:距离从大到小排序。如果栈为空,或者栈顶时间<当前车辆时间,将当前车辆时间压入栈中,最终返回栈的长度。
C++代码如下,
class Solution {
public:
int carFleet(int target, vector<int>& position, vector<int>& speed) {
vector<pair<int,int>> data;
int n = position.size();
for (int i = 0; i < n; ++i) {
data.push_back(make_pair(position[i],speed[i]));
}
sort(data.begin(), data.end(), [](const pair<int,int>& a, const pair<int,int>& b) {
return a.first > b.first || (a.first == b.first && a.second >= b.second);
});
stack<double> stk;
for (int i = 0; i < n; ++i) {
double t = (1.0 * target - data[i].first) / data[i].second;
if (stk.empty() || stk.top() < t) {
stk.push(t);
}
}
return stk.size();
}
};
python3代码如下,
class Solution:
def carFleet(self, target: int, position: List[int], speed: List[int]) -> int:
data = list(zip(position, speed))
data.sort(reverse=True)
stk = []
for p,s in data:
t = (target - p) / s
if not stk or stk[-1] < t:
stk.append(t)
return len(stk)
题目8:901. 股票价格跨度
解题思路:单调栈。
C++代码如下,
class StockSpanner {
public:
StockSpanner() {
idx = 0;
stk = stack<pair<int,int>>();
}
int next(int price) {
int ans = 1;
while (!stk.empty() && stk.top().first <= price) {
stk.pop();
}
if (!stk.empty()) {
ans = idx - stk.top().second;
} else {
ans = max(ans, idx + 1);
}
stk.push(make_pair(price,idx));
idx += 1;
return ans;
}
private:
int idx = 0;
stack<pair<int,int>> stk;
};
/**
* Your StockSpanner object will be instantiated and called as such:
* StockSpanner* obj = new StockSpanner();
* int param_1 = obj->next(price);
*/
python3代码如下,
class StockSpanner:
def __init__(self):
self.stk = []
self.idx = 0
def next(self, price: int) -> int:
ans = 1
while self.stk and self.stk[-1][0] <= price:
del self.stk[-1]
if self.stk:
ans = self.idx - self.stk[-1][1]
else:
ans = max(ans, self.idx + 1)
self.stk.append([price, self.idx])
self.idx += 1
return ans
# Your StockSpanner object will be instantiated and called as such:
# obj = StockSpanner()
# param_1 = obj.next(price)
题目9:1124. 表现良好的最长时间段
解题思路:首先构建数组s
,s[i+1] = 1 if hours[i] > 8 else -1
。然后,顺序遍历数组s
,初始化递减栈stk
。最后,逆序遍历数组s
,得到最终答案res
。注意,由于数组s
表示前缀和,因此,s[stk.top()] < s[i]
,对应的区间为(stk.top(),i]
,因此长度为i - stk.top()
。
C++代码如下,
class Solution {
public:
int longestWPI(vector<int>& hours) {
//计算s
int n = hours.size();
vector<int> s(n+1, 0);
for (int i = 0; i < n; ++i) {
s[i+1] = s[i] + (hours[i] > 8 ? 1 : -1);
}
//初始化stk
stack<int> stk;
for (int i = 0; i < n+1; ++i) {
while (stk.empty() || s[stk.top()] > s[i]) {
stk.push(i);
}
}
//计算res
int res = 0;
for (int i = n; i >= 0; --i) {
while (!stk.empty() && s[stk.top()] < s[i]) {
res = max(res, i - stk.top()); //s表示"前缀和",故长度为i-j,对应s[j] < s[i]。
stk.pop();
}
}
return res;
}
};
python3代码如下,
class Solution:
def longestWPI(self, hours: List[int]) -> int:
#计算数组s
n = len(hours)
s = [0] * (n+1)
for i in range(n):
s[i+1] = s[i] + (1 if hours[i] > 8 else -1)
#正序遍历数组s,初始化栈stk
stk = []
for i in range(n+1):
while not stk or s[stk[-1]] > s[i]:
stk.append(i)
res = 0
#逆序遍历数组s,计算答案res
for i in range(n,-1,-1):
while stk and s[stk[-1]] < s[i]:
res = max(res, i - stk[-1])
del stk[-1]
return res
题目10:1793. 好子数组的最大分数
解题思路:首先计算数组a
(nums[i]
左侧最近的比它小的数的下标)和数组b
(nums[i]
右侧最近的比它小的数的下标),然后正序遍历数组nums
,如果a[i] < k < b[i]
,则更新答案res = max(res, nums[i] * (b[i] - a[i] - 1))
。
C++代码如下,
class Solution {
public:
int maximumScore(vector<int>& nums, int k) {
int n = nums.size();
//计算数组a
vector<int> a(n, -1); //a[i]表示nums[i]左侧最近的比它小的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = stk.top();
}
stk.push(i);
}
//计算数组b
vector<int> b(n, n); //b[i]表示nums[i]右侧最近的比它小的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = stk.top();
}
stk.push(i);
}
//计算答案
int res = 0;
for (int i = 0; i < n; ++i) {
int left = a[i];
int right = b[i];
if (left < k && k < right) {
res = max(res, nums[i] * (right - left - 1));
}
}
return res;
}
};
python3代码如下,
class Solution:
def maximumScore(self, nums: List[int], k: int) -> int:
n = len(nums)
a = [-1] * n #a[i]表示nums[i]左侧最近的小于它的元素的下标
b = [n] * n #b[i]表示nums[i]右侧最近的小于它的元素的下标
#计算数组a
stk = []
for i in range(n):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
a[i] = stk[-1]
stk.append(i)
#计算数组b
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
b[i] = stk[-1]
stk.append(i)
res = 0
for i in range(n):
left = a[i]
right = b[i]
if left < k < right:
ans = nums[i] * (right - left - 1)
res = max(res, ans)
return res
题目11:456. 132 模式
解题思路:单调栈。如果stk.top() < nums[i]
,则弹出栈顶,直到条件不满足,最后把nums[i]
推入栈中。
C++代码如下,
class Solution {
public:
bool find132pattern(vector<int>& nums) {
int n = nums.size();
if (n < 3) {
return false;
}
int second = -(1e9 + 10);
stack<int> stk;
for (int i = n-1; i >= 0; --i) {
if (nums[i] < second) {
return true;
}
while (!stk.empty() && stk.top() < nums[i]) {
second = stk.top();
stk.pop();
}
stk.push(nums[i]);
}
return false;
}
};
python3代码如下,
class Solution:
def find132pattern(self, nums: List[int]) -> bool:
n = len(nums)
if n < 3:
return False
second = -inf
stk = []
for i in range(n-1,-1,-1):
if nums[i] < second:
return True #i,j,k分别为i,stk[-1],second
while stk and stk[-1] < nums[i]:
second = stk[-1]
del stk[-1]
stk.append(nums[i])
return False
题目12:3113. 边界元素是最大值的子数组数目
解题思路:单调递减栈。
C++代码如下,
class Solution {
public:
long long numberOfSubarrays(vector<int>& nums) {
int n = nums.size();
long long res = n;
stack<pair<int,int>> stk;
stk.push(make_pair(1e9+10,0));
for (auto x : nums) {
while (stk.top().first < x) {
stk.pop();
}
if (stk.top().first == x) {
res += stk.top().second;
auto t = stk.top();
stk.pop();
t.second += 1;
stk.push(t);
} else {
stk.push(make_pair(x,1));
}
}
return res;
}
};
python3代码如下,
class Solution:
def numberOfSubarrays(self, nums: List[int]) -> int:
n = len(nums)
res = n
stk = [[inf, 0]]
for x in nums:
while stk[-1][0] < x:
del stk[-1]
if stk[-1][0] == x:
res += stk[-1][1]
stk[-1][1] += 1
else:
stk.append([x,1])
return res
题目13:2866. 美丽塔 II
解题思路:单调栈。单调递增。最大的累加和。
C++代码如下,
class Solution {
public:
long long maximumSumOfHeights(vector<int>& nums) {
int n = nums.size();
//计算数组a
vector<long long> a(n, 0); //a[i]表示区间[0,i]的最大的累加和
stack<int> stk;
stk.push(-1);
long long curr = 0; //当前累加和
for (int i = 0; i < n; ++i) {
while (stk.size() > 1 && nums[stk.top()] > nums[i]) {
int j = stk.top();
stk.pop();
curr -= (1ll * j - stk.top()) * nums[j];
}
curr += (1ll * i - stk.top()) * nums[i];
a[i] = curr;
stk.push(i);
}
//计算数组b
vector<long long> b(n, 0); //b[i]表示区间[i,n-1]的最大的累加和
stk = stack<int>();
stk.push(n);
curr = 0; //当前累加和
for (int i = n-1; i >= 0; --i) {
while (stk.size() > 1 && nums[stk.top()] > nums[i]) {
int j = stk.top();
stk.pop();
curr -= (1ll * stk.top() - j) * nums[j];
}
curr += (1ll * stk.top() - i) * nums[i];
b[i] = curr;
stk.push(i);
}
//计算答案
long long res = 0;
for (int i = 0; i < n; ++i) {
long long ans = a[i] + b[i] - nums[i];
res = max(res, ans);
}
return res;
}
};
python3代码如下,
class Solution:
def maximumSumOfHeights(self, nums: List[int]) -> int:
n = len(nums)
#计算数组a
a = [0] * n #a[i]表示[0,i]区间内的最大累加和
stk = [-1]
curr = 0 #当前累加和
for i in range(n):
while len(stk) > 1 and nums[stk[-1]] > nums[i]:
j = stk[-1]
del stk[-1]
curr -= (j - stk[-1]) * nums[j]
curr += (i - stk[-1]) * nums[i]
a[i] = curr
stk.append(i)
#计算数组b
b = [0] * n #b[i]表示[i,n-1]区间内的最大累加和
stk = [n]
curr = 0 #当前累加和
for i in range(n-1,-1,-1):
while len(stk) > 1 and nums[stk[-1]] > nums[i]:
j = stk[-1]
del stk[-1]
curr -= (stk[-1] - j) * nums[j]
curr += (stk[-1] - i) * nums[i]
b[i] = curr
stk.append(i)
#计算答案
res = 0
for i in range(n):
ans = a[i] + b[i] - nums[i]
res = max(res, ans)
return res
题目14:1944. 队列中可以看到的人数
解题思路:单调栈。
C++代码如下,
class Solution {
public:
vector<int> canSeePersonsCount(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, 0);
stack<int> stk;
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] < nums[i]) {
res[i] += 1;
stk.pop();
}
if (!stk.empty()) {
res[i] += 1;
}
stk.push(i);
}
return res;
}
};
python3代码如下,
class Solution:
def canSeePersonsCount(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [0] * n
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] < nums[i]: #不会存在数值相等的情况
res[i] += 1
del stk[-1]
if stk:
res[i] += 1
stk.append(i)
return res
题目15:2454. 下一个更大元素 IV
解题思路:两个单调递减栈。
C++代码如下,
class Solution {
public:
vector<int> secondGreaterElement(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, -1);
vector<int> stk1;
vector<int> stk2;
for (int i = 0; i < n; ++i) {
while (stk2.size() > 0 && nums[stk2.back()] < nums[i]) {
int j = stk2.back();
res[j] = nums[i]; //nums[j]的下下个更大的数是nums[i]
stk2.pop_back();
}
int j = stk1.size() - 1;
while (j >= 0 && nums[stk1[j]] < nums[i]) {
j -= 1;
}
if (stk1.size() > 0) {
stk2.insert(stk2.end(), stk1.begin()+j+1, stk1.end());
stk1.erase(stk1.begin()+j+1, stk1.end());
}
stk1.push_back(i);
}
return res;
}
};
python3代码如下,
class Solution:
def secondGreaterElement(self, nums: List[int]) -> List[int]:
n = len(nums)
stk1 = []
stk2 = []
ans = [-1] * n
for i in range(n):
while stk2 and nums[stk2[-1]] < nums[i]:
j = stk2[-1]
ans[j] = nums[i] #nums[j]的下下个更大的元素是nums[i]
del stk2[-1]
j = len(stk1)-1
while j >= 0 and nums[stk1[j]] < nums[i]:
j -= 1
stk2 += stk1[j+1:]
del stk1[j+1:]
stk1.append(i)
return ans
题目16:1130. 叶值的最小代价生成树
解题思路:单调栈。
C++代码如下,
class Solution {
public:
int mctFromLeafValues(vector<int>& arr) {
int n = arr.size();
stack<int> stk;
stk.push(20);
int res = 0;
for (auto x : arr) {
while (stk.top() <= x) {
int y = stk.top();
stk.pop();
res += y * min(stk.top(), x);
}
stk.push(x);
}
while (stk.size() > 2) {
int y = stk.top();
stk.pop();
res += y * stk.top();
}
return res;
}
};
python3代码如下,
class Solution:
def mctFromLeafValues(self, arr: List[int]) -> int:
res = 0
n = len(arr)
stack = [inf]
for x in arr:
while stack[-1] <= x:
y = stack.pop()
res += y * min(stack[-1], x)
stack.append(x)
while len(stack) > 2:
res += stack.pop() * stack[-1]
return res
题目17:2289. 使数组按非递减顺序排列
解题思路:单调递减栈。
C++代码如下,
class Solution {
public:
int totalSteps(vector<int>& nums) {
int res = 0;
stack<pair<int,int>> stk;
for (auto num : nums) {
int ans = 0;
while (!stk.empty() && stk.top().first <= num) {
ans = max(ans, stk.top().second);
stk.pop();
}
ans = stk.empty() ? 0 : ans + 1;
res = max(res, ans);
stk.push(make_pair(num, ans));
}
return res;
}
};
python3代码如下,
class Solution:
def totalSteps(self, nums: List[int]) -> int:
res = 0
stk = []
for num in nums:
ans = 0
while stk and stk[-1][0] <= num:
ans = max(ans, stk[-1][1])
del stk[-1]
ans = ans + 1 if stk else 0
res = max(res, ans)
stk.append([num, ans])
return res
题目18:1776. 车队 II
解题思路:单调栈。
C++代码如下,
class Solution {
public:
vector<double> getCollisionTimes(vector<vector<int>>& cars) {
int n = cars.size();
vector<double> res(n, 0.0);
stack<int> stk;
for (int i = n-1; i >= 0 ;--i) {
while (!stk.empty()) {
if (cars[i][1] <= cars[stk.top()][1]) { //cars[i]追不上cars[stk.top()]
stk.pop();
} else {
if (res[stk.top()] < 0) { //cars[i]能追上cars[stk.top()]
break;
} else {
double d = 1.0 * res[stk.top()] * (cars[i][1] - cars[stk.top()][1]);
if (d > cars[stk.top()][0] - cars[i][0]) { //cars[i]能追上cars[stk.top()]
break;
} else { //cars[i]追不上cars[stk.top()]
stk.pop();
}
}
}
}
if (stk.empty()) {
res[i] = -1; //cars[i]追不上任何车
} else { //cars[i]能追上cars[stk.top()]
double t = (1.0 * cars[stk.top()][0] - cars[i][0]) / (cars[i][1] - cars[stk.top()][1]);
res[i] = t;
}
stk.push(i);
}
return res;
}
};
python3代码如下,
class Solution:
def getCollisionTimes(self, cars: List[List[int]]) -> List[float]:
n = len(cars)
res = [0] * n
stk = []
for i in range(n-1,-1,-1):
while stk:
if cars[stk[-1]][1] >= cars[i][1]: #cars[i]不能追上cars[stk[-1]],追它前面的
del stk[-1]
else:
if res[stk[-1]] < 0: #cars[i]能追上cars[stk[-1]]
break
else:
d = res[stk[-1]] * (cars[i][1] - cars[stk[-1]][1])
if d > cars[stk[-1]][0] - cars[i][0]: #cars[i]能追上cars[stk[-1]]
break
else: #cars[i]不能追上cars[stk[-1]],追它前面的
del stk[-1]
if stk: #cars[i]能追上stk[-1]
t = (cars[stk[-1]][0] - cars[i][0]) / (cars[i][1] - cars[stk[-1]][1])
res[i] = t
else: #cars[i]无车可追
res[i] = -1
stk.append(i)
return res
class Solution:
def getCollisionTimes(self, cars: List[List[int]]) -> List[float]:
n = len(cars)
ans = [0] * n
stk = []
for i in range(n-1,-1,-1):
while stk:
if cars[stk[-1]][1] >= cars[i][1]:
del stk[-1]
else:
if ans[stk[-1]] < 0:
break
d = ans[stk[-1]] * (cars[i][1] - cars[stk[-1]][1])
if d > cars[stk[-1]][0] - cars[i][0]:
break
else:
del stk[-1]
if not stk:
ans[i] = -1
else:
t = (cars[stk[-1]][0] - cars[i][0]) / (cars[i][1] - cars[stk[-1]][1])
ans[i] = t
stk.append(i)
return ans
2.2 矩形面积
题目19:84. 柱状图中最大的矩形
解题思路:两个单调栈。
C++代码如下,
class Solution {
public:
int largestRectangleArea(vector<int>& nums) {
int n = nums.size();
//计算left数组
vector<int> left(n, -1); //left[i]表示nums[i]左边首个比它小的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
left[i] = stk.top();
}
stk.push(i);
}
//计算right数组
vector<int> right(n, n); //right[i]表示nums[i]右边首个比它小的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
right[i] = stk.top();
}
stk.push(i);
}
//计算答案
int res = 0;
for (int i = 0; i < n; ++i) {
int ans = nums[i] * (right[i] - left[i] - 1);
res = max(res, ans);
}
return res;
}
};
python3代码如下,
class Solution:
def largestRectangleArea(self, nums: List[int]) -> int:
n = len(nums)
#计算left
left = [-1] * n #left[i]表示nums[i]左边首个比它小的数的下标
stk = []
for i in range(n):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
left[i] = stk[-1]
stk.append(i)
#计算right
right = [n] * n #right[i]表示nums[i]右边首个比它小的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
right[i] = stk[-1]
stk.append(i)
#计算答案
res = 0
for i in range(n):
ans = nums[i] * (right[i] - left[i] - 1)
#print(f"i = {i}, left[i] = {left[i]}, right[i] = {right[i]}, ans = {ans}.")
res = max(res, ans)
return res
题目20:1793. 好子数组的最大分数
解题思路:两个单调栈。
C++代码如下,
class Solution {
public:
int maximumScore(vector<int>& nums, int k) {
int n = nums.size();
//计算left数组
vector<int> left(n, -1);
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
left[i] = stk.top();
}
stk.push(i);
}
//计算right数组
vector<int> right(n, n);
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
right[i] = stk.top();
}
stk.push(i);
}
//计算res
int res = 0;
for (int i = 0; i < n; ++i) {
if (left[i] < k && k < right[i]) {
int ans = nums[i] * (right[i] - left[i] - 1);
res = max(res, ans);
}
}
return res;
}
};
python3代码如下,
class Solution:
def maximumScore(self, nums: List[int], k: int) -> int:
n = len(nums)
#计算left数组
left = [-1] * n #left[i]表示nums[i]左侧首个比它小的数的下标
stk = []
for i in range(n):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
left[i] = stk[-1]
stk.append(i)
#计算right数组
right = [n] * n #right[i]表示nums[i]右侧首个比它小的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
right[i] = stk[-1]
stk.append(i)
#计算res
res = 0
for i in range(n):
if left[i] < k < right[i]:
ans = nums[i] * (right[i] - left[i] - 1)
res = max(res, ans)
return res
题目21:85. 最大矩形
解题思路:柱状图的最大矩形面积,注意柱状图的构造。单调栈。
C++代码如下,
class Solution {
public:
int f(const vector<int>& nums) {
int n = nums.size();
//计算数组a
vector<int> a(n, -1); //a[i]表示nums[i]左侧最近比它小的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = stk.top();
}
stk.push(i);
}
//计算数组b
vector<int> b(n, n); //b[i]表示nums[i]右侧最近比它小的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = stk.top();
}
stk.push(i);
}
//计算答案
int res = 0;
for (int i = 0; i < n; ++i) {
int ans = nums[i] * (b[i] - a[i] - 1);
res = max(res, ans);
}
return res;
}
int maximalRectangle(vector<vector<char>>& matrix) {
int n = matrix.size();
int m = matrix[0].size();
vector<int> nums(m, 0);
int res = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (matrix[i][j] == '1') {
nums[j] += 1;
} else {
nums[j] = 0;
}
}
int ans = f(nums);
res = max(res, ans);
}
return res;
}
};
python3代码如下,
class Solution:
def f(self, nums):
n = len(nums)
#计算数组a
a = [-1] * n #a[i]表示nums[i]左侧最近的比它小的数的下标
stk = []
for i in range(n):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
a[i] = stk[-1]
stk.append(i)
#计算数组b
b = [n] * n #b[i]表示nums[i]右侧最近的比它小的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
b[i] = stk[-1]
stk.append(i)
#计算答案
res = 0
for i in range(n):
ans = nums[i] * (b[i] - a[i] - 1)
res = max(res, ans)
return res
def maximalRectangle(self, matrix: List[List[str]]) -> int:
n, m = len(matrix), len(matrix[0])
nums = [0] * m
res = 0
for i in range(n):
for j in range(m):
if matrix[i][j] == '1':
nums[j] += 1
else:
nums[j] = 0
#计算柱状图nums中的最大矩形
ans = self.f(nums)
res = max(res, ans)
return res
题目22:1504. 统计全 1 子矩形
解题思路:单调栈。
C++代码如下,
class Solution {
public:
int numSubmat(vector<vector<int>>& mat) {
int n = mat.size();
int m = mat[0].size();
int res = 0;
vector<int> nums(m, 0);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (mat[i][j] == 1) {
nums[j] += 1;
} else {
nums[j] = 0;
}
}
//计算nums对答案的贡献
int curr = 0;
stack<int> stk;
stk.push(-1);
for (int j = 0; j < m; ++j) {
while (stk.size() > 1 && nums[stk.top()] > nums[j]) {
int idx = stk.top();
stk.pop();
curr -= (idx - stk.top()) * nums[idx];
}
curr += (j - stk.top()) * nums[j];
res += curr;
stk.push(j);
}
}
return res;
}
};
python3代码如下,
class Solution:
def numSubmat(self, mat: List[List[int]]) -> int:
n = len(mat)
m = len(mat[0])
res = 0
nums = [0] * m
for i in range(n):
for j in range(m):
if mat[i][j] == 1:
nums[j] += 1
else:
nums[j] = 0
#计算nums数组对答案的贡献
stk = [-1]
curr = 0
for j in range(m):
while len(stk) > 1 and nums[stk[-1]] > nums[j]:
idx = stk.pop()
curr -= (idx - stk[-1]) * nums[idx]
curr += (j - stk[-1]) * nums[j]
res += curr
stk.append(j)
return res
class Solution:
def numSubmat(self, mat: List[List[int]]) -> int:
n = len(mat)
m = len(mat[0])
res = 0
nums = [0] * m
for i in range(n):
for j in range(m):
if mat[i][j] == 1:
nums[j] += 1
else:
nums[j] = 0
#计算数组nums对答案的贡献
stk = [-1]
curr = 0
for j in range(m):
while len(stk) > 1 and nums[stk[-1]] > nums[j]:
idx = stk.pop()
curr -= (idx - stk[-1]) * nums[idx]
curr += (j - stk[-1]) * nums[j]
res += curr
stk.append(j)
return res
题目23:42. 接雨水
解题思路:多种做法。
C++代码如下,
class Solution {
public:
int trap(vector<int>& nums) {
int n = nums.size();
//计算数组a
vector<int> a(n, 0); //a[i]表示区间[0,i]的最大值
a[0] = nums[0];
for (int i = 1; i < n; ++i) {
a[i] = max(a[i-1], nums[i]);
}
//计算数组b
vector<int> b(n, 0); //b[i]表示区间[i,n-1]的最大值
b[n-1] = nums[n-1];
for (int i = n-2; i >= 0; --i) {
b[i] = max(b[i+1], nums[i]);
}
//计算最终答案
int res = 0;
for (int i = 0; i < n; ++i) {
int x = min(a[i], b[i]);
res += x - nums[i];
}
return res;
}
};
python3代码如下,
class Solution:
def trap(self, nums: List[int]) -> int:
n = len(nums)
#计算数组a
a = [0] * n #a[i]表示区间[0,i]的最大值
a[0] = nums[0]
for i in range(1,n):
a[i] = max(a[i-1], nums[i])
#计算数组b
b = [0] * n #a[i]表示区间[i,n-1]的最大值
b[n-1] = nums[n-1]
for i in range(n-2,-1,-1):
b[i] = max(b[i+1], nums[i])
#计算答案
res = 0
for i in range(n):
x = min(a[i], b[i])
res += x - nums[i]
return res
2.3 贡献法
题目24:907. 子数组的最小值之和
解题思路:两个单调栈,避免相同的数重复计算贡献。
C++代码如下,
class Solution {
public:
int sumSubarrayMins(vector<int>& nums) {
int n = nums.size();
//计算数组a
vector<int> a(n, -1); //a[i]表示nums[i]左侧第1个比它小的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = stk.top();
}
stk.push(i);
}
//计算数组b
vector<int> b(n, n); //b[i]表示nums[i]右侧第1个比它小或等于它的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] > nums[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = stk.top();
}
stk.push(i);
}
//计算答案
const int mod = 1e9 + 7;
int res = 0;
for (int i = 0; i < n; ++i) {
int ans = 1ll * nums[i] * (i - a[i]) * (b[i] - i) % mod;
res += ans;
res %= mod;
}
return res;
}
};
python3代码如下,
class Solution:
def sumSubarrayMins(self, nums: List[int]) -> int:
n = len(nums)
#计算数组a
a = [-1] * n #a[i]表示nums[i]的上一个比它小的数的下标
stk = []
for i in range(n):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
a[i] = stk[-1]
stk.append(i)
#计算数组b
b = [n] * n #b[i]表示nums[i]的下一个比它小或等于它的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] > nums[i]:
del stk[-1]
if stk:
b[i] = stk[-1]
stk.append(i)
#计算res
res = 0
for i in range(n):
ans = nums[i] * (i - a[i]) * (b[i] - i)
res += ans
mod = int(1e9 + 7)
res %= mod
return res
题目25:2104. 子数组范围和
解题思路:分别计算最大值和最小值的贡献。单调栈。
C++代码如下,
class Solution {
public:
long long f1(const vector<int>& nums) {
//计算最大值贡献
int n = nums.size();
//计算数组a
vector<int> a(n, -1); //a[i]表示nums[i]左侧比它大的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] <= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = stk.top();
}
stk.push(i);
}
//计算数组b
vector<int> b(n, n); //b[i]表示nums[i]右侧比它大或者等于它的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] < nums[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = stk.top();
}
stk.push(i);
}
//计算答案
long long res = 0;
for (int i = 0; i < n; ++i) {
long long curr = 1ll * nums[i] * (i - a[i]) * (b[i] - i);
res += curr;
}
return res;
}
long long f2(const vector<int>& nums) {
//计算最小值贡献
int n = nums.size();
//计算数组a
vector<int> a(n, -1); //a[i]表示nums[i]左侧比它小的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = stk.top();
}
stk.push(i);
}
//计算数组b
vector<int> b(n, n); //b[i]表示nums[i]右侧比它小或等于它的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] > nums[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = stk.top();
}
stk.push(i);
}
//计算答案
long long res = 0;
for (int i = 0; i < n; ++i) {
long long ans = 1ll * nums[i] * (i - a[i]) * (b[i] - i);
res += ans;
}
return res;
}
long long subArrayRanges(vector<int>& nums) {
long long res = 0;
long long ans1 = f1(nums); //计算最大值的贡献
long long ans2 = f2(nums); //计算最小值的贡献
res = ans1 - ans2;
return res;
}
};
python3代码如下,
class Solution:
def f1(self, nums):
#计算最大值的贡献
res = 0
n = len(nums)
#计算数组a
a = [-1] * n #a[i]表示nums[i]左侧第1个比它大的数的下标
stk = []
for i in range(n):
while stk and nums[stk[-1]] <= nums[i]:
del stk[-1]
if stk:
a[i] = stk[-1]
stk.append(i)
#计算数组b
b = [n] * n #b[i]表示nums[i]右侧第1个比它大或等于它的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] < nums[i]:
del stk[-1]
if stk:
b[i] = stk[-1]
stk.append(i)
#计算贡献
for i in range(n):
res += nums[i] * (i - a[i]) * (b[i] - i)
return res
def f2(self, nums):
#计算最小值的贡献
res = 0
n = len(nums)
#计算数组a
a = [-1] * n #a[i]表示nums[i]左侧比它小的数的下标
stk = []
for i in range(n):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
a[i] = stk[-1]
stk.append(i)
#计算数组b
b = [n] * n #b[i]表示nums[i]右侧比它小或等于它的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] > nums[i]:
del stk[-1]
if stk:
b[i] = stk[-1]
stk.append(i)
#计算贡献
for i in range(n):
res += nums[i] * (i - a[i]) * (b[i] - i)
return res
def subArrayRanges(self, nums: List[int]) -> int:
res = 0
res += self.f1(nums) #最大值的贡献
res -= self.f2(nums) #最小值的贡献
return res
题目26:1856. 子数组最小乘积的最大值
解题思路:单调栈。前缀和。
C++代码如下,
class Solution {
public:
int maxSumMinProduct(vector<int>& nums) {
int n = nums.size();
//计算数组nums中区间[i,j]的累加和s[j+1]-s[i]
vector<long long> s(n+1,0);
for (int i = 0; i < n; ++i) {
s[i+1] = s[i] + nums[i];
}
//计算数组a
vector<int> a(n, -1); //a[i]表示nums[i]左侧第1个比它小的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = stk.top();
}
stk.push(i);
}
//计算数组b
vector<int> b(n, n); //b[i]表示nums[i]右侧第1个比它小的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = stk.top();
}
stk.push(i);
}
//计算答案
long long res = 0;
for (int i = 0; i < n; ++i) {
int left = a[i];
int right = b[i];
//区间(left,right),区间[left+1,right-1]的累加和
long long curr = 1ll * nums[i] * (s[right] - s[left+1]);
res = max(res, curr);
}
const int mod = 1e9 + 7;
res %= mod;
return res;
}
};
python3代码如下,
class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
n = len(nums)
#计算nums中[i,j]区间内的累加和,s[j+1]-s[i]
s = [0] * (n+1)
for i in range(n):
s[i+1] = s[i] + nums[i]
#计算数组a
a = [-1] * n #a[i]表示nums[i]左侧比它小的数的下标
stk = []
for i in range(n):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
a[i] = stk[-1]
stk.append(i)
#计算数组b
b = [n] * n #b[i]表示nums[i]右侧比它小的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
b[i] = stk[-1]
stk.append(i)
#计算答案
res = 0
for i in range(n):
left = a[i]
right = b[i]
#区间(left,right),区间[left+1,right-1]的累加和s[right-1+1]-s[left+1]
curr = nums[i] * (s[right] - s[left+1])
res = max(res, curr)
mod = int(1e9 + 7)
res %= mod
return res
题目27:2818. 操作使得分最大
解题思路:快速幂、单调栈、质数筛法。注意leetcode网站中的C++语言的初始化。
int init =[]() {
//业务部分
return 0;
}();
C++代码如下,
const int N = 1e5 + 10;
int cnt[N] = {0};
int init = []() {
for (int i = 2; i < N; ++i) {
if (cnt[i] == 0) {
for (int j = i; j < N; j += i) {
cnt[j] += 1;
}
}
}
return 0;
}();
class Solution {
public:
long long qmi(int a, int b, int p) {
//计算a^b%p
long long res = 1;
while (b) {
if (b & 1) res = res * a % p;
b >>= 1;
a = 1ll * a * a % p;
}
return res;
}
int maximumScore(vector<int>& nums, int k) {
int n = nums.size();
vector<int> arr(n, 0);
for (int i = 0; i < n; ++i) {
arr[i] = cnt[nums[i]];
}
// for (int i = 0; i < n; ++i) {
// cout << arr[i] << ",";
// }
// cout << endl;
//计算数组a
vector<int> a(n, -1); //a[i]表示arr[i]左侧第1个大于或等于它的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && arr[stk.top()] < arr[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = stk.top();
}
stk.push(i);
}
//计算数组b
vector<int> b(n, n); //b[i]表示arr[i]右侧第1个大于它的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && arr[stk.top()] <= arr[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = stk.top();
}
stk.push(i);
}
//计算贡献
vector<pair<int,long long>> data;
for (int i = 0; i < n; ++i) {
data.push_back(make_pair(nums[i],(1ll * i-a[i])*(b[i]-i)));
}
sort(data.begin(), data.end());
reverse(data.begin(), data.end());
const int mod = 1e9 + 7;
long long res = 1;
for (int i = 0; i < n; ++i) {
int x = data[i].first;
long long y = data[i].second;
//x表示数值,y表示次数
if (k >= y) {
res = res * qmi(x, y, mod) % mod;
k -= y;
} else {
res = res * qmi(x, k, mod) % mod;
k = 0;
}
if (k == 0) {
break;
}
}
return res;
}
};
python3代码如下,
N = int(1e5 + 10)
cnt = [0] * N
for i in range(2,N):
if cnt[i] == 0:
for j in range(i,N,i):
cnt[j] += 1
class Solution:
def qmi(self, a, b, p):
#计算a^b%p
res = 1
while b > 0:
if b & 1:
res = res * a % p
b >>= 1
a = a * a % p
return res
def maximumScore(self, nums: List[int], k: int) -> int:
#计算10^5+10以内的数的不同质因子的个数
n = len(nums)
#cnt = self.f()
arr = [0] * n
for i in range(n):
arr[i] = cnt[nums[i]]
#计算数组a
a = [-1] * n #a[i]表示数arr[i]左侧第1个大于或等于它的数的下标
stk = []
for i in range(n):
while stk and arr[stk[-1]] < arr[i]:
del stk[-1]
if stk:
a[i] = stk[-1]
stk.append(i)
#计算数组b
b = [n] * n #b[i]表示arr[i]右侧第1个大于它的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and arr[stk[-1]] <= arr[i]:
del stk[-1]
if stk:
b[i] = stk[-1]
stk.append(i)
#计算贡献
data = []
for i in range(n):
data.append([nums[i], (i-a[i])*(b[i]-i)])
data.sort(reverse=True)
res = 1
mod = int(1e9 + 7)
for x,y in data:
#x表示数值,y表示次数
if k >= y:
res = res * self.qmi(x, y, mod) % mod
k -= y
else:
res = res *self.qmi(x, k, mod) % mod
k = 0
if k == 0:
break
return res
题目28:2281. 巫师的总力量和
解题思路:以nums[i]
为最小值的区间为[left,right]
且要包含i
,那么可以:遍历区间左端点,遍历区间右端点,区间[l,r]
的累积和为s[r+1]-l
。用公式表示如下,
C++
版本需要注意加法、减法、乘法与模运算的等价性。
C++代码如下,
class Solution {
public:
int totalStrength(vector<int>& nums) {
int n = nums.size();
const int mod = 1e9 + 7;
//数组nums区间[i,j]的前缀和为s[j+1]-s[i]
vector<long long> s(n+1, 0);
for (int i = 0; i < n; ++i) {
s[i+1] = s[i] + nums[i];
}
//数组s区间[i,j]的前缀和为ss[j+1]-ss[i]
vector<long long> ss(n+2,0);
for (int i = 0; i < n+1; ++i) {
ss[i+1] = ss[i] + s[i];
}
//计算数组a
vector<int> a(n, -1); //a[i]表示nums[i]左侧第1个比它小的数的下标
stack<int> stk;
for (int i = 0; i < n; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
if (!stk.empty()) {
a[i] = stk.top();
}
stk.push(i);
}
//计算数组b
vector<int> b(n, n); //b[i]表示nums[i]右侧第1个比它小或等于它的数的下标
stk = stack<int>();
for (int i = n-1; i >= 0; --i) {
while (!stk.empty() && nums[stk.top()] > nums[i]) {
stk.pop();
}
if (!stk.empty()) {
b[i] = stk.top();
}
stk.push(i);
}
//计算贡献
long long res = 0;
for (int i = 0; i < n; ++i) {
int left = a[i] + 1;
int right = b[i] - 1;
long long curr = (1ll*i-left+1) % mod *((ss[right+2]-ss[i+1]) % mod) - (1ll*right-i+1) % mod * ((ss[i+1]-ss[left]) % mod);
curr %= mod;
curr = curr * nums[i] % mod;
res = (res + curr) % mod;
}
res = (res + mod) % mod;
return res;
}
};
python3代码如下,
class Solution:
def totalStrength(self, nums: List[int]) -> int:
n = len(nums)
#计算数组nums在区间[i,j]的累加和s[j+1]-s[i]
s1 = [0] * (n+1) #计算1次前缀和
for i in range(n):
s1[i+1] = s1[i] + nums[i]
s2 = [0] * (n+2) #计算2次前缀和
for i in range(n+1):
s2[i+1] = s2[i] + s1[i]
#计算数组a
a = [-1] * n #a[i]表示数组nums[i]左侧比它小的数的下标
stk = []
for i in range(n):
while stk and nums[stk[-1]] >= nums[i]:
del stk[-1]
if stk:
a[i] = stk[-1]
stk.append(i)
#计算数组b
b = [n] * n #b[i]表示数组nums[i]右侧比它小或等于它的数的下标
stk = []
for i in range(n-1,-1,-1):
while stk and nums[stk[-1]] > nums[i]:
del stk[-1]
if stk:
b[i] = stk[-1]
stk.append(i)
#计算贡献
res = 0
for i in range(n):
left = a[i] + 1
right = b[i] - 1
#nums中的区间[left,right]
curr = nums[i] * ((i-left+1) * (s2[right+2] - s2[i+1]) - (right-i+1)*(s2[i+1]-s2[left]))
res += curr
mod = int(1e9 + 7)
res %= mod
return res
2.4 最小字典序
题目29:402. 移掉 K 位数字
解题思路:字典序最小,单调递增栈。
C++代码如下,
class Solution {
public:
string removeKdigits(string num, int k) {
int n = num.size();
vector<char> stk;
int cnt = 0;
for (auto c : num) {
while (cnt < k && stk.size() > 0 && stk.back() > c) {
stk.pop_back();
cnt += 1;
}
stk.push_back(c);
}
if (cnt < k) {
//还有k-cnt个字符需要删除
for (int i = 0; i < k-cnt; ++i) {
stk.pop_back();
}
}
// cout << "cnt = " << cnt << ", k = " << k << endl;
// for (int i = 0; i < stk.size(); ++i) {
// cout << "i = " << i << ", stk[i] = " << stk[i] << endl;
// }
//去除前导零0
if (stk.size() > 0 && stk[0] == '0') {
int i = 0;
while (i < stk.size() && stk[i] == '0') {
i += 1;
}
vector<char> new_stk;
for (int j = i; j < stk.size(); ++j) {
new_stk.push_back(stk[j]);
}
stk = new_stk; //更新
}
//构造答案
string res = "";
for (auto c : stk) {
res += c;
}
//如果为空字符串"",则返回"0"
if (res == "") {
res = "0";
}
return res;
}
};
python3代码如下,
class Solution:
def removeKdigits(self, num: str, k: int) -> str:
#字典序最小,单调递增栈
n = len(num)
stk = []
cnt = 0
for x in num:
while cnt < k and stk and stk[-1] > x:
del stk[-1]
cnt += 1
stk.append(x)
if cnt < k: #还可以移除k-cnt个数
stk = stk[0:cnt-k]
#去掉前导零
res = "".join(stk)
res = res.lstrip('0')
#如果是空字符,返回"0"
if res == "":
res = "0"
return res
题目30:1673. 找出最具竞争力的子序列
解题思路:字典序最小,单调递增栈。
C++代码如下,
class Solution {
public:
vector<int> mostCompetitive(vector<int>& nums, int k) {
int n = nums.size();
k = n - k; //需要删除k个数
//cout << "n = " << n << ", k = " << k << endl;
vector<int> stk;
int cnt = 0;
for (auto x : nums) {
while (cnt < k && stk.size() > 0 && stk.back() > x) {
stk.pop_back();
cnt += 1;
}
stk.push_back(x);
}
if (cnt < k) {
//还有k-cnt个数需要删除
for (int i = 0; i < k-cnt; ++i) {
stk.pop_back();
}
}
return stk;
}
};
python3代码如下,
class Solution:
def mostCompetitive(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
k = n - k #删除k个元素,使得nums中字典序最小
stk = []
cnt = 0
for x in nums:
while cnt < k and stk and stk[-1] > x:
del stk[-1]
cnt += 1
stk.append(x)
if cnt < k:
#还剩下k-cnt个数需要删除
stk = stk[0:cnt-k]
return stk
题目31:316. 去除重复字母
解题思路:字典序最小,则单调递增。正序遍历字符串s
,如果答案中已经有s[i]
,则跳过。如果res[-1] > s[i] and left[res[-1]] > 0
,则将字符res[-1]
从答案中删除。其中left[res[-1]] > 0
表示第i
位右侧还有字符res[-1]
。
C++代码如下,
class Solution {
public:
string removeDuplicateLetters(string s) {
int n = s.size();
//计算字符个数
unordered_map<char, int> left;
for (auto c : s) left[c] += 1;
//计算答案
vector<char> stk;
set<char> has;
for (auto c : s) {
left[c] -= 1;
if (has.find(c) != has.end()) { //答案中已经存在字符c了,跳过
continue;
}
while (stk.size() > 0 && stk.back() > c && left[stk.back()] > 0) {
//可以删除字符stk.back()
has.erase(stk.back());
stk.pop_back();
}
stk.push_back(c);
has.insert(c);
}
//构造答案
string res = "";
for (auto c : stk) {
res += c;
}
return res;
}
};
python3代码如下,
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
left = collections.Counter(s) #剩余字符的个数
n = len(s)
res = []
has = set() #答案中的字符
for c in s:
left[c] -= 1
if c in has: #答案中已经有字符c,跳过
continue
while res and res[-1] > c and left[res[-1]]:
has.remove(res[-1])
del res[-1]
res.append(c)
has.add(c)
return "".join(res)
题目32:Q3. 整理书架
解题思路:字典序最小,单调递增栈。
C++代码如下,
class Solution {
public:
vector<int> arrangeBookshelf(vector<int>& order, int limit) {
int n = order.size();
unordered_map<int, int> left; //order中第i位右侧,该类书籍有多少个
for (auto x : order) left[x] += 1;
unordered_map<int, int> cnt; //当前选择的书籍中,该类书籍有多少个
vector<int> res; //答案
for (auto x : order) {
left[x] -= 1;
if (cnt[x] == limit) { //答案中已经有limit个该类书籍了,跳过
continue;
}
while (res.size() > 0 && res.back() > x && (cnt[res.back()]-1 + left[res.back()]) >= limit) {
cnt[res.back()] -= 1;
res.pop_back();
}
res.push_back(x);
cnt[x] += 1;
}
return res;
}
};
python3代码如下,
class Solution:
def arrangeBookshelf(self, order: List[int], limit: int) -> List[int]:
n = len(order)
left = collections.Counter(order) #第i位右侧还有多少个该类书籍
cnt = collections.defaultdict(int) #选择的书籍中该类书籍的个数
res = [] #选择的书籍
for x in order:
left[x] -= 1
if cnt[x] == limit: #答案中已经有limit个该类书籍
continue
while res and res[-1] > x and (cnt[res[-1]]-1 + left[res[-1]] >= limit):
cnt[res[-1]] -= 1
del res[-1]
res.append(x)
cnt[x] += 1
return res
class Solution:
def arrangeBookshelf(self, order: List[int], limit: int) -> List[int]:
delete = collections.Counter(order) #需要删除的该类书籍的个数
for x in delete:
delete[x] -= limit
#print(f"delete = {delete}.")
n = len(order)
res = []
has = collections.defaultdict(int) #答案中的该类书籍的个数
for x in order:
#print(f"[before]x = {x}, res = {res}.")
if has[x] == limit: #如果该类书籍的数目已经达到limit,则跳过
delete[x] -= 1
continue
while res and res[-1] > x and delete[res[-1]] > 0:
has[res[-1]] -= 1 #更新has
delete[res[-1]] -= 1 #更新delete
del res[-1]
res.append(x)
has[x] += 1 #更新has
return res
题目33:1081. 不同字符的最小子序列
解题思路:字典序最小,单调递增栈。
C++代码如下,
class Solution {
public:
string smallestSubsequence(string s) {
unordered_map<char, int> left; //字符串s中第i个字符右侧,该类字符有多少个
for (auto c : s) left[c] += 1;
set<char> has; //答案中的字符
vector<char> res;
for (auto c : s) {
left[c] -= 1;
if (has.find(c) != has.end()) { //答案中已经有字符c了,跳过
continue;
}
while (res.size() > 0 && res.back() > c && left[res.back()] > 0) {
has.erase(res.back());
res.pop_back();
}
res.push_back(c);
has.insert(c);
}
string res_s = "";
for (auto c : res) res_s += c;
return res_s;
}
};
python3代码如下,
class Solution:
def smallestSubsequence(self, s: str) -> str:
left = collections.Counter(s) #字符串s中第i个字符右侧,该类字符的数目
has = set()
n = len(s)
res = []
for c in s:
left[c] -= 1
if c in has: #答案中已经有字符c了,跳过
continue
while res and res[-1] > c and left[res[-1]] > 0:
has.remove(res[-1])
del res[-1]
res.append(c)
has.add(c)
return "".join(res)
题目34:321. 拼接最大数
解题思路:从nums1
和nums2
中分别取出arr1
和arr2
,两个子数组的长度之和为k
,遍历所有的长度组合。从nums
中选择长度为k
的最大字典序子序列,可以使用单调递减栈实现。然后合并arr1
和arr2
,合并时需要定义一个compare(arr1, i, arr2, j)
函数。
已知,
故,
C++代码如,
class Solution {
public:
vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) {
int n = nums1.size();
int m = nums2.size();
int left = max(0, k-m);
int right = min(k, n);
vector<int> res(k, 0);
for (int a = left; a < right+1; ++a) {
vector<int> arr1 = get_max_sub(nums1, a);
vector<int> arr2 = get_max_sub(nums2, k-a);
vector<int> curr = merge(arr1, arr2);
if (compare(curr, 0, res, 0) > 0) {
res = curr;
}
}
return res;
}
vector<int> get_max_sub(const vector<int>& nums, int k) {
//cout << "===start get_max_sub" << endl;
//cout << "nums = ";
//for (auto x : nums) cout << x << ",";
//cout << endl;
//cout << "k = " << k << endl;
vector<int> res;
int n = nums.size();
for (int i = 0; i < n; ++i) {
while (res.size() > 0 && res.back() < nums[i] && (res.size()-1 + n-i) >= k) {
res.pop_back();
}
res.push_back(nums[i]);
}
res.erase(res.begin() + k, res.end());
//cout << "res = ";
//for (auto x : res) cout << x << ",";
//cout << endl;
//cout << "==end get_max_sub" << endl;
return res;
}
vector<int> merge(const vector<int>& nums1, const vector<int>& nums2) {
// cout << "===before merge" << endl;
// cout << "nums1: ";
// for (auto x : nums1) cout << x << ",";
// cout << endl;
// cout << "nums2: ";
// for (auto x : nums2) cout << x << ",";
// cout << endl;
int n = nums1.size();
int m = nums2.size();
int i = 0;
int j = 0;
vector<int> res;
while (i < n || j < m) {
if (compare(nums1, i, nums2, j) > 0) {
res.push_back(nums1[i]);
i += 1;
} else {
res.push_back(nums2[j]);
j += 1;
}
}
// cout << "res: ";
// for (auto x : res) cout << x << ",";
// cout << endl;
// cout << "===end merge" << endl;
return res;
}
int compare(const vector<int>& nums1, int i, const vector<int>& nums2, int j) { //注意返回值类型为int
int n = nums1.size();
int m = nums2.size();
while (i < n && j < m) {
int difference = nums1[i] - nums2[j];
if (difference != 0) {
return difference;
} else {
i += 1;
j += 1;
}
}
return (n-i)-(m-j) > 0;
}
};
python3代码如下,
class Solution:
def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
n, m = len(nums1), len(nums2)
left = max(0, k-m)
right = min(k, n)
res = [0] * k #最佳答案
for a in range(left, right+1):
arr1 = self.get_max_sub(nums1, a)
arr2 = self.get_max_sub(nums2, k-a)
curr = self.merge(arr1, arr2)
if self.compare(curr, 0, res, 0) > 0:
res = curr
return res
def get_max_sub(self, nums: List[int], k: int) -> List[int]:
res = []
n = len(nums)
for i,x in enumerate(nums):
while res and res[-1] < x and (n-i+len(res)-1) >= k:
del res[-1]
res.append(x)
return res[0:k]
def merge(self, nums1: List[int], nums2: List[int]) -> List[int]:
n, m = len(nums1), len(nums2)
i = j = 0
res = []
while i < n or j < m:
if self.compare(nums1, i, nums2, j) > 0:
res.append(nums1[i])
i += 1
else:
res.append(nums2[j])
j += 1
return res
def compare(self, nums1: List[int], i: int, nums2: List[int], j: int) -> bool:
n, m = len(nums1), len(nums2)
while i < n and j < m:
difference = nums1[i] - nums2[j]
if difference != 0:
return difference
else:
i += 1
j += 1
return (n-i) - (m-j)
class Solution:
def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
m, n = len(nums1), len(nums2)
maxSubsequence = [0] * k
start, end = max(0, k-n), min(k, m)
for i in range(start, end+1):
subsequence1 = self.getMaxSubsequence(nums1, i)
subsequence2 = self.getMaxSubsequence(nums2, k-i)
curMaxSubsequence = self.merge(subsequence1, subsequence2)
if self.compare(curMaxSubsequence, 0, maxSubsequence, 0) > 0:
maxSubsequence = curMaxSubsequence
return maxSubsequence
def getMaxSubsequence(self, nums: List[int], k: int) -> int:
stack = [0] * k
top = -1
remain = len(nums) - k
for i, num in enumerate(nums):
while top >= 0 and stack[top] < num and remain > 0:
top -= 1
remain -= 1
if top < k-1:
top += 1
stack[top] = num
else:
remain -= 1
return stack
def merge(self, subsequence1: List[int], subsequence2: List[int]) -> List[int]:
x, y = len(subsequence1), len(subsequence2)
if x == 0:
return subsequence2
if y == 0:
return subsequence1
mergeLength = x + y
merged = list()
index1 = index2 = 0
for _ in range(mergeLength):
if self.compare(subsequence1, index1, subsequence2, index2) > 0:
merged.append(subsequence1[index1])
index1 += 1
else:
merged.append(subsequence2[index2])
index2 += 1
return merged
def compare(self, subsequence1: List[int], index1: int, subsequence2: List[int], index2: int) -> int:
x, y = len(subsequence1), len(subsequence2)
while index1 < x and index2 < y:
difference = subsequence1[index1] - subsequence2[index2]
if difference != 0:
return difference
index1 += 1
index2 += 1
return (x-index1) - (y-index2)
题目35:2030. 含特定字母的最小子序列
解题思路:题目要求是选择k
个字符,满足字典序尽量小且字符letter
的出现次数大于等于repetition
。分成两步走。(1)选择至少k
个字符,满足字典序尽量小且字符letter
的出现次数大于等于repetition
。(2)删除len(res)-k
个字符,同时满足字典序尽量小且字符letter
的出现次数大于等于repetition
。
C++代码如下,
class Solution {
public:
string smallestSubsequence(string s, int k, char letter, int repetition) {
int n = s.size();
unordered_map<char, int> left; //字符串s中[i,n)区间内该字符的出现次数
for (auto c : s) left[c] += 1;
unordered_map<char, int> cnt; //答案中该字符的出现次数
vector<char> res;
for (int i = 0; i < n; ++i) {
while (res.size() > 0 && res.back() > s[i] && res.size()-1+n-i >= k) {
if ((res.back() != letter) || (res.back() == letter && cnt[letter]-1+left[letter] >= repetition)) {
cnt[res.back()] -= 1;
res.pop_back();
} else {
break;
}
}
res.push_back(s[i]);
cnt[s[i]] += 1;
left[s[i]] -= 1; //更新left
}
if (res.size()-k > 0) {
//删除多余元素res.size()-k
int need_delete_cnt = res.size() - k; //需要删除的元素的个数
vector<char> new_res;
for (int i = res.size()-1; i >= 0; --i) {
if (res[i] == letter) {
if (need_delete_cnt > 0 && cnt[letter]-1 >= repetition) {
need_delete_cnt -= 1;
cnt[letter] -= 1;
continue; //删除res[i]
}
} else {
if (need_delete_cnt > 0) {
need_delete_cnt -= 1;
continue; //删除res[i]
}
}
new_res.push_back(res[i]);
}
reverse(new_res.begin(), new_res.end());
res = new_res; //更新res
}
//构造答案
string res_s = "";
for (auto c : res) res_s += c;
return res_s;
}
};
python3代码如下,
class Solution:
def smallestSubsequence(self, s: str, k: int, letter: str, repetition: int) -> str:
#从s中选择k个字符==>从s中选择至少k个字符
#保证这k个字符中,字符letter的出现次数>=repetition
#保证字典序尽量小
n = len(s)
res = []
left = collections.Counter(s) #left[i]表示字符串s中[i,n)该类字符有多少个
cnt = collections.defaultdict(int) #答案中该类字符的个数
for i,c in enumerate(s):
while res and res[-1] > c and (len(res)-1+n-i) >= k:
if res[-1] != letter or (res[-1] == letter and cnt[letter]-1 + left[letter] >= repetition):
cnt[res[-1]] -= 1
del res[-1]
else: #不能删除res[-1]
break
res.append(c)
cnt[c] += 1
left[c] -= 1 #更新left
if len(res) > k:
#需要删除len(res)-k个数
need_delete_cnt = len(res) - k
new_res = []
for i in range(len(res)-1,-1,-1):
if res[i] == letter:
#todo
if cnt[letter]-1 >= repetition and need_delete_cnt > 0:
cnt[letter] -= 1
need_delete_cnt -= 1
continue #res[i]不加入到new_res当中
else:
#todo
if need_delete_cnt > 0:
need_delete_cnt -= 1
continue #res[i]不加入到new_res当中
new_res.append(res[i])
res = new_res[::-1] #更新res
#构造答案
res_s = "".join(res)
return res_s