【每日一题】 2024年2月汇编(上)

🔥博客主页A_SHOWY
🎥 系列专栏力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_

【2.1】LCP 24.数字游戏

LCP 24. 数字游戏https://leetcode.cn/problems/5TxKeK/

这个题目可以变换一下就是将一个递增的需求经过nums【i】-i变换变成一个求相等的需求,找中位数,用到了大小堆栈(优先队列),收获颇多。

  1. 首先是对于大小堆栈的定义,对于大堆栈可以默认定义为 如下,小顶堆需要声明一下

    cpp 复制代码
    priority_queue<int> L;//大顶堆
    priority_queue<int,vector<int>,greater<int>> R;//小顶堆
    //第一个int是堆中数据类型,第二个数组是定义底层容器类型,第三个是比较函数大顶堆可以默认,也可以写上less<int>,小顶堆是greater

    大顶堆是堆顶是最大元素,后边push的一直维护一个递减,小顶堆相反

  2. 这个操作是一个动态平衡的一个思想,每加入一个元素,优先放到左边,进行动态调整,始终保持左边最多比右边多一个,堆会自动排好序。计算结果的时候,考虑先找出这一段的中位数,左右分开计算。

  3. 在计算这个ans【i】的时候,要注意这个整除溢出问题,所以给mid一个long long

cpp 复制代码
class Solution {
public:
    vector<int> numsGame(vector<int>& nums) {
    priority_queue<int> L;//大顶堆
    priority_queue<int,vector<int>,greater<int>> R;//小顶堆
    for(int i = 0; i < nums.size(); i++){
        nums[i] = nums[i] - i;
    }
    long sumL = 0;
    long sumR = 0;
    int mod = 1e9 +7;

    vector<int> ans(nums.size(),0);
    for(int i = 0; i < nums.size(); i++){
        if(L.empty() || nums[i] <= L.top()) {
            L.push(nums[i]);
            sumL += nums[i];
            //平衡
              if(L.size() > R.size() + 1){
            R.push(L.top());
            sumL -= L.top();
            sumR += R.top();
            L.pop();
        }
        }
        else{
            R.push(nums[i]);
            sumR += nums[i];
            if(L.size() < R.size()){
                L.push(R.top());
            sumR -= R.top();
            sumL += L.top();
            R.pop();
            }
        }
         long long  mid = L.top();
         ans[i] = (((i + 2) / 2) * mid - sumL + sumR - ((i + 1) / 2) * mid) % mod ; 
        
    }
    return ans;
    }
};

【2.2】1686.石子游戏Ⅵ

1686. 石子游戏 VIhttps://leetcode.cn/problems/stone-game-vi/

对贪心的理解还是不够深入,贪心+排序即可解决这个问题,当你选择取一个石子的时候,你不仅是取得了你的分数,也让这个分数不能被你的对手获取,所以每次取的时候,你应该取使得aliceValues[i] + bobVlaues[i]最大的石子。

  1. 贪心的话,不仅要让自己取得的数比较大,还不能让别人取到大的你应该取使得aliceValues[i] + bobVlaues[i]最大的石子。
  2. 整体的代码思路为创建一个数组,这个数组是一维的,但是存储的是一个键值对,因为一会儿要排序,但是要存储它原始数组对应的位置,用这个新数组存储两个和,先排序再翻转就得到了一个递减数组,然后每两个一对做一个循环两个人依次取比较即可。
cpp 复制代码
class Solution {
public:
    int stoneGameVI(vector<int>& aliceValues, vector<int>& bobValues) {
     int n = aliceValues.size();
     vector<pair<int,int>> stones;
     for(int i = 0 ; i < n ; i++){
         stones.push_back({aliceValues[i] + bobValues[i],i});
     }
     sort(stones.begin(),stones.end());
     reverse(stones.begin(),stones.end());
     int a = 0,b = 0;
     for(int i = 0; i < n; i += 2){
            a += aliceValues[stones[i].second];
            if(i + 1 < n) b += bobValues[stones[i + 1].second];
     }
     return a == b ? 0 : (a > b ? 1 : -1);
    }
};

【2.3】1690.石子游戏

1690. 石子游戏 VIIhttps://leetcode.cn/problems/stone-game-vii/

这个题目是一个动态规划问题,相对来说还是比较复杂,可能也是我对动态规划整体不够熟悉,值得整理的一个题目,思路清晰很重要。

  • 对于这个题目的解题首先要理解明确几个问题 ,找出dp关系
  1. 首先明确------谁是先手谁的得分就最大.

  2. 对于 dp[i][j] 定义为区间 [i,j] 我们要的结果,在区间 [i, j],dp[i][j] = 先手的总分 - 后手的总分。

  3. 如果 dp[i][j]这个区间当前是鲍勃操作,那么鲍勃的得分一定最大。选择去掉 stones[i] 后当前的分数为 sum(stones[i + 1], stones[j]).那么区间 [i + 1, j]鲍勃的得分是多少呢?不用管它dp[i + 1][j] 一定为对手爱丽丝作为先手得到的结果,因为谁先手谁的得分最大,则 dp[i + 1][j] = 爱丽丝得分 - 鲍勃的得分。
    sum(stones[i + 1], stones[j]) - dp[i + 1][j]
    = 鲍勃当前操作得分 - (爱丽丝的总分 - 鲍勃的总分)
    = 鲍勃当前操作得分 + 鲍勃的总分 - 爱丽丝的总分
    = 鲍勃新的总分 - 爱丽丝的总分 > 0(谁先手谁最大)。
    如果去掉 stones[j] 则原理同上.

    如果当前 dp[i][j] 是爱丽丝,则将上面的叙述中爱丽丝和鲍勃名字互换。

  4. 对于爱丽丝我们很好理解为什么要最大化dp[i][j] = max(sum(stones[i + 1], stones[j]) - dp[i + 1][j], sum(stones[i], stones[j - 1]) - dp[i][j - 1]);那么鲍勃为什么也要最大化 dp[i][j] 呢,因为爱丽丝先手,鲍勃必输,题目给出了。所以只有当鲍勃操作时 dp[i][j] 最大,才能让爱丽丝操作时得到的结果最小,满足鲍勃的野心

    爱丽丝当前操作得分 - (鲍勃的总分 - 爱丽丝的总分)(鲍勃操作时的最大化差值)

    cpp 复制代码
    //假设现在Bob先手
    //现在要得到的结果dp[i][j] = Bob当前操作的得分 - (Alice的总得分 - Bob的总得分)=  sum(stones[i + 1], stones[j]) - dp[i + 1][j]
    class Solution {
    public:
    
        int stoneGameVII(vector<int>& stones) {
         vector<int> sums(stones.size() + 1,0);
         vector<vector<int>> dp(stones.size(),vector<int>(stones.size(),0));
    //   前缀和
         for(int i = 0; i < stones.size(); i++){
             sums[i + 1] = sums[i] + stones[i];
         }
    //  dp关系
        for(int i = stones.size() - 2; i >= 0; --i){
            for(int j = i + 1; j < stones.size(); ++j){
                dp[i][j] = max(sums[j + 1] - sums[i + 1] - dp[i + 1][j],sums[j] - sums[i] - dp[i][j -1]);
            }
        }
        return dp.front().back();
        }
    };
  • 对于代码的核心其实是前缀和 + dp关系。定义两个数组,一个记录sum(n+1),一个记录dp关系,做dp关系的循环时候,要从内部往外部递归,不要错了顺序。最后.front.back是【0】【size - 1】的意思。

【2.4】292.Nim游戏

292. Nim 游戏https://leetcode.cn/problems/nim-game/

一个小小的思维题,当有1、2、3个时候,你可以直接拿走直接赢了,但是当有4个时候,你拿几个,对面都能直接赢,当有5,6,7个时候。你可以先拿走一些剩下四个,这样对面拿啥你都行,但是当第8个的时候,你拿几个,对面都能给你剩下四个,依次类推,当有4,8,12等的时候4的倍数的时候,你都不能赢。

cpp 复制代码
class Solution {
public:
    bool canWinNim(int n) {
      return n % 4 != 0;
    }
};

【2.5】1696.跳跃游戏Ⅵ

1696. 跳跃游戏 VIhttps://leetcode.cn/problems/jump-game-vi/

这道题目,第一思路就是递推,但是很遗憾2000分的题直接超时,需要用到单调队列去处理,好久不用了,有点生疏了。

方法一:递归(超时)

cpp 复制代码
 dp[i] = *max_element(dp.begin() +max(i - k,0),dp.begin() + i) + nums[i];

看一下从i往前k个数中的最大值加上nums【i】

cpp 复制代码
//递推
class Solution {
public:
    int maxResult(vector<int>& nums, int k) {
    int n = nums.size();
    vector<int> dp(n);
    dp[0] = nums[0];
    for(int i = 1; i < n; i++){
        dp[i] = *max_element(dp.begin() +max(i - k,0),dp.begin() + i) + nums[i];
    }
    return dp[n - 1];
    }
};

方法二: 单调队列

仔细观察题目,和我们之前做的一个题目特别的像,滑动窗口最大值,我们用到就是单调双端队列!,这个其实一个意思。入的时候,先看前面有没有比他小的,如果有,pop掉再入,保证整体是一个单调递减,开头判断是否需要出。然后转移关系是 f[i] = f[q.front()] + nums[i];

cpp 复制代码
class Solution {
public:
    int maxResult(vector<int>& nums, int k) {
      int n = nums.size();
      vector<int> f(n);
      f[0] = nums[0];
      deque<int> q = {0};
      for(int i = 1; i < n; i++){
          //数量超了,出
          if(q.front() < i - k){
              q.pop_front();
          }
          //转移
          f[i] = f[q.front()] + nums[i];

          //入,注意这里是while
          while(!q.empty() && f [i] >= f[q.back()]){
              q.pop_back();
          }
          q.push_back(i);
      }
      return f[n - 1];
    }
};

回顾一下滑动窗口最大值那个题目

239.滑动窗口最大值

239. 滑动窗口最大值https://leetcode.cn/problems/sliding-window-maximum/

cpp 复制代码
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    int n = nums.size();
    deque<int> q;
    vector<int>res (nums.size() - k + 1);
    // 形成窗口前
    for(int i = 0; i < k; i++){
       while(!q.empty() && q.back() < nums[i]){
           q.pop_back();
       }
       q.push_back(nums[i]);
    }
    res[0] = q.front();

    // 形成窗口后
    for(int i = k; i < n; i++){
        if(q.front() == nums[i - k]){
            q.pop_front();
        }
        while(!q.empty() && q.back() < nums[i]){
            q.pop_back();
        }
        q.push_back(nums[i]);
        res[i - k + 1] = q.front();
    }
      return res;
    }
};

【2.6】LCP 30魔塔游戏

LCP 30. 魔塔游戏https://leetcode.cn/problems/p0NxJO/

魔塔游戏,就是让这个血量在最后前不能小于0,是一个贪心加优先队列的一个思路,用一个小顶堆去维护一个顶端最小的一个数,每当这个hp小于0的时候,就贪心地把一个最小的扔到最后,但是不要忘了设置一个delay,最后要加上验算是否总和大于0.

需要注意以下数据量,大概10的15次方,hp和dalay需要声明long long

cpp 复制代码
class Solution {
public:
    int magicTower(vector<int>& nums) {
    // 小顶堆
    priority_queue<int,vector<int>,greater<int>> q; 
    int ans = 0;
    long long hp = 1,delay = 0;
    int sum = 0;
    for(int num : nums){
        if(num < 0) {
            q.push(num);
        }
        hp += num;
        if(hp <= 0){
            ans ++;
            delay += q.top();
            hp -= q.top();
            q.pop();
        }
    }
    // 最后要把这个delay加上,如果小于0,铁不行
         hp += delay;
         return hp > 0 ? ans : -1; 
    }
};

补充:LCP 24数字游戏

LCP 24. 数字游戏https://leetcode.cn/problems/5TxKeK/

同样是用优先队列,大顶推和小顶堆都用上了。整体思路就是,对顶堆维护动态的中位数,两个相对的顶堆,大顶堆的left就是中位数。

注意,顶堆在push和pop后,会自动插入相应位置形成单调。再次回顾顶堆的定义。大顶堆可以直接默认,小顶堆需要声明完全

cpp 复制代码
        priority_queue<int> q1;//大顶堆
        priority_queue<int,vector<int>,greater<int>>q2;//小顶堆
cpp 复制代码
class Solution {
public:
    vector<int> numsGame(vector<int>& nums) {
        int n = nums.size();
        priority_queue<int> q1;//大顶堆
        priority_queue<int,vector<int>,greater<int>>q2;//小顶堆
        for(int i = 0; i < n; i++){
        nums[i] = nums[i] - i;
     }

vector<int> res(n,0);

long long mod = 1e9 + 7;
long long sum1 = 0;
long long sum2 = 0;

//对顶堆维护动态的中位数
for(int i = 0 ;i < n; i++){
    if(q1.empty() || nums[i] <= q1.top()){
        q1.push(nums[i]);
        sum1 += nums[i];
        //维护平衡
        if(q1.size() > q2.size() + 1){
            q2.push(q1.top());
            sum1 -= q1.top();
            sum2 += q1.top();
            q1.pop();
        }
    }
        else{
            q2.push(nums[i]);
            sum2 += nums[i];
            if(q2.size() > q1.size()){
                q1.push(q2.top());
                sum2 -= q2.top();
                sum1 += q2.top();
                q2.pop();
            }
        
    }
    long long mid = q1.top();
        res[i] = (((i + 2) / 2) * mid - sum1 + sum2 - mid * ((i + 1) / 2)) % mod;
}
return res;
    }
};

【2.8】993.二叉树的堂兄弟结点

993. 二叉树的堂兄弟节点https://leetcode.cn/problems/cousins-in-binary-tree/

关于找堂兄弟,可以用深度优先和广度优先,虽然简单,但是确实冗长,我更熟悉用DFS解决。

方法一:DFS(深度优先遍历)

核心代码在于dfs函数内部逻辑。

cpp 复制代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //x 数据
    int x;
    int x_depth;
    TreeNode *x_parent;
    bool x_found = false;
    //y 数据
    int y;
    int y_depth;
    TreeNode *y_parent;
    bool y_found = false;
    void dfs(TreeNode *node, int depth,TreeNode *parent){
        if(!node){return;}
        if(node -> val == x){
            x_depth = depth;
            x_parent = parent;
            bool x_found = true;
        }
        else if(node -> val == y){
            y_depth = depth;
            y_parent = parent;
            bool y_found = true;
        }
        //两个都找到了,就return
        if(x_found && y_found){
            return;
        }
        dfs(node -> left,depth + 1,node);
        if(x_found && y_found){
            return;
        }
        dfs(node -> right,depth + 1, node);
    }
    bool isCousins(TreeNode* root, int x, int y) {
     this -> x = x;
     this -> y = y;
     dfs(root,0,nullptr);
     return(x_depth == y_depth && x_parent != y_parent);
    }
};

【2.09】236.二叉树的最近公共祖先

236. 二叉树的最近公共祖先https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/

整体思路还是递归,树的知识感觉大同小异,直接dfs ,分类四种情况

  1. 分三步考虑,第一终止条件,当root等于p或者q的时候,返回root,说明找到了,当为空的时候,返回null,说明找穿了
  2. 第二步递推工作,就是递归完左边,递归右边
  3. 第三步返回值,分为四种情况,第一种,left,right都是空,说明root的左右子树都不包含,返回空,如果left,right、同时不为空,说明在root的两侧,root就是最近子节点,返回root,如果left为空,说明p或者q在root的右侧直接返回right,right为空同理。
cpp 复制代码
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == p || root == q || root == nullptr) return root;
        TreeNode* left = lowestCommonAncestor(root -> left,p,q);
        TreeNode* right = lowestCommonAncestor(root -> right,p,q);
        if(left == NULL && right == NULL) return NULL;
        if(left == NULL) return right;
        if(right == NULL) return left;
        return root;
    }
};

【2.10】94.二叉树的中序遍历

【2.11】144.二叉树的前序遍历

【2.12】145.二叉树的后序遍历

94. 二叉树的中序遍历https://leetcode.cn/problems/binary-tree-inorder-traversal/
144. 二叉树的前序遍历https://leetcode.cn/problems/binary-tree-preorder-traversal/

145. 二叉树的后序遍历https://leetcode.cn/problems/binary-tree-postorder-traversal/

这三个题目过年三天官方福利,三个简单题目思路完全一样,前序后序和中序遍历,搞清楚概念用dfs解决即可。

前序遍历:按照访问根节点------左子树------右子树

中序遍历:按照访问左子树------根节点------右子树

后序遍历:按照访问左子树------右子树------根节点

这个序是按照根节点位置来的

cpp 复制代码
//94
class Solution {
public:
    void inorder(TreeNode* root,vector<int>& res)
    {
        if(!root){
            return;
        }
        inorder(root -> left,res);
        res.push_back(root -> val);
        inorder(root ->right,res);
    }

    vector<int> inorderTraversal(TreeNode* root) {
    vector<int> res;
    inorder(root,res);
    return res;
    }
};
cpp 复制代码
//144
class Solution {
public:
     void qianxu(TreeNode* root,vector<int> &res){
        if(!root) {return;}
        res.push_back(root -> val);
        qianxu(root -> left,res);
        qianxu(root -> right,res);
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        qianxu(root,res);
        return res;
      
    }
};
cpp 复制代码
//145
class Solution {
public:
    void houXu(TreeNode* root,vector<int> &res){
        if(!root){
            return;
        }
        houXu(root -> left,res);
        houXu(root-> right,res);
        res.push_back(root -> val);
       
    }
    vector<int> postorderTraversal(TreeNode* root) {
     vector<int>res;
     houXu(root,res);
     return res;
    }
};

【2.13】987.二叉树的垂序遍历

987. 二叉树的垂序遍历https://leetcode.cn/problems/vertical-order-traversal-of-a-binary-tree/

整体思路运用dfs输出每个结点的行列和值的对应关系,用哈希表存储,再对哈希表进行遍历输出。

  1. 值得注意的是,在哈希表遍历输入的时候,一定要用emplace_back(),这样可以一个键存多个值对,如果用push_back则不行,在后续遍历哈希表的时候,无法将键一样的存在一个数组里。
  2. 整体思路,dfs经典递归,然后先对每一个键的值对遍历,排序后存到每一个数组中,还有就是ranges::sort排序更为方便直接()内加容器。
cpp 复制代码
class Solution {
      map<int, vector<pair<int, int>>> groups;
public:
     void dfs(TreeNode *node, int row,int  col){
      if(!node){return;}
     groups[col].emplace_back(row,node -> val);
     dfs(node -> left,row + 1,col - 1);
     dfs(node -> right,row + 1, col + 1);
     }
    vector<vector<int>> verticalTraversal(TreeNode* root) {
    dfs(root,0,0);
    vector<vector<int>> ans;
    for(auto &[_,g] : groups){
        ranges :: sort(g);
        vector<int> vals;
    for(auto &[_,val] : g){
        vals.push_back(val);
    }
    ans.push_back(vals);
    }
    return ans;
    }
};

【2.14】102.二叉树的层序遍历

102. 二叉树的层序遍历https://leetcode.cn/problems/binary-tree-level-order-traversal/

对于这种层序遍历就不得不用BFS了。层序遍历和最短路径是BFS的主要就业空间。

  1. 值得注意的几个点,第一个是 在二维向量中添加一维向量之前最好是先创建一个一维向量。
  2. 在BFS中,常用队列数据结构,因为其先进先出的特性,可以pop出以后,对结点的左右子树进行遍历实现广度优先遍历。
cpp 复制代码
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
      if(!root) {
          return res;
      }
    
      queue<TreeNode*> q;
      q.push(root);
      while(!q.empty()){
          int currentSize = q.size();
          res.push_back(vector<int>());//在二维向量中添加一维向量之前最好是先创建一个一维向量
          for(int i = 1; i <= currentSize;++i){
              auto node = q.front();q.pop();
              res.back().push_back(node -> val);
              if(node -> left) q.push(node -> left);
              if(node -> right) q.push(node -> right);
          }
      }
      return res;
    }
};

【2.15】102.二叉树的层序遍历Ⅱ

107. 二叉树的层序遍历 IIhttps://leetcode.cn/problems/binary-tree-level-order-traversal-ii/

和上一题基本一样,就是加一个reverse的函数,BFS以后数组再swap一下反向输出就行

cpp 复制代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:

 void reverseArray(vector<vector<int>> &arr,int size){
      int start = 0;
      int end = size -1;
      while(start <= end){
        //   auto temp = arr[start];
        //   arr[start] = arr[end];
        //   arr[end] = temp;

        //   start ++;
        //   end  --;
        swap(arr[start],arr[end]);
          start ++;
          end  --;
      }
    }

    vector<vector<int>> levelOrderBottom(TreeNode* root) {
     vector<vector<int>> res;
      if(!root) {
          return res;
      }
    
      queue<TreeNode*> q;
      q.push(root);
      while(!q.empty()){
          int currentSize = q.size();
          res.push_back(vector<int>());//在二维向量中添加一维向量之前最好是先创建一个一维向量
          for(int i = 1; i <= currentSize;++i){
              auto node = q.front();q.pop();
              res.back().push_back(node -> val);
              if(node -> left) q.push(node -> left);
              if(node -> right) q.push(node -> right);
          }
      }
      reverseArray(res,res.size());
      return res;
    }
};
相关推荐
哭泣的眼泪4081 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame
Microsoft Word2 小时前
c++基础语法
开发语言·c++·算法
天才在此2 小时前
汽车加油行驶问题-动态规划算法(已在洛谷AC)
算法·动态规划
莫叫石榴姐3 小时前
数据科学与SQL:组距分组分析 | 区间分布问题
大数据·人工智能·sql·深度学习·算法·机器学习·数据挖掘
茶猫_4 小时前
力扣面试题 - 25 二进制数转字符串
c语言·算法·leetcode·职场和发展
肥猪猪爸6 小时前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
readmancynn6 小时前
二分基本实现
数据结构·算法
萝卜兽编程6 小时前
优先级队列
c++·算法
盼海7 小时前
排序算法(四)--快速排序
数据结构·算法·排序算法
一直学习永不止步7 小时前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表