LeetCode周赛记录--第196场周赛

2020年7月5日, LeetCode第196场周赛个人记录。第一次参加leetcode周赛,4道题目完成3道,中国排名163,全球排名377。对我个人来说算是个普通成绩吧。以后打算每两周参加一次周赛,同时利用博客记录下每次的成绩,同时分享一下对于题目的个人想法和感受。

题目1.判断能否形成等差数列

给你一个数字数组 arr 。
如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数列就称为 等差数列 。
如果可以重新排列数组形成等差数列,请返回 true ;否则,返回 false 。
示例 1:
输入:arr = [3,5,1]
输出:true
解释:对数组重新排序得到 [1,3,5] 或者 [5,3,1] ,任意相邻两项的差分别为 2 或 -2 ,可以形成等差数列。
示例 2:
输入:arr = [1,2,4]
输出:false
解释:无法通过重新排序得到等差数列。

题目链接1502.判断能否形成等差数列
第一题算是一道送分题,由等差数列的性质,等差数列相邻两项的差为定值,判断一个数组能否形成等差数列,可以直接sort排序,再遍历数组判断相邻项的差是否为定值。

class Solution {
public:
    bool canMakeArithmeticProgression(vector<int>& arr) {
        sort(arr.begin(), arr.end());
        int differ = arr[1] - arr[0];
        for (int i = 2; i < arr.size(); ++i)
            if (arr[i] - arr[i - 1] != differ)
                return false;
        return true;
    }
};

时间复杂度:时间主要消耗在sort排序中,sort使用快排,时间复杂度为O(nlogn)。
空间复杂度:O(1)

题目2.所有蚂蚁掉下来前的最后一刻

有一块木板,长度为 n 个 单位 。一些蚂蚁在木板上移动,每只蚂蚁都以 每秒一个单位 的速度移动。其中,一部分蚂蚁向 左 移动,其他蚂蚁向 右 移动。
当两只向 不同 方向移动的蚂蚁在某个点相遇时,它们会同时改变移动方向并继续移动。假设更改方向不会花费任何额外时间。
而当蚂蚁在某一时刻 t 到达木板的一端时,它立即从木板上掉下来。
给你一个整数 n 和两个整数数组 left 以及 right 。两个数组分别标识向左或者向右移动的蚂蚁在 t = 0 时的位置。请你返回最后一只蚂蚁从木板上掉下来的时刻。

题目链接:1503.所有蚂蚁掉下来前的最后一刻
比赛的时候被这道题卡了好久,一直到结束都没想出来,一直感觉很可惜,困难的题目都做出来了,被一道中等难度的题卡住。这道题其实很简单,硬要说的话其实是一道脑经急转弯,不然被相遇掉头给迷惑到,因为每一只蚂蚁是一样的,相遇掉头和相遇穿过本质上是相同的,所以这道题就是求哪知蚂蚁离木板边缘最远,我们只要遍历两个数组就可以得出答案。

class Solution {
public:
    int getLastMoment(int n, vector<int>& left, vector<int>& right) {
        int maxVal = 0;
        for (auto x: left)
            maxVal = max(x, maxVal);
        for (auto x: right)
            maxVal = max(n - x, maxVal);
        return maxVal;
    }
};

时间复杂度:O(n)
空间复杂度:O(1)

题目3.统计全 1 子矩形

给你一个只包含 0 和 1 的 rows * columns 矩阵 mat ,请你返回有多少个 子矩形 的元素全部都是 1 。

题目链接1504.统计全1子矩形
这道题也算是运气好,在周赛前正好遇到了相同的题目所以虽然题目很难,但是思路还是很清晰的。看到这种题目首先想到的是暴力法,对遍历每一个元素,以这个元素为矩形的左上角,同时枚举所有可能的长和宽,判断这个矩形是否满足全1这个条件。但是这种方法的时间复杂度为O( m³ n³),m和n是矩阵的行和列,题目中m和n最高可达150,数据规模就达到了10^13 数量级,肯定会超时的。这里提一下,一般来说C/C++的数据规模在10 ^7,超过就会超时。这样就要考虑时间上的优化。这里介绍柱状图方法,时间复杂度可以达到O(m²n)。
首先我们把每个元素当成矩形的右下角,之所以是右下角,是为了让循环从下标0开始递增,符合我们的思路。我们利用动态规划的思想求出每一个元素左侧连续的1的个数,我们用dp[i][j]表示第i行j列的元素连续1的个数。我们可以在O(mn)的复杂度下完成。之后,对每个元素,我们遍历它可能的宽度,为了便于理解,我们用高度来称呼宽度,想象矩形是立起来的。当高度是1时,以当前元素为右下角形成的子矩形的个数就是dp[i][j],这很好理解,这里就不多赘述了,当高度变为2时,以当前元素为右下角形成的子矩形的个数为 min(dp[i][j], dp[i - 1][j],这里可能有点难以理解,但是让我们来看一个例子。

0 1 1
1 1 1

我们以第2行第3列的这个元素为例,dp[2][3] = 3, dp[1][3] = 2,首先这个元素为子矩阵的右下角,高度为1时,dp[2][3]就是可能形成的矩阵个数,当高度为2时,我们可以把高度为2的矩阵当成两个高度为1的矩阵的组合,所以个数为两个dp中小的那一个。由此类推,当高度增加时,我们要继续取最小的dp。

class Solution {
public:
    int numSubmat(vector<vector<int>>& mat) {
        if (mat.empty())        // 判空
            return 0;
        int row = mat.size(), line = mat[0].size();
        int sum = 0;
        vector<vector<int>>dp(row, vector<int>(line + 1, 0)); // 多初始化一列,便于循环中统一计算
        for (int i = 0; i < row; ++i)
        {
            for (int j = 1; j <= line; ++j)
            {
                if (mat[i][j - 1] == 0)
                    dp[i][j] = 0;
                else
                    dp[i][j] = dp[i][j - 1] + 1;        //计算dp

                int minDp = dp[i][j];                   //储存最小的dp                
                for (int height = i; height >= 0 && mat[height][j - 1] != 0; --height)       //遍历height
                {
                    minDp = min(dp[height][j], minDp);
                    sum += minDp;
                }
            }
        }

        return sum;
    }
};

题目4.最多 K 次交换相邻数位后得到的最小整数

给你一个字符串 num 和一个整数 k 。其中,num 表示一个很大的整数,字符串中的每个字符依次对应整数上的各个 数位 。
你可以交换这个整数相邻数位的数字 最多 k 次。
请你返回你能得到的最小整数,并以字符串形式返回。

题目链接:P1505.最多K次交换相邻数位后得到的最小整数
这道题难度不大,首先看到交换相邻的数位,这应该能让我们想到应该算我我们遇见的第一个算法–冒泡排序法。在仔细观察,这道题本质上就是一个限制了交换次数的冒泡排序,按照冒泡排序法的思想,外循环控制当前判断的是哪一位数,内循环寻找出最小的数。只不过普通的冒泡排序法内循环是可以遍历整个数组,而现在我们只能遍历最多k次,所以我们只要多加上一个判断就可以了。
完成了大体的思路后,我们有一些细节要处理,首先我们知道,冒泡排序的处理次数是n * (n - 1) / 2 - 1,所以当k大于这个数时,实际上就是进行了一次冒泡排序,我们可以直接sort然后返回。另外就是交换,我们不需要真的进行交换,我们只要知道,交换后的结果相当于把最后一个数提到了最开头,我们可以用insert和erase实现。最后注意一下判断的边界条件,是否需要加等号。

class Solution {
public:
    string minInteger(string num, int k) {
        int size = num.size();
        if (k >= size * (size - 1) / 2 - 1)         //当k过大时,可以直接sort返回
        {
            sort(num.begin(), num.end());
            return num;
        }

        int head = 0;       // 记录当前在查找哪一位的最小值

        while (k > 0 && head < size)    //外循环
        {
            int minIndex = head;            //寻找最小的数的下标
            for (int i = head; i < size && i - head <= k; ++i)
            {
                if (num[i] < num[minIndex])
                    minIndex = i;
            }
            if (minIndex != head)       //当head就是最小的数时,移动没有意义
            {
                num.insert(num.begin() + head, num[minIndex]);
                num.erase(num.begin() + minIndex + 1);
                k -= minIndex - head;
            }
            ++head;
        }

        return num;
    }
};

总结

第一次参加周赛,也算是知道了人外有人,天外有天,我4道题总用时75分钟,平均下来一道题也有接近20分钟,而且还有一道题目没有做出来,而高排名的大佬4道题一共只用了20分钟。这次周赛算是对我这一个月刷题的总结,希望以后的自己能多多努力。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值