一周代码集合(Week 1)

# 前言 终于再次开启了 一周代码总结这个系列 这一次的目标就是 冲击ACM 拿铜牌 希望一年后的结果可以让自己满意

LeetCode每日一题

901. 股票价格跨度

设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度 。

当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

  • 例如,如果未来 7 天股票的价格是 [100,80,60,70,60,75,85],那么股票跨度将是 [1,1,1,2,1,4,6] 。

实现 StockSpanner 类:

  • StockSpanner() 初始化类对象。
  • int next(int price) 给出今天的股价 price ,返回该股票当日价格的 跨度 。

# 做题思路和 优化思路

根据问题 可以有一个基本思路: 利用数组来做 每一次price来询问的时候 可以从上一个数开始 一个一个的读取 是否小于等于当前的 price 循环停止条件:到第一个数字 或者 当前数字大于price 

可以看出来 上述代码的效率很低 当数据量大的时候容易超出时间限制 所以我们需要优化思路 

数组并不需要一个一个读数 我们可以利用哈希表来存储每个位置的信息:当前数字之前有多少小于等于自己的 当我们有查询需求的时候 每一次都和一个数字对比 如果当前数字大于price 就终止循环 如果小于等于price 就说明 至少还有 countMap[当前数字] 个连续数字 小于price 于是 res可以直接加上 countMap[当前数字] 并且 下标可以跳到 idx-countMap[当前数字] 和下一个数字继续比较 直到终止循环

# coding

class StockSpanner {
    vector<int> arr;
    int size;
public:
    StockSpanner() {
        arr.clear();
        size=0;
    }
    
    int next(int price) {
        int res=1;
        if(!arr.empty())
        {
            int idx=size-1;
            while(idx>=0 && arr[idx]<=price) 
            {
                res++;
                idx--;
            } 
        }
        arr.push_back(price);
        size++;
        return res;
    }
};



//哈希表优化
class StockSpanner {
    unordered_map<int,int> countMap;
    vector<int> arr;
    int size=0;
    int lastNum;
public:
    StockSpanner() {
        countMap.clear();
        arr.clear();
    }
    
    int next(int price) {
        if(size==0) 
        {
            arr.push_back(price);
            countMap[0]=1;
            size++;
            lastNum=price;
            return 1;
        }
        if(price<lastNum)
        {
            arr.push_back(price);
            countMap[size]=1;
            lastNum=price;
            size++;
            return 1;
        }
        int res=1,idx=size-1;
        while(idx>=0 && price>=arr[idx])
        {
            res+=countMap[idx];
            idx-=countMap[idx];
        }
        lastNum=price;
        countMap[size]=res;
        arr.push_back(price);
        size++;
        return res;
    }
};

/**
 * Your StockSpanner object will be instantiated and called as such:
 * StockSpanner* obj = new StockSpanner();
 * int param_1 = obj->next(price);
 */

/**
 * Your StockSpanner object will be instantiated and called as such:
 * StockSpanner* obj = new StockSpanner();
 * int param_1 = obj->next(price);
 */

 2034. 股票价格波动

给你一支股票价格的数据流。数据流中每一条记录包含一个 时间戳 和该时间点股票对应的 价格 。

不巧的是,由于股票市场内在的波动性,股票价格记录可能不是按时间顺序到来的。某些情况下,有的记录可能是错的。如果两个有相同时间戳的记录出现在数据流中,前一条记录视为错误记录,后出现的记录 更正 前一条错误的记录。

请你设计一个算法,实现:

  • 更新 股票在某一时间戳的股票价格,如果有之前同一时间戳的价格,这一操作将 更正 之前的错误价格。
  • 找到当前记录里 最新股票价格 。最新股票价格 定义为时间戳最晚的股票价格。
  • 找到当前记录里股票的 最高价格 。
  • 找到当前记录里股票的 最低价格 。

    请你实现 StockPrice 类:

  • StockPrice() 初始化对象,当前无股票价格记录。
  • void update(int timestamp, int price) 在时间点 timestamp 更新股票价格为 price 。
  • int current() 返回股票 最新价格 。
  • int maximum() 返回股票 最高价格 。
  • int minimum() 返回股票 最低价格 。

 #做题思路 和 优化思路

利用大小根堆来优化时间 在O(N*logN) 的时间复杂度情况下就可以实现最大小值的排序 每次遇到update 就把时间戳和价格包装成pair放入大小根堆 同时利用哈希表来记录最新时间戳的数值 查询最大最小值的时候 可以通过哈希表来查询 当前位置的值是否正确 如果不正确 就把他删掉 正确就返回他

#coding


class StockPrice {
//大小根堆 第二个是小根堆
  priority_queue<pair<int,int>,vector<pair<int,int>>> maxQ;
  priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> minQ;
  unordered_map<int,int> umap;
  int curPrice,curTime=0;
public:
  StockPrice() {
      umap.clear();
  }
  
  void update(int timestamp, int price) {
      maxQ.push(make_pair(price,timestamp));
      minQ.push(make_pair(price,timestamp));
      umap[timestamp]=price;
      if(timestamp>=curTime) 
      {
          curPrice=price;
          curTime=timestamp;
      }
  }
  
  int current() {
      return curPrice;
  }
  
  int maximum() {
      cout<<maxQ.top().first<<endl;
      while(!maxQ.empty())
      {
          int price=maxQ.top().first,time=maxQ.top().second;
          if(price==umap[time]) break;
          maxQ.pop();
      }
      return maxQ.top().first;
  }
  
  int minimum() {
      while(!minQ.empty())
      {
          int price=minQ.top().first,time=minQ.top().second;
          if(price==umap[time]) break;
          minQ.pop();
      }
      return minQ.top().first;
  }
};

/**
* Your StockPrice object will be instantiated and called as such:
* StockPrice* obj = new StockPrice();
* obj->update(timestamp,price);
* int param_2 = obj->current();
* int param_3 = obj->maximum();
* int param_4 = obj->minimum();
*/

2578. 最小和分割

给你一个正整数 num ,请你将它分割成两个非负整数 num1 和 num2 ,满足:

  • num1 和 num2 直接连起来,得到 num 各数位的一个排列。
    • 换句话说,num1 和 num2 中所有数字出现的次数之和等于 num 中所有数字出现的次数。
  • num1 和 num2 可以包含前导 0 。

请你返回 num1 和 num2 可以得到的和的 最小 值。

注意:

  • num 保证没有前导 0 。
  • num1 和 num2 中数位顺序可以与 num 中数位顺序不同。

 #做题思路 和 优化思路

今天的每日一题很简单 就是简单的排序 加 贪心策略 让数字最小就是让 最小的数字尽量的做最高位 这样num1 + num2 的和一定是最小的

#coding

class Solution {
public:
    int splitNum(int num) {
        int num1=0,num2=0;
        vector<int> nums;
        while(num)
        {
            int d=num%10;
            nums.push_back(d);
            num/=10;
        }
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();++i)
        {
            if(i%2==0) num1=num1*10+nums[i];
            else num2=num2*10+nums[i];
        }
        return num1+num2;
    }
};

2731. 移动机器人

有一些机器人分布在一条无限长的数轴上,他们初始坐标用一个下标从 0 开始的整数数组 nums 表示。当你给机器人下达命令时,它们以每秒钟一单位的速度开始移动。

给你一个字符串 s ,每个字符按顺序分别表示每个机器人移动的方向。'L' 表示机器人往左或者数轴的负方向移动,'R' 表示机器人往右或者数轴的正方向移动。

当两个机器人相撞时,它们开始沿着原本相反的方向移动。

请你返回指令重复执行 d 秒后,所有机器人之间两两距离之和。由于答案可能很大,请你将答案对 109 + 7 取余后返回。

注意:

  • 对于坐标在 i 和 j 的两个机器人,(i,j) 和 (j,i) 视为相同的坐标对。也就是说,机器人视为无差别的。
  • 当机器人相撞时,它们 立即改变 它们的前进方向,这个过程不消耗任何时间。
  • 当两个机器人在同一时刻占据相同的位置时,就会相撞。

    • 例如,如果一个机器人位于位置 0 并往右移动,另一个机器人位于位置 2 并往左移动,下一秒,它们都将占据位置 1,并改变方向。再下一秒钟后,第一个机器人位于位置 0 并往左移动,而另一个机器人位于位置 2 并往右移动。

    • 例如,如果一个机器人位于位置 0 并往右移动,另一个机器人位于位置 1 并往左移动,下一秒,第一个机器人位于位置 0 并往左行驶,而另一个机器人位于位置 1 并往右移动。

#解题思路和优化思路

这道题属于思维题目 两个机器人碰撞后都反转方向 相当于两个机器人互相穿透了身体 所以我们只需要计算每个机器人的我最后位置 第二点就是 知道了n个点的坐标 如何计算他们的两两之间的和 我们可以用到前缀和的思想 我们把数组排序后 可以发现每两个点之间的差值 会被用 i*(n-i)次 可以画图来简单证明

#coding 

class Solution {
    const int mod=1e9+7;
public:
    int sumDistance(vector<int>& nums, string s, int d) {
        vector<long long> pos(nums.size());
        for(int i=0;i<nums.size();++i)
        {
            if(s[i]=='L') pos[i]=(long long) nums[i]-d;
            else pos[i]=(long long) nums[i]+d;
        }
        sort(pos.begin(),pos.end());
        long long res=0;
        for(int i=1;i<pos.size();++i)
        {
            res+=(pos[i]-pos[i-1])*i%mod*(pos.size()-i)%mod;
            res%=mod;
        }
        return res;
    }
};

LeetCode 周赛优选题目

2874. 有序三元组中的最大值 II

给你一个下标从 0 开始的整数数组 nums 。

请你从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。如果所有满足条件的三元组的值都是负数,则返回 0 。

下标三元组 (i, j, k) 的值等于 (nums[i] - nums[j]) * nums[k] 。

 #做题思路 和 优化思路

 这道题根据题意 首先想到的就是 三重枚举 分别取 i j k 枚举 然后取最大值 但时间复杂度O({n_{}}^{3}) 数据量大的时候毫无疑问会超时 我们需要想办法 来减少枚举的次数 从而达到满足时间复杂度 引入后缀最大数组(从后到前依次枚举数组 将最大值填入当前位置                          suffMax[i]=max(suffMax[i+1],nums[i])  从而达到来到每一个点 都知道 后面的最大值是多少 由于nums数组是非负数数组 所以可以引入一个preMax 变量 来记录前面的最大值 这样 只需要枚举中间数字 就可以实现题目要求 时间复杂度 为 O(n) 大大的降低了时间复杂度 

#coding


//暴力解法
class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        long long res=0;
        for(int i=0;i<nums.size();++i)
        {
            for(int j=i+1;j<nums.size();++j)
            {
                for(int k=j+1;k<nums.size();++k)
                {
                    res=(long long)max(res,(long long)(nums[i] - nums[j]) * nums[k]);
                }
            }
        }
        return res;
    }

}; 




//后缀数组优化
class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        int n=nums.size();
        vector<int> suffMax(n+1,0);
        for(int i=n-1;i>0;--i)
        {
            suffMax[i]=max(suffMax[i+1],nums[i]);
        }
        long long res=0;
        int preMax=nums[0];
        for(int i=1;i<nums.size();++i)
        {
            res=max(res,(long long)(preMax-nums[i])*suffMax[i+1]);
            preMax=max(preMax,nums[i]);
        }
        return res;
    }
};

2875. 无限数组的最短子数组

给你一个下标从 0 开始的数组 nums 和一个整数 target 。

下标从 0 开始的数组 infinite_nums 是通过无限地将 nums 的元素追加到自己之后生成的。

请你从 infinite_nums 中找出满足 元素和 等于 target 的 最短 子数组,并返回该子数组的长度。如果不存在满足条件的子数组,返回 -1 。

#解题思路 和 优化思路 

看到题目就知道这是一道数学题 分类讨论一下 如果 target 比较小 即小于数组的值 则有 target=rest 如果target 比较大 就有 target=n*sum+rest 其中·sum 是 nums的数组和 rest 是 target%sum 所以 问题就转化成了 找到 最小的 rest值 的子数组 由于 rest=target%sum < sum 所以只需要 2 个 nums 数组加一起就可以完成要求 解决如何找到最短长度子数组和等于rest  引入滑动窗口来解决问题 两个下标 left 和 right 来控制窗口的大小 并且窗口的和和 rest 作比较 从而实现题目的要求

# coding

class Solution {
public:
    int minSizeSubarray(vector<int>& nums, int target) {
        int sum=accumulate(nums.begin(),nums.end(),0LL);
        int rest=target%sum;
        int minLen=INT_MAX;
        int left=0,n=nums.size();
        long long curSum=0;
        for(int right=0;right<2*n;++right)
        {
            curSum+=nums[right%n];
            while(curSum>rest)  curSum-=nums[left++%n];
            if(curSum==rest) minLen=min(minLen,right-left+1);
        }
        return minLen==INT_MAX?-1:minLen+target/sum*n;
    }
};

100103. 分类求和并作差

给你两个正整数 n 和 m 。

现定义两个整数 num1 和 num2 ,如下所示:

  • num1:范围 [1, n] 内所有 无法被 m 整除 的整数之和。
  • num2:范围 [1, n] 内所有 能够被 m 整除 的整数之和。

返回整数 num1 - num2 。

#解题思路 和 优化思路 
题目很简单 但是有很多的优化方法 例如类似质数筛的方法可以加快求num2 以及高斯求和法 加速求num1 使得整个算法时间复杂度为O(1) 

#coding

class Solution {
public:
    int differenceOfSums(int n, int m) {
        int k=1;
        int num2=0;
        while(k*m<=n)
        {
            num2+=k*m;
            k++;
        }
        int num1=(n+1)*n/2-num2;
        return num1-num2;
    }
};

 100085. 最小处理时间

你有 n 颗处理器,每颗处理器都有 4 个核心。现有 n * 4 个待执行任务,每个核心只执行 一个 任务。

给你一个下标从 0 开始的整数数组 processorTime ,表示每颗处理器最早空闲时间。另给你一个下标从 0 开始的整数数组 tasks ,表示执行每个任务所需的时间。返回所有任务都执行完毕需要的 最小时间 。

注意:每个核心独立执行任务。

# 解题思路 和 优化思路 
这道题的思路是 贪心算法 最快的机子去解决时间最久的活 贪心下来就是答案

# coding

class Solution {
public:
    int minProcessingTime(vector<int>& processorTime, vector<int>& tasks) {
        sort(processorTime.begin(),processorTime.end());
        sort(tasks.rbegin(),tasks.rend());
        int index=0;
        int maxTime=0;
        for(int i=0;i<processorTime.size();++i)
        {
            for(int j=0;j<4;++j)
            {
                maxTime=max(maxTime,processorTime[i]+tasks[index]);
                index++;
            }
        }     
        return maxTime;                             
    }
};

2870. 使数组为空的最少操作次数

给你一个下标从 0 开始的正整数数组 nums 。

你可以对数组执行以下两种操作 任意次 :

  • 从数组中选择 两个 值 相等 的元素,并将它们从数组中 删除 。
  • 从数组中选择 三个 值 相等 的元素,并将它们从数组中 删除 。

请你返回使数组为空的 最少 操作次数,如果无法达成,请返回 -1 。

 # 解题思路 和 优化思路 
这道题是一道数学题 知道 2 和 3 的最小公倍数是6 并且我们可以把相同的数字用哈希表来统计个数 方便计算 算法设计 先将每个数字出现的个数变成 k*2+rest 其中rest是num%2的结果 这样就通过计算来简化算法 我们知道 2*3 == 3*2 即 每3次2个相同的数字消除 可以用 2次3个相同数字消除即每六个相同的数字 用3个相同的比2个相同的少一次 由此我们可以算一下 num%6的值 设为a 即将6个的全都转化成2次3个的 就可以算出来最终的结果 是 k-a 但是有一种特殊情况 即 2*k%6==0 并且 rest==1 这种情况  不能单独的用2变成3 只能拆掉一组 来凑 2 2 3 因此会多一次

# coding

class Solution {
public:
    int minOperations(vector<int>& nums) {
        unordered_map<int,int> umap;
        int res=0;
        for(int i=0;i<nums.size();++i)
        {
            umap[nums[i]]++;
        }
        for(auto& i : umap)
        {
            int num=i.second;
            if(num==1) return -1;
            int k=num/2,rest=num%2;
            int sub=num/6;
            if(2*k%6==0 && rest) k++; 
            k-=sub;
            res+=k;
        }
        return res;
    }
};

2871. 将数组分割成最多数目的子数组

给你一个只包含 非负 整数的数组 nums 。

我们定义满足 l <= r 的子数组 nums[l..r] 的分数为 nums[l] AND nums[l + 1] AND ... AND nums[r] ,其中 AND 是按位与运算。

请你将数组分割成一个或者更多子数组,满足:

  • 每个 元素都  属于一个子数组。
  • 子数组分数之和尽可能 小 。

请你在满足以上要求的条件下,返回 最多 可以得到多少个子数组。

一个 子数组 是一个数组中一段连续的元素。

 #解题思路 和 优化思路 
解题思路很巧妙 分情况讨论 #1 整个与值不是0 设为a 那么每一段与值都不小于a 则总体和一定大于a 这种情况不合理 所以他只能分成一组 #2 整个与值是0 则最多的情况就是从左到右 只要与值和为0 就分割

#coding

class Solution {
public:
    int maxSubarrays(vector<int>& nums) {
        int res=0;
        int cur=-1;
        for(int i=0;i<nums.size();++i)
        {
            cur&=nums[i];
            if(cur==0)
            {
                cur=-1;
                res++;
            }
        }
        return max(res,1);
    }
};

2872. 可以被 K 整除连通块的最大数目

给你一棵 n 个节点的无向树,节点编号为 0 到 n - 1 。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 有一条边。

同时给你一个下标从 0 开始长度为 n 的整数数组 values ,其中 values[i] 是第 i 个节点的  。再给你一个整数 k 。

你可以从树中删除一些边,也可以一条边也不删,得到若干连通块。一个 连通块的值 定义为连通块中所有节点值之和。如果所有连通块的值都可以被 k 整除,那么我们说这是一个 合法分割 。

请你返回所有合法分割中,连通块数目的最大值 。

 #解题思路 和 优化思路 
先解释树的定义 树的边和点数的关系是 边=点-1 而且树是一个连通图 且对于任何的点 x y 有且仅有一条路 使得x y联通  并且题目已知条件是\sum nums[i]%k==0 所以我们可以随便挑一点进行深度优先遍历 回溯的时候计算到子树和%k==0 就可以去掉这条边 最后回溯到 x就是答案

#coding

class Solution {
public:
    int maxKDivisibleComponents(int n, vector<vector<int>>& edges, vector<int>& values, int k) {
        vector<vector<int>> g(n);
        for (auto &e : edges) 
        {
            int x = e[0], y = e[1];
            g[x].push_back(y);
            g[y].push_back(x);
        }
        int ans = 0;
        function<long long(int, int)> dfs = [&](int x, int fa) -> long long {
            long long sum = values[x];
            for (int y : g[x])
            {
                if (y != fa) sum += dfs(y, x);
            }
            ans += sum % k ==0;
            return sum;
        };
        dfs(0, -1);
        return ans;
    }
};

专项类型训练(链表 栈 图论)

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

 #解题思路 和 优化思路 
链表的相加相减 如果原地算法 会变得非常的麻烦 所以一般的解法都是自己做一个新的链表头 ListNode* dummy = new ListNode() 这样就会简化非常多的问题 然后就是深入的看这道题 加法就会涉及到一个问题 进位问题 所以我们引入一个变量 carry 来记录 两个链表相加后的 进位值 最后就是注意链表的边界条件 和 终止条件 不要然链表变成NULL 后继续使用 会造成内存错误

#coding

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int carry=0;
        ListNode* dummy=new ListNode(0);
        ListNode* p=dummy;
//注意边界条件
        while(carry || l1 || l2)
        {
            int val1=l1?l1->val:0;
            int val2=l2?l2->val:0;
            int val=(carry+val1+val2)%10;
            carry=(carry+val1+val2)/10;
            ListNode* node=new ListNode(val);
            p->next=node;
            p=node;
            if(l1) l1=l1->next;
            if(l2) l2=l2->next;
        }
        return dummy->next;
    }
};

19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

#解题思路和优化思路 

这道题用到一个非常经典的算法结构快慢指针 既然求倒数第k个节点位置的数值 我们可以先让快指针先走k步 最后再让快慢指针一起走 直到快指针到NULL节点终止 此时 慢指针的位置就是倒数第k个节点位置 然后就是怎么删掉指针 我们需要引入一个新的指针 Pre指针 初始化指向NULL  每次slow走前 pre指向slow的位置 这样就实现了 pre一直在slow的上一个位置节点 最后用 pre->next=slow->next 实现删除slow节点

#coding

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* fast=head,* slow=head;
        ListNode* pre=NULL;
//快指针先走k步
        for(int i=0;i<n;++i)
        {
            if(fast->next==NULL)  return head->next;
            fast=fast->next;
        }
//快慢指针一起走 直到快指针到NULL节点终止
        while(fast)
        {
            fast=fast->next;
            pre=slow;
            slow=slow->next;
        }
        pre->next=slow->next;
        delete slow;
        return head;
    }
};

82. 删除排序链表中的重复元素 II

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

 #解题思路和优化思路 

这道题就很简单 利用 哈希表来解答就好了 统计每个数字出现的次数 只让出现1次的数字出现在链表上

#coding

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummy=new ListNode();
        ListNode* p=dummy,*p1=head;
        unordered_map<int,int> umap;//来记录每个数字出现的次数
        while(p1)
        {
            umap[p1->val]++;
            p1=p1->next;
        }
        p1=head;
        while(p1)
        {
            if(umap[p1->val]==1)
            {
                ListNode* tmp=new ListNode(p1->val);
                p->next=tmp;
                p=tmp;
            }
            p1=p1->next;
        }
        return dummy->next;
    }
};

24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

#解题思路和优化思路 

这道题的解题思路和反转链表特别特别相似 用next指针来记录当前指针的下一个的下一个节点next  然后 当前指针和 当前指针的下一个 交换节点 再让指针来到next位置 循环上述过程 同时用一个pre指针 来 抓主之前位置的链表节点 用pre->next=swapNode 实现链表的连接

#coding

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* p=head,* pre=NULL;
        while(p)
        {
           ListNode* swapNode=p->next;
           if(swapNode==NULL) break;
           if(p==head) head=swapNode;
           ListNode* next=swapNode->next;
           if(pre!=NULL) pre->next=swapNode;
           pre=p;
           swapNode->next=p;
           p->next=next;
           p=next;
        }
        return head;
    }
};

155. 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

 #解题思路和优化思路 

这道题的解题思路就是如何去完成最小值的记录 我使用了经典的算法方法 最小栈 栈只有不比自己小的数字在可以进来当栈顶 这样就可以实现 在栈顶被弹出的时候 可以保证他是整个栈的最小数 但是只有这一个栈还是不够 因为我们还需要统计什么时候弹出栈顶元素 所以还需要一个记录每个栈上面有几个数字 这样子就可以在上面元素弹完后在弹出栈顶元素 

#coding

class MinStack {
    stack<int> minStack;
    stack<int> countStack;
    stack<int> s;
    int k=0;
public:
    MinStack() {

    }
    
    void push(int val) {
        s.push(val);
        if(minStack.empty() || minStack.top()>=val)
        {
            if(!minStack.empty()) countStack.push(k);
            minStack.push(val);
            k=0;
        }
        else k++;
    }
    
    void pop() {
        if(k) k--;
        else 
        {
            k=countStack.top();
            if(!minStack.empty()) minStack.pop();
        } 
        if(!s.empty()) s.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int getMin() {
        return minStack.top();
    }
};

150. 逆波兰表达式求值

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*' 和 '/' 。
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断 。
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

 #解题思路和优化思路 

这道题就相较于前面那道题简单许多就是 代码方面需要抓细节 很简单的一道题

#coding

class Solution {
    stack<int> st;
    bool isDigit(string s)
    {
        if(s.size()==1 && (s[0]<'0' || s[0]>'9')) return false;
        return true;
    }
public:
    int evalRPN(vector<string>& tokens) {
        for(int i=0;i<tokens.size();++i)
        {
            if(isDigit(tokens[i])) st.push(stoi(tokens[i]));
            else 
            {
                int num1=st.top();
                st.pop();
                int num2=st.top();
                st.pop();
                if(tokens[i]=="+") num2+=num1;
                if(tokens[i]=="-") num2-=num1;
                if(tokens[i]=="*") num2*=num1;
                if(tokens[i]=="/") num2/=num1;
                st.push(num2);
            }
        }
        return st.top();
    }
};

143. 重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

 #解题思路和优化思路 

和24题非常的像 不一样的地方就是 swapNode 不再是 p->next 而是 s.top()  思路很简单的一道题

#coding

class Solution {
public:
    void reorderList(ListNode* head) {
        stack<int> s;
        queue<int> q;
        ListNode* dummy=new ListNode();
        ListNode* p=head;
        while(p)
        {
            s.push(p->val);
            q.push(p->val);
            p=p->next;
        }
        p=head;
        while(p)
        {
            p->val=q.front();
            q.pop();
            p=p->next;
            if(p) 
            {
                p->val=s.top();
                s.pop();
                p=p->next;
            }
        }
    }
};

114. 二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
  • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

 #解题思路和优化思路 

二叉树类型的题目 核心思想还是栈的思想 根据题目意思 我们知道他需要将一颗二叉树转换变成一个只有右树没有左树的"链表" 解题思路是 我们需要通过 "右 左 中" 的遍历方式来遍历这颗二叉树 原因: 当我们改变左树结构的时候 会把root->right的部分 直接放到左树的right位置 所以我们需要先让 root->right先排好位置 其次就是 每一层的节点 都需要自己下面完全排序好 当前节点的操作是 将左树移植到右树上面 所以确定好遍历顺序后就可以考虑如何才能完成每一层的操作 最底一层很容易想到 最多就是 把左子树的单节点放到右子树上 但对于 左子树 右子树 都大于 1 层 这样的想法就是不正确的 所以我们需要一个node节点 来遍历这棵树的左子树 让他到达叶节点位置 再将叶节点与右子树连接 从而实现题目的要求 最后就是 代码 的细节问题 注意到就可以了

#coding

class Solution {
public:
    void flatten(TreeNode* root) {
        if(root==NULL) return;
        flatten(root->right);
        flatten(root->left);
        if(root->left)
        { 
            TreeNode* right=root->right;
            TreeNode* node=root->left;
            while(node->right) node=node->right;
            node->right=right;
            root->right=root->left;
            root->left=NULL;
        }
    }
};

133. 克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 valint) 和其邻居的列表(list[Node])。

class Node {
    public int val;
    public List<Node> neighbors;
}

 #解题思路和优化思路 

这道题主要的思路是 深度拷贝一个图 图有两个重要因素 val 和 neighbor 所以map来记录cloneNode 同时递归的建立Node的邻接点

#coding

class Solution {
public:
    unordered_map<Node*, Node*> visited;
    Node* cloneGraph(Node* node) 
    {
        if (node == nullptr)  return node;
        if (visited.find(node) != visited.end()) return visited[node];
        Node* cloneNode = new Node(node->val);
        visited[node] = cloneNode;
        for (auto& neighbor: node->neighbors) 
        {
            cloneNode->neighbors.emplace_back(cloneGraph(neighbor));
        }
        return cloneNode;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值