LeetCode冲刺:Day10

发布于 2022-11-17  1059 次阅读


1. 最长上传前缀

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

第一次遇到数据结构的题。初看确实懵了。但打个草稿之后理解了题意。这里说“视频流”,其实用一个数组就行了。如果当前下标的视频被上传,就标记为1,否则为0。

我最初把 longest() 函数用遍历数组来实现,但是超时了。然后想到,可以用一个 front 来保存当前的最长前缀下标,在每次添加视频进流的时候,由 front+1 这个位置为起始,向后遍历,遇1则 front++ ,遇0就停止。这样节省了重复查找的事件。 longest() 直接返回 front 即可。

代码

class LUPrefix {
private:
    vector<int> stream;
    int front;
public:
    LUPrefix(int n) {
        vector<int> vec(n+2,0);
        stream=vec;
        front=0;
    }

    void upload(int video) {
        ++stream;
        for (int i=front+1;front<stream.size();++i) {
            if (stream[i]==1) ++front;
            else break;
        }
    }

    int longest() {
        return front;
    }
};

2. 所有数对的异或和

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

打个草稿不难发现,最后要求的值,其实是所有数对异或的结果。要知道异或的性质,即0异或任何数都等于原来的数,相同的数异或得0。那么思路就很清晰了。统计下每种数字出现频率,模2为1的则参与异或,最后返回结果。统计数字出现频率,也不用遍历m*n,因为只要知道另一个数组的长度,那么当前数组中元素都要出现它的常数次。为了防止溢出,也对数组长度模2。于是统计数组长度用了m+n 的复杂度,最后异或在最坏条件下的复杂度是 m+n ,总体亦同。但有点奇怪,时间复杂度超过5%……

代码

class Solution {
public:
    int xorAllNums(vector<int>& nums1, vector<int>& nums2) {
        int ans=0;
        int m=nums1.size();
        int n=nums2.size();
        map<long long,int> mp;
        for (auto elem:nums1) mp[elem]+=n%2;
        for (auto elem:nums2) mp[elem]+=m%2;
        for (auto elem:mp) {
            if (elem.second%2==1) ans^=elem.first;
        }
        return ans;
    }
};

3. 满足不等式的数对数目

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

暴力法不出意外超时了。看这篇 。理解了如何用归并排序解这道题, 然后自己凭理解完整写了一遍,顺利通过。

本质上是要求解数组中大于左侧元素值+diff的元素数目,如果用暴力法,显然是n的平方复杂度。而利用归并排序,可以降到nlogn

代码其实跟正常归并排序代码差不多,只不过多了五行用于计算答案。因为对比元素时,左半边和右半边已经分别有序,而两边的元素相对对方而言,相对位置其实并未改变,故只需较少的比较次数,便能算出局部答案,累加即可。

通过这道题,对归并排序有了一定理解。

代码

class Solution {
private:
    long long ans=0;
    void mergeSort(vector<int>& nums,vector<int>& temp,int diff,int left,int right) {
        if (left>=right) return;
        int mid=left+(right-left>>1);
        mergeSort(nums,temp,diff,left,mid);
        mergeSort(nums,temp,diff,mid+1,right);

        for (int i=left,j=mid+1;j<=right;++j) {
            while (i<=mid&&nums[i]<=nums[j]+diff) ++i;
            ans+=(i-left);
        }

        for (int i=left,j=mid+1,k=left;i<=mid||j<=right;++k) {
            if (i>mid) temp[k]=nums[j++];
            else if (j>right) temp[k]=nums[i++];
            else if (nums[i]<nums[j]) temp[k]=nums[i++];
            else temp[k]=nums[j++];
        }
        for (int i=left;i<=right;++i) nums[i]=temp[i];
    }
public:
    long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int diff) {
        ans=0;
        int n=nums1.size();
        vector<int> nums(n);
        vector<int> temp(n);
        for (int i=0;i<n;++i) nums[i]=nums1[i]-nums2[i];
        mergeSort(nums,temp,diff,0,n-1);
        return ans;
    }
};

4. 按身高排序

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

建立一个由身高到姓名的映射,由于身高和姓名都唯一,所以可行。

降序排列身高数组,然后按对应关系依次填入names数组即可。

代码

class Solution {
public:
    vector<string> sortPeople(vector<string>& names, vector<int>& heights) {
        int n=names.size();
        map<int,string> mp;
        for (int i=0;i<n;++i) mp[heights[i]]=names[i];
        sort(heights.rbegin(),heights.rend());
        for (int i=0;i<n;++i) names[i]=mp[heights[i]];
        return names;
    }
};

5. 按位与最大的最长子数组

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

完全是脑筋急转弯。看到这题的时候,特意去搜了下按位与的性质,但没有什么突破性发现,还想着用DP做。看了下题解的思路,原来就是找最大值的连续长度!因为两个数字按位与,结果一定小于他们俩中的任何一个!所以只要找到最大值,然后计算这个元素在数组中的最大连续长度,就做出来了。

代码

class Solution {
public:
    int longestSubarray(vector<int>& nums) {
        int n=nums.size();
        int maxNum=INT_MIN;
        for (int i=0;i<n;++i) maxNum=nums[i]>maxNum?nums[i]:maxNum;
        int maxLen=1;
        int len=0;
        if (nums[0]==maxNum) len=1;
        for (int i=1;i<n;++i) {
            if (nums[i]==maxNum) {
                ++len;
                maxLen=max(len,maxLen);
            } else len=0;
        }
        return maxLen;
    }
};

6. 找到所有好下标

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

n平方的写法超时了,逻辑本身没问题。

题解做到了n的复杂度。巧妙的地方在于,不是每次确定一个i,再去判断它的左右是否满足条件。而是先正向遍历一遍,判断每个元素左侧的最长连续非递增序列长度,再反向遍历一遍,计算每个元素右侧的最长连续非递减子序列长度。最后再遍历一遍,如果当前坐标在两个数组的左右最长序列长度都大于等于k,则将其加入答案。

这题考虑得不太够。

代码

class Solution {
public:
    vector<int> goodIndices(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int> res;
        vector<int> back(n,1);
        vector<int> forward(n,1);
        int pre=INT_MIN;
        for (int i=n-2;i>=k;--i) {
            if (nums[i+1]<=pre) back[i]=back[i+1]+1;
            pre=nums[i+1];
        }
        pre=INT_MIN;
        for (int i=1;i<n-k;++i) {
            if (nums[i-1]<=pre) forward[i]=forward[i-1]+1;
            pre=nums[i-1];
        }
        for (int i=k;i<n-k;++i) {
            if (back[i]>=k&&forward[i]>=k) res.push_back(i);
        }
        return res;
    }
};

7. 好路径的数目

  • TODO: 写个图的数据结构,dfs完成。

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

代码

8. 最小偶倍数

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

两分钟写完。首先写个欧几里得算法计算最大公因数。再写个函数用于计算最小公倍数。最小公倍数等于a和b的积除以两者的最大公因数。

代码

class Solution {
private:
    int gcd(int a,int b) {
        if (a<b) {
            a=a^b;
            b=a^b;
            a=a^b;
        }
        if (b==0) return a;
        else return gcd(b,a%b);
    }
    int lcm(int a,int b) {
        return (a*b)/gcd(a,b);
    }
public:
    int smallestEvenMultiple(int n) {
        return lcm(n,2);
    }
};

9. 最长的字母序连续子字符串的长度

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

实际感觉要归到Easy里。

遍历一遍,如果当前字符跟上一个字符刚好差1,就加一,否则计数归一。

代码

class Solution {
public:
    int longestContinuousSubstring(string s) {
        int n=s.size();
        int ans=1;
        int cnt=1;
        for (int i=1;i<n;++i) {
            if (s[i]-s[i-1]==1) {
                cnt++;
                ans=cnt>ans?cnt:ans;
            }
            else cnt=1;
        }
        return ans;
    }
};

10. 反转二叉树的奇数层

难度

  • Easy
  • Medium
  • Hard

完成情况

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

思路

之前有类似的,需要对树的整层元素进行操作的题目。这次用到了那会儿学的方法。

dfs附加一个深度参数,用二维数组存储结点,同层结点在一个一维数组内。结点全部放进数组里之后,再操作奇数层的元素。

判断层数是否为奇数用按位与运算,交换元素用三次异或,改善效率。

代码

class Solution {
private:
    vector<vector<TreeNode*>> tree;
    void dfs(TreeNode* root, int depth) {
        if (root==nullptr) return;
        if (tree.size()<depth+1) tree.push_back(vector<TreeNode*>());
        tree[depth].push_back(root);
        dfs(root->left,depth+1);
        dfs(root->right,depth+1);
    }
public:
    TreeNode* reverseOddLevels(TreeNode* root) {
        tree.clear();
        dfs(root,0);
        int n=tree.size();
        for (int i=0;i<n;++i) {
            if (i&1==1) {
                int m=tree[i].size();
                int k=m>>1;
                for (int j=0;j<k;++j) {
                    tree[i][j]->val=tree[i][j]->val^tree[i][m-1-j]->val;
                    tree[i][m-1-j]->val=tree[i][j]->val^tree[i][m-1-j]->val;
                    tree[i][j]->val=tree[i][j]->val^tree[i][m-1-j]->val;
                }
            }
        }
        return tree[0][0];
    }
};