LeetCode冲刺:Day12

发布于 2022-11-19  1058 次阅读


明天是第二次周赛了,紧张。预计下周二后找老师考核。不过要是明天周赛还发挥不好的话,就不太好意思了。

今天才知道,C++ &的优先级低于 ==,难怪之前有些代码的异常很奇怪……

1. 最长优雅子数组

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

看到连续子数组,就应该联想到滑动窗口法。还要注意到,与组内每一个元素与等于0,相当于对应每个1为0,所以把之前的元素都异或和起来再对比即可,窗口滑出元素时也用异或。

但这里有用例过不了,迷惑。

更新,把while的判断改为无不等号就行了,更迷惑了。

代码

class Solution {
public:
    int longestNiceSubarray(vector<int>& nums) {
        int ans=1;
        int n=nums.size();
        if (n==1) return 1; 
        int sum=0;
        int left=0;
        for (int i=0;i<n;++i) {
            while((sum&nums[i])>0) { //括号不能省
                sum^=nums[left++];
            }
            sum^=nums[i];
            ans=i-left+1>ans?i-left+1:ans;
        }
        return ans;
    }
};

2. 和相等的子数组

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

遍历一遍即可。

代码

class Solution {
public:
    bool findSubarrays(vector<int>& nums) {
        int n=nums.size();
        map<int,int> mp;
        for (int i=1;i<n;++i) {
            if (mp[nums[i]+nums[i-1]]==1) return true;
            else mp[nums[i]+nums[i-1]]=1;
        }
        return false;
    }
};

3. 严格回文的数字

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

针对每个数字,转换成对应进制的字符串,然后对比取反字符串即可。

代码

class Solution {
public:
    bool isStrictlyPalindromic(int n) {
        for (int i=2;i<=n-2;++i) {
            int num=n;
            string s1="";
            while(num) {
                s1+=to_string(num%i);
                num/=i;
            }
            string s2(s1.rbegin(),s1.rend());
            if (s1!=s2) return false;
        }
        return true;
    }
};

4. 被列覆盖的最多行数

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

参考这篇 下的评论。

又学到一个新操作,用内建函数 __builtin_popcount() 来统计一个数字的二进制形式包含多少0。将每行的01序列转换为二进制数字,将选定序列的也转换为二进制形式,当该数字含有的1的数量等于numsSelect,则检查覆盖情况。如果两者相与还等于原数字,则说明该行已经被覆盖,可以++计数。最后取最大计数即可。

代码

class Solution {
public:
    int maximumRows(vector<vector<int>>& matrix, int numSelect) {
        int n=matrix.size();
        int m=matrix[0].size();
        vector<int> rowNum(n);
        for (int i=0;i<n;++i) {
            int num=0;
            for (int j=0;j<m;++j) {
                num*=2;
                num+=matrix[i][j];
            }
            rowNum[i]=num;
        }

        int ans=0;
        for (int now=1;now<(1<<m);++now) {
            if ((__builtin_popcount(now))==numSelect) {
                int cnt=0;
                for (int i=0;i<n;++i) {
                    if ((now&rowNum[i])==rowNum[i]) {
                        ++cnt;
                        ans=cnt>ans?cnt:ans;
                    }
                }
            }
        }
        return ans;
    }
};

5. 和有限的最长子序列

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

虽然背包超时了,但还是趁机回顾下背包问题。对于本题,可以看作若干个01背包问题。

这题同时标了独立完成和参考思路。独立完成是因为能够无辅助直接流畅写出背包代码。参考思路是看了评论区题解的标题“排序+二分”,根据这个写出了代码。

虽然题目描述说的是子序列,但实际上答案是顺序无关的,所以可以给nums数组排序,计算前缀和,二分搜索即可。另外,在前缀和数组末尾增加一个INT_MAX值,以解决目标值超出前缀和数组最大值引发的问题。

代码

// 01背包
class Solution {
public:
    vector<int> answerQueries(vector<int>& nums, vector<int>& queries) {
        int capacity=*max_element(queries.begin(),queries.end());
        int m=queries.size();
        int n=nums.size();
        vector<vector<int>> dp(capacity+1,vector<int>(n+1,0));

        for (int j=1;j<=capacity;++j) {
            for (int i=1;i<=n;++i) {
                if (j>=nums[i-1]) dp[j][i]=max(dp[j-nums[i-1]][i-1]+1,dp[j][i-1]);
                else dp[j][i]=dp[j][i-1];
            }
        }
        vector<int> ans(m);
        for (int i=0;i<m;++i) ans[i]=dp[queries[i]][n];
        return ans;

        // 排序+二分
        sort(nums.begin(),nums.end());
        int n=nums.size();
        vector<int> vec(n+1);
        vec[0]=nums[0];
        for (int i=1;i<n;++i) {
            vec[i]=vec[i-1]+nums[i];
        }
        vec[n]=INT_MAX;
        int m=queries.size();
        vector<int> ans(m);
        for (int i=0;i<m;++i) {
            int target=queries[i];
            int left=0,right=n;
            while (left<right) {
                int mid=left+((right-left)>>1);
                if (vec[mid]>target) {
                    right=mid;
                } else left=mid+1;
            }
            ans[i]=left;
        }
        return ans;
    }
};

6. 从字符串中移除星号

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

前后两题难度标反了吧……

在字符串上删除中间元素不好操作,直接构造一个新字符串。遍历给定字符串,如果遇到非星号字符,直接加到新字符串末尾;如果遇到星号,把新字符串的末尾字符弹出。最后返回新字符串。

代码

class Solution {
public:
    string removeStars(string s) {
        string ans="";
        int n=s.size();
        for (int i=0;i<n;++i) {
            if (s[i]!='*') ans+=s[i];
            else ans.pop_back();
        }
        return ans;
    }
};

7. 收集垃圾的最少总时间

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

首先计算每个房子里每种垃圾的数量,显然,总时间必定包含总数量(一比一)。然后记录每个车要到达的最远位置,加上对应时间即可。总的时间即求出来了。

初始版本虽然能过,但效率惨不忍睹,时间内存都超过5%提交者。优化一下之后时间超过96%,空间超过38%。

这次周赛两道M都比E简单……

代码(未优化)

class Solution {
public:
    int garbageCollection(vector<string>& garbage, vector<int>& travel) {
        int n=garbage.size();
        vector<map<char,int>> center(n);
        for (int i=0;i<n;++i) {
            for (auto ch:garbage[i]) center[i][ch]++;
        }
        int timeM=0,timeP=0,timeG=0;
        int farM=0,farP=0,farG=0;
        for (int i=0;i<n;++i) {
            if (center[i]['M']!=0) {
                timeM+=center[i]['M'];
                farM=i;
            }
            if (center[i]['P']!=0) {
                timeP+=center[i]['P'];
                farP=i;
            }
            if (center[i]['G']!=0) {
                timeG+=center[i]['G'];
                farG=i;
            }
        }
        int m=travel.size();
        vector<int> totalTime(m+1,0);
        totalTime[1]=travel[0];
        for (int i=2;i<=m;++i) totalTime[i]=totalTime[i-1]+travel[i-1];
        timeM+=totalTime[farM];
        timeP+=totalTime[farP];
        timeG+=totalTime[farG];
        return timeM+timeP+timeG;
    }
};

代码(优化后)

class Solution {
public:
    int garbageCollection(vector<string>& garbage, vector<int>& travel) {
        int n=garbage.size();
        int farM=0,farP=0,farG=0;
        int totalTime=0;
        for (int i=0;i<n;++i) {
            for (auto ch:garbage[i]) {
                ++totalTime;
                if (ch=='M') farM=i;
                if (ch=='P') farP=i;
                if (ch=='G') farG=i;
            }
        }
        int m=travel.size();
        vector<int> driveTime(m+1,0);
        driveTime[1]=travel[0];
        for (int i=2;i<=m;++i) driveTime[i]=driveTime[i-1]+travel[i-1];
        totalTime+=driveTime[farM]+driveTime[farP]+driveTime[farG];
        return totalTime;
    }
};

8. 赢得比赛需要的最少训练时长

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

本身不难,但要注意细节。即初始状态已经足够过关的时候,中间变量可能为负,这个时候不能直接加到返回值上,而要判负为0。

代码

class Solution {
public:
    int minNumberOfHours(int initialEnergy, int initialExperience, vector<int>& energy, vector<int>& experience) {
        int ans=0;
        int totalEn=0;
        for (int elem:energy) totalEn+=elem;
        ans+=max(totalEn+1-initialEnergy,0);
        int maxGap=INT_MIN;
        int totalEx=initialExperience;
        for (int elem:experience) {
            int newGap=max(elem+1-totalEx,0);
            maxGap=max(maxGap,newGap);
            totalEx+=elem;
        }
        ans+=maxGap;
        return ans;
    }
};

9. 最大回文数字

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

本身难度不高,但细节比较多,如果在竞赛时,看不到特殊用例,有很多细节会做不出来。

代码

class Solution {
public:
    string largestPalindromic(string num) {
        sort(num.begin(),num.end());
        string mid="";
        string preHalf="";
        int n=num.size();
        if (n==1) return num;
        bool flag=false;
        for (int i=n-2;i>=0;--i) {
            if (num[i]!=num[i+1]) {
                if (mid=="") mid=num[i+1];
            }
            else {
                if (num[i]=='0') {
                    if (flag) preHalf+=num[i];
                } else {
                    flag=true;
                    preHalf+=num[i];
                }
                --i;
                if (i==0&&mid=="") mid=num[0];
            }
        }
        string backHalf(preHalf.rbegin(),preHalf.rend());
        if (preHalf==""&&mid=="") mid="0";
        preHalf+=mid+backHalf;
        return preHalf;
    }
};

10. 最大回文数字

难度

  • Easy
  • Medium
  • Hard

完成情况

  • 独立完成
  • 参考思路
  • 参考答案

思路

我的思路是分情况。如果起始结点是根节点,那么返回最大深度;如果起始结点在较深的子树上,返回其感染自己的子树或到根节点再感染另一棵子树时间的较大值;如果起始结点在较浅的子树上,返回其与根节点的距离加上较深子树的深度和。

但到用例69通不过,我意识到分类是有问题的。

答案的分类是基于起始结点当前位置作为相对位置来讨论,而不是我那样基于绝对位置讨论,因而会更全面和清晰。

参考这篇

代码

class Solution {
public:
    int ans=0;
    int depth=-1;
    int dfs (TreeNode* root,int level,int start) {
        if (root==nullptr) return 0;
        if (root->val==start) depth=level;
        int l=dfs(root->left,level+1,start);
        bool inLeft=depth!=-1;
        int r=dfs(root->right,level+1,start);
        if (root->val==start) ans=max(ans,max(l,r));
        if (inLeft) ans=max(ans,depth-level+r);
        else ans=max(ans,depth-level+l);
        return max(l,r)+1;
    }
    int amountOfTime(TreeNode* root, int start) {
        dfs(root,0,start);
        return ans;
    }
};