leetcode(力扣)刷题笔记(c++)【下】

leetcode(力扣)刷题笔记(c++、python)【上】
leetcode(力扣)刷题笔记(c++)【中】

文章预览:

  • 单调栈
    • 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;
    }
};

leetcode(力扣)刷题笔记(c++)【下】_第1张图片法二:动态规划

当前列雨水面积: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;
    }
};

你可能感兴趣的:(c++,leetcode,c++)