文章预览:
- 单调栈
- 739. 每日温度
- 496.下一个更大元素 I
- 503. 下一个更大元素 II
- 42. 接雨水
- 84.柱状图中最大的矩形
- 额外题目
- 1365.有多少小于当前数字的数字
- 941. 有效的山脉数组
- 1207. 独一无二的出现次数
- 189. 轮转数组
- 724. 寻找数组的中心下标
- 922. 按奇偶排序数组 II
- 后续刷题记录
- 21 合并有序链表
参考刷题链接代码随想录
单调栈
739. 每日温度
法一:暴力
超出时间限制
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> result(temperatures.size(),0);
for(int i=0;i<temperatures.size();i++){
for(int j=i+1;j<temperatures.size();j++){
if(temperatures[j]>temperatures[i]){
result[i]=j-i;
break;
}
}
}
return result;
}
};
法二:单调栈
栈必须先判断是否为空再取栈顶元素
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
// 递增栈 栈内放元素下标,因为要计算距离
stack<int> st;
vector<int> result(temperatures.size(), 0);
st.push(0);//先放入第一个元素
for(int i=1;i<temperatures.size();i++){
if(temperatures[i]<=temperatures[st.top()]){
st.push(i);
}
else{
while(!st.empty()&&temperatures[i]>temperatures[st.top()]){//判断空必须放在前面
result[st.top()]=i-st.top();
st.pop();
}
st.push(i);
}
}
return result;
}
};
496.下一个更大元素 I
法一:单调栈
对nums2使用单调栈,就可以获得nums2中每个元素的下一个更大元素,在循环里面判断nums2[i]是否出现在nums1中,因为nums1是nums2的子集。
写法一:使用unordered_map记录nums1的元素值和下标
使用umap是便于查找
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> result(nums1.size(),-1);
stack<int> st;//存放nums的元素值
unordered_map<int, int> umap;
for(int i=0;i<nums1.size();i++){
umap[nums1[i]]=i;//key为元素值,value为下标
}
st.push(nums2[0]);
for(int i=1;i<nums2.size();i++){
while(!st.empty()&&nums2[i]>st.top()){
if(umap.find(st.top())!=umap.end()){//说明在nums1中找到了
result[umap[st.top()]]=nums2[i];
}
st.pop();
}
st.push(nums2[i]);
}
return result;
}
};
写法二:直接利用find函数对nums1进行查找
迭代器之间相减就可以获得距离
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> result(nums1.size(),-1);
stack<int> st;//存放nums的元素值
st.push(nums2[0]);
for(int i=1;i<nums2.size();i++){
while(!st.empty()&&nums2[i]>st.top()){
auto it=find(nums1.begin(),nums1.end(),st.top());
if(it!=nums1.end()){//说明在nums1中找到了
result[it-nums1.begin()]=nums2[i];
}
st.pop();
}
st.push(nums2[i]);
}
return result;
}
};
503. 下一个更大元素 II
法一:单调栈
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
//使用单调栈
stack<int> st;
vector<int> new_nums;
//拼接成一个新数组
for(int i=0;i<nums.size();i++){
new_nums.push_back(nums[i]);
}
for(int i=0;i<nums.size();i++){
new_nums.push_back(nums[i]);
}
vector<int> result(new_nums.size(),-1);
st.push(0);
for(int i=1;i<new_nums.size();i++){
while(!st.empty()&&new_nums[i]>new_nums[st.top()]){
result[st.top()]=new_nums[i];
st.pop();
}
st.push(i);
}
result.resize(new_nums.size()/2);
return result;
}
};
42. 接雨水
法一:双指针。时间复杂度为O(n^2)。 空间复杂度为O(1)。按列查找,找到左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。
超出时间限制
class Solution {
public:
int trap(vector<int>& height) {
//使用双指针
int result=0;
for(int i=0;i<height.size();i++){
if(i==0||i==height.size()-1) continue;//第一个和最后一个不接水
int right_maxh=0;
for(int j=i+1;j<height.size();j++){
//找右边最大值
if(height[j]>right_maxh) right_maxh=height[j];
}
int left_maxh=0;
for(int j=0;j<i;j++){
//找i左边最高值
if(height[j]>left_maxh) left_maxh=height[j];
}
int h=min(left_maxh,right_maxh)-height[i];
if(h > 0) result+=h;
}
return result;
}
};
法二:动态规划
当前列雨水面积:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度。
为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边(包含当前位置)最高高度记录在一个数组上(maxLeft),右边(包含当前位置)最高高度记录在一个数组上(maxRight)。这样就避免了重复计算,这就用到了动态规划。
包含当前位置的原因:如果最高高度和当前位置高度一样,那就说明不能装水,算出来面积是0,不影响后面的计算,不需要再判断大小
class Solution {
public:
int trap(vector<int>& height) {
//使用动态规划计算每个位置左边和右边最高柱子高度
if (height.size() <= 2) return 0;
vector<int> maxLeft(height.size(), 0);//maxLeft[i]:i位置前(包括i)的最高柱子
vector<int> maxRight(height.size(), 0);//maxRight[i]:i位置后(包括i)的最高柱子
maxLeft[0]=height[0];
int size=height.size();
for(int i=1;i<size;i++){
maxLeft[i]=max(height[i],maxLeft[i-1]);
}
maxRight[size-1]=height[size-1];
for(int i=size-2;i>=0;i--){
maxRight[i]=max(height[i],maxRight[i+1]);
}
int result=0;
for(int i=0;i<size;i++){
int h=min(maxLeft[i],maxRight[i])-height[i];
result+=h;
}
return result;
}
};
法三:单调栈
由于是横向计算,所以需要跳过高度相同的柱子,不然计算面积时会重复
class Solution {
public:
int trap(vector<int>& height) {
if (height.size() <= 2) return 0; // 可以不加
stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
int sum = 0;
for (int i = 1; i < height.size(); i++) {
if (height[i] < height[st.top()]) { // 情况一
st.push(i);
}
else if (height[i] == height[st.top()]) { // 情况二
st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
st.push(i);
} else { // 情况三
while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
return sum;
}
};
不跳过相同高度的柱子也可以通过,因为h = min(height[st.top()], height[i]) - height[mid]对相同高度柱子的右边的柱子算出来是0,因为该柱子左边的柱子和自己高度相同
class Solution {
public:
int trap(vector<int>& height) {
if (height.size() <= 2) return 0; // 可以不加
stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
int sum = 0;
for (int i = 1; i < height.size(); i++) {
if (height[i] <= height[st.top()]) { // 情况一
st.push(i);
}
// else if (height[i] == height[st.top()]) { // 情况二
// st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
// st.push(i);
// }
else { // 情况三
while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
return sum;
}
};
84.柱状图中最大的矩形
踩坑想法:刚开始想的是找到最高柱子,再从最高柱子两边延展,但是最大矩形不一定包含了最高的柱子
思路分析:
我们可以遍历每根柱子,以当前柱子 i 的高度作为矩形的高,那么矩形的宽度边界即为向左找到第一个高度小于当前柱体 i 的柱体,向右找到第一个高度小于当前柱体 i 的柱体。
对于每个柱子我们都如上计算一遍以当前柱子作为高的矩形面积,最终比较出最大的矩形面积即可。
法一:动态规划
使用两个数组分别记录当前i为高时,矩阵的边界,这样就可以算出矩形的宽
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int size=heights.size();
vector<int> left(size,0);//左边第一个高度小于当前柱体 i 的柱体的下标
vector<int> right(size,0);//右边第一个高度小于当前柱体 i 的柱体的下标
left[0]=-1;//防止while死循环
for(int i=1;i<size;i++){//向左查找
int t=i-1;
while(t>=0&&heights[i]<=heights[t]){//这里有等号
t=left[t];//这里并不是通过t--来找,t--这样比较费时
}
left[i]=t;
}
right[size-1]=size;//防止while死循环
for(int i=size-2;i>=0;i--){//向右查找
int t=i+1;
while(t<size&&heights[i]<=heights[t]){
t=right[t];
}
//如果右边没有比当前位置小的,那right[i]=size
//也就是当前位置作为高时,右边的所有柱子都可以和i组成矩形
right[i]=t;
}
int result=0;
for(int i=0;i<size;i++){
int sum=right[i]-left[i]-1;//矩形的宽
result=max(sum*heights[i],result);
}
return result;
}
};
法二:单调栈
栈底到栈顶为递增顺序,当进入while循环时,栈顶的下一个元素就是左边第一个小于i的高度,栈头就是右边的第一个高度
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
//单调栈,栈底到栈顶为递增顺序
heights.insert(heights.begin(), 0); // 数组头部加入元素0
heights.push_back(0); // 数组尾部加入元素0
int size=heights.size();
stack<int> st;
st.push(0);
int result=0;
for(int i=1;i<size;i++){
if(heights[i]>=heights[st.top()]){
st.push(i);
}
else{
while(!st.empty() && heights[i]<heights[st.top()]){
int mid=st.top();
st.pop();
int w=i-st.top()-1;
result=max(result,w*heights[mid]);
}
st.push(i);
}
}
return result;
}
};
额外题目
1365.有多少小于当前数字的数字
法一:暴力
class Solution {
public:
vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
vector<int> result(nums.size(),0);
for(int i=0;i<nums.size();i++){
for(int j=0;j<nums.size();j++){
if(j==i) continue;
if(nums[j]<nums[i]) result[i]++;
}
}
return result;
}
};
941. 有效的山脉数组
法一:先找到arr最大值,再判断两边情况
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
if(arr.size()<3) return false;
int max_num=0;
int index=0;
for(int i=0;i<arr.size();i++){
if(max_num<arr[i]){
max_num=arr[i];
index=i;
}
}
if(index==0||index==arr.size()-1) return false;
for(int i=0;i<index;i++){
if(arr[i]>=arr[i+1]) return false;
}
for(int i=index;i<arr.size()-1;i++){
if(arr[i]<=arr[i+1]) return false;
}
return true;
}
};
法二:双指针
从左到右找到严格递增的最高点,从右到左找到严格递增的最高点,再比较两者位置是否相等
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
int left=0;
int right=arr.size()-1;
while(left<arr.size()-1&&arr[left]<arr[left+1]){
left++;
}
while(right>0&&arr[right]<arr[right-1]){
right--;
}
if(left==right&&left!=0&&right!=arr.size()-1) return true;
return false;
}
};
1207. 独一无二的出现次数
class Solution {
public:
bool uniqueOccurrences(vector<int>& arr) {
unordered_map<int,int> m;
for(int i=0;i<arr.size();i++){//统计出现次数
m[arr[i]]++;
}
map<int,int> count_m;//key不允许有重复的
for(auto it=m.begin();it!=m.end();it++){
count_m[it->second]++;
}
if(m.size()!=count_m.size()) return false;
return true;
}
};
189. 轮转数组
法一:设一个新数组,先取后面的k个数字,再取前面的nums.size()-k个数字,但是需要注意如果k>nums.size()时,需要对k进行处理
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if(nums.size()<k) k=k-k/nums.size()*nums.size();
vector<int> result;
for(int i=nums.size()-k;i<nums.size();i++){
result.push_back(nums[i]);
}
for(int i=0;i<nums.size()-k;i++){
result.push_back(nums[i]);
}
nums=result;
}
};
法二:使用空间复杂度为 O(1) 的 原地 算法
注意reverse是前闭后开区间
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if(nums.size()<k) k=k-k/nums.size()*nums.size();
reverse(nums.begin(),nums.end());
reverse(nums.begin(),nums.begin()+k);
reverse(nums.begin()+k,nums.end());
}
};
724. 寻找数组的中心下标
1.遍历一遍求出总和sum
2.遍历第二遍求中心索引左半和leftSum
判断sum-nums[i]=2*k?
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int sum=0;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
}
int k=0;
for(int i=0;i<nums.size();i++){
if(i==0||(i==nums.size()-1)){
if(sum-nums[i]==0)
return i;
}
else{
k+=nums[i-1];
if(sum-nums[i]==2*k) return i;
}
}
return -1;
}
};
922. 按奇偶排序数组 II
法一:不适用额外空间
第一层while遍历每个元素,第二个while相当于还是遍历每个元素,时间复杂度O(n^2)
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
//使用双指针
int slow=0;
int fast=0;
while(slow<nums.size()){
if(nums[slow]%2==1 && slow%2==0){//只对这种情况进行交换
fast=0;
while(!(nums[fast]%2==0&&fast%2==1)){
fast++;
if(fast>=nums.size()) break;
}
if(fast>=nums.size()) break;
//交换
int temp=nums[slow];
nums[slow++]=nums[fast];
nums[fast]=temp;
}
slow++;
}
return nums;
}
};
优化写法:其实和前面的思路一致。
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
int index=1;//奇数位
for(int i=0;i<nums.size();i+=2){//遍历偶数位
if(nums[i]%2==1){
while(nums[index]%2!=0) index+=2;
swap(nums[index],nums[i]);
}
}
return nums;
}
};
每次while循环fast不一定从头开始寻找,因为前面需要更换的已经换过了,再读取一遍造成浪费
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
//使用双指针
int slow=0;
int fast=1;
while(slow<nums.size()){
if(nums[slow]%2==1 ){//只对这种情况进行交换
while(nums[fast]%2!=0){
fast+=2;
if(fast>=nums.size()) break;
}
if(fast>=nums.size()) break;
//交换
swap(nums[slow],nums[fast]);
}
slow+=2;
}
return nums;
}
};
后续刷题记录
21 合并有序链表
/**
* 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:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* virtul_head=new ListNode(0);//虚拟头节点
ListNode* temp=virtul_head;
while(list1&&list2){
//将两个链表中较小的节点加到新链表的下一个
//类似于双指针的思想,list1和list2指向两个链表中还未加入新链表的第一个节点
if(list1->val<list2->val){
temp->next=list1;
list1=list1->next;
}
else{
temp->next=list2;
list2=list2->next;
}
temp=temp->next;//temp指向新链表的最后一个节点
// temp->next=NULL;
}
if(list1!=NULL){
temp->next=list1;
}
if(list2!=NULL){
temp->next=list2;
}
return virtul_head->next;
}
};