LeetCode第15场双周赛(Biweekly Contest 15)解题报告

第一次参加双周赛,不愧是双周赛的简单,本来可以 AK 的,但是第三题理解了很久,不过最后还是想出了怎么做,开shang。

第一题:暴力模拟。

第二题:暴力枚举或者先排序再枚举。

第三题:DFS。

第四题:动态规划DP。

详细题解如下。


1. 有序数组中出现次数超过25%的元素(Element Appearing More Than 25 in Sorted Array)

           AC代码(方法一   C++)

           AC代码(方法二   C++)

2. 删除被覆盖区间(Remove Covered Intervals)

           AC代码(方法一  暴力枚举  C++)

           AC代码(方法二  排序+遍历  C++)

3.字母组合迭代器(Iterator for Combination)

           AC代码(C++)

4.下降路径最小和  II(minimum-falling-path-sum-ii)

           AC代码( C++)


LeetCode第15场双周赛地址:

https://leetcode-cn.com/contest/biweekly-contest-15/


1. 有序数组中出现次数超过25%的元素(Element Appearing More Than 25 in Sorted Array)

题目链接

https://leetcode-cn.com/problems/element-appearing-more-than-25-in-sorted-array/

题意

给你一个非递减的 有序 整数数组,已知这个数组中恰好有一个整数,它的出现次数超过数组元素总数的 25%。

请你找到并返回这个整数

示例 1:

输入:arr = [1,2,2,6,6,6,6,7,10]
输出:6

提示:

  • 1 <= arr.length <= 10^4
  • 0 <= arr[i] <= 10^5

解题思路

假设arr 数组的长度为 N,其中数字中最大数为 M。

方法一:

根据数据范围,数字最小为0,最大为10^5,一开始想的是,利用开一个一维数组,用于记录每个数出现的次数,然后再遍历该一维数组,判断哪个数出现了超过25%。该种方法的时间复杂度是 O(N+M),空间复杂度是 O(M)。

方法二:

从题目或者题意中我们可以发现给出的 arr 数组是有序的,即是一个非递减的数组。那边我们计算某个数出现的次数时,可以避免使用开一维数组来存储次数。因为是有序的,所以相同的数肯定是相邻的。因此我们可以

记录一开始的数,val,出现次数为cnt = 1,然后判断下一个数,是否和当前记录的 val 相同。

  • 如果相同,则val 不变,同时 cnt++,并且判断此时该数出现的次数,是不是已经超过25%,如果是这个就是答案。
  • 如果不同,那就次数该数记为 val,并且重新把 cnt = 1,重新开始。

接着判断下一个数。

因此该方法的时间复杂度是O(N),空间复杂度为 O(1)。

AC代码(方法一   C++)

class Solution {
public:
    int findSpecialInteger(vector<int>& arr) {
        int cnt[100010];
        memset(cnt, 0, sizeof(cnt));
        int ans = 0;
        int len = arr.size();
        for(int num: arr)
        {
            cnt[num]++;
        }
        for(int i = 0;i < 100010;i++)
        {
            if(cnt[i]*4 > len)
            {
                ans = i;
                break;
            }
        }
        return ans;
    }
};

AC代码(方法二   C++)

class Solution {
public:
    int findSpecialInteger(vector<int>& arr) {
        int n = arr.size();
        if(n==1) return arr[0];
        int cnt = 1;
        int val = arr[0];
        int ans;
        for(int i = 1;i < n;i++)
        {
            if(val == arr[i])
            {
                cnt++;
                if(cnt*4 > n)
                {
                    ans = val;
                    break;
                }
            }
            if(val != arr[i])
            {
                val = arr[i];
                cnt = 1;
            }
        }
        return ans;
    }
};

2. 删除被覆盖区间(Remove Covered Intervals)

题目链接

https://leetcode-cn.com/problems/remove-covered-intervals/

题意

给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。

只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。

在完成所有删除操作后,请你返回列表中剩余区间的数目。

示例 1:

输入:intervals = [[1,4],[3,6],[2,8]]
输出:2
解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了。

提示:

 

  • 1 <= intervals.length <= 1000
  • 0 <= intervals[i][0] < intervals[i][1] <= 10^5
  • 对于所有的 i != j:intervals[i] != intervals[j]

 

解题思路

方法一:

拿到题目,一开始的想法是,对于某一个区间,暴力枚举所有其他区间,判断该区间有没有被覆盖。

该方法的时间复杂度是 O(N^2),根据题目的数据大小,可以判断,不会超时,因此该方法可以使用。

方法二:

我们想有没有方法可以优化方法一,那么看看能不能降低时间复杂度。

对于已给的区间数组,我们可以先进行排序。如示例1所给的区间,[[1,4],[3,6],[2,8]],经过升序排序(先按区间第一个数升序排序,如果第一个数相同,就按第二个数升序排序),得到的已排序为 [[1,4],[2,8],[3,6]]。

那么对于判断某一个区间是否被覆盖,比如 [3, 6],因为该区间不是第一个区间,因此对于区间第一个数 3 而言,前面区间的第一个数一定都是小于等于 3 的(因为是升序排序)。

因此重点在于判断区间的第二个数 6 ,是不是前面区间的第二个数中,有大于等于 6的。那么我们可以利用一个变量,存储前面出现的所有区间的第二个数的最大值。示例1中是 8,因此大于等于6,所以该区间被覆盖了。

所以该方法的是,先排序,从而只需要遍历一次即可。时间复杂度是 O(N*logN + N)。

AC代码(方法一  暴力枚举  C++)

class Solution {
public:
    int removeCoveredIntervals(vector<vector<int>>& intervals) {
        
        int n = intervals.size();
        int ans = n;
        
        for(int i = 0;i < n;i++)
        {
            int a = intervals[i][0], b = intervals[i][1];
            for(int j = 0;j < n;j++)
            {
                if(j == i)
                    continue;

                int c = intervals[j][0], d = intervals[j][1];
                if(c<=a && b<=d)
                {
                    --ans;
                    break;
                }
            }
        }
        return ans;
    }
};

AC代码(方法二  排序+遍历  C++)

class Solution {
public:
    int removeCoveredIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end());

        int n = intervals.size();
        int ans = n;

        int val = intervals[0][1];

        for(int i = 1;i < n;i++)
        {
            if(val >= intervals[i][1])
            {
                --ans;
            }
            val = max(val, intervals[i][1]);
        }
        return ans;
    }
};

3.字母组合迭代器(Iterator for Combination)

题目链接

https://leetcode-cn.com/problems/iterator-for-combination/

题意

请你设计一个迭代器类,包括以下内容:

  • 一个构造函数,输入参数包括:一个 有序且字符唯一 的字符串 characters(该字符串只包含小写英文字母)和一个数字 combinationLength 。
  • 函数 next() ,按 字典序 返回长度为 combinationLength 的下一个字母组合。
  • 函数 hasNext() ,只有存在长度为 combinationLength 的下一个字母组合时,才返回 True;否则,返回 False。

示例 1:

CombinationIterator iterator = new CombinationIterator("abc", 2); // 创建迭代器 iterator

iterator.next(); // 返回 "ab"
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 "ac"
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 "bc"
iterator.hasNext(); // 返回 false

提示:

  • 1 <= combinationLength <= characters.length <= 15
  • 每组测试数据最多包含 10^4 次函数调用。
  • 题目保证每次调用函数 next 时都存在下一个字母组合。

 

解题分析

题目的意思是,先执行一个类,然后的进行类中的两个函数调用,要求我们得到对应的返回值。

先理解题目中的几个定义,下一个字母组合,其实就是一个排列组合,但是是按照字典序的顺序得到的,比如示例1中的,长度为 2 ,说明排列组合得到的string 的长度要为 2。即 ab,ac,bc。按字典序顺序。

因此 next() 函数的作用就是,按字典序的顺序输出排列组合的可能。

hasNext() 函数的作用是,判断还有没有存在的排列组合,比如当我们已经 next() 是 ac,因为后面还有,所以无论我们调用几次 hasNext() 函数,都是返回 true。而当调用 next() 是 bc时,无论我们调用几次 hasNext() 函数,都是返回 false。

因此这道题,其实就是当调用类的时候,我们得到了所有的排列组合,同时按字典顺序排好,放在一个数组中。这样子无论是调用哪个函数,只要相对应从这个数组中取出或者进行判断即可。

因此重点在于,如果得到所有的排列组合,那其实就是DFS。

按照题目的题意,我们可以知道,给定的 characters 是有序的,所以我们就按照顺序取,那就是字典序从小到大。比如示例1,abc,那么先从第一个 a ,剩下按顺序取,只要每次取都是从后面取,那就是字典序顺序。

所以我们的DFS,有三个参数,首先是要存储这种情况下组合成的字符串 “”,还有对应此时字符串的长度(用于判断DFS是否结束,当 == combinationLength,说明结束,因为此时组合的长度符合了要求),还有一个此时要取得字符要从原来 characters 得哪个位置开始,这样子取出来得才会是按照字典序。

剩下的就是简单的DFS + 回溯了。

当得到所有可能的排列组合的数组时,只要调用 next(),我们就从中按顺序输出。只要调用 hasNext(),只要判断此时输出还有没有可以输出的。我们可以用一个全局变量index表示输出的下标,如果是 next(),我们就输出的同时,index++。如果是 hasNext(),我们只要判断 index此时等不等于 所有可能的排列组合的数组的长度,如果等于,说明没有了,那就是false。否则true。

AC代码(C++)

class CombinationIterator {
public:
    string cha;
    int com;
    
    int vis[20];
    vector<string> res;
    int index = 0;

    CombinationIterator(string characters, int combinationLength) {
        cha = characters;
        com = combinationLength;  // 变成全局变量,方便处理,把局部变为全局变量,新建一个全局变量赋值
        
        memset(vis, 0, sizeof(vis));
        dfs("", 0, 0);
    }
    
    string next() {
        return res[index++];
    }
    
    bool hasNext() {
        if(index == res.size())
            return false;
        return true;
    }
    
    void dfs(string str, int len, int loc)
    {
        if(len == com)
        {
            res.push_back(str);  // 此时str是一种组合
            return;
        }
        for(int i = loc; i < cha.size();i++)
        {
            if(vis[i]==0)  // 还要判断这个字符有没有被用过
            {
                vis[i] = 1;
                string temp = str;
                str += cha[i];
                dfs(str, len+1, i+1);  // 下一个

                vis[i] = 0;  // 回溯
                str = temp;  // 把原来的值重新赋予
            }
        }
    }
};

/**
 * Your CombinationIterator object will be instantiated and called as such:
 * CombinationIterator* obj = new CombinationIterator(characters, combinationLength);
 * string param_1 = obj->next();
 * bool param_2 = obj->hasNext();
 */

 


4.下降路径最小和  II(minimum-falling-path-sum-ii)

题目链接

https://leetcode-cn.com/problems/minimum-falling-path-sum-ii/

题意

给你一个整数方阵 arr ,定义「非零偏移下降路径」为:从 arr 数组中的每一行选择一个数字,且按顺序选出来的数字中,相邻数字不在原数组的同一列。

请你返回非零偏移下降路径数字和的最小值。

示例 1:

输入:arr = [[1,2,3],[4,5,6],[7,8,9]]
输出:13
解释:
所有非零偏移下降路径包括:
[1,5,9], [1,5,7], [1,6,7], [1,6,8],
[2,4,8], [2,4,9], [2,6,7], [2,6,8],
[3,4,8], [3,4,9], [3,5,7], [3,5,9]
下降路径中数字和最小的是 [1,5,7] ,所以答案是 13 。

提示:

  • 1 <= arr.length == arr[i].length <= 200
  • -99 <= arr[i][j] <= 99

解题分析

先理解题目意思,从 arr 数组中的每一行选择一个数字,且按顺序选出来的数字中,相邻数字不在原数组的同一列。表示每一行取一个数,同时下一行取出的数,不能和上一行取出的数在同一列。

因为是要最小值,因此我们考虑能不能使用动态规划DP来做。

动态规划最重要的几个:

1、先定义状态,dp[i][j] 表示,在 [i][j] 位置上非零偏移下降路径数字和的最小值。

2、状态转移方程

示例1,
1 2 3
4 5 6
7 8 9

对应 4 而言,dp[i][j] = 4 + 上一行中除了相同列中最小的 = 4 + 2 = 6
对于 5 而言,dp[i][j] = 5 + 1 = 6
对于 6 而言,dp[i][j] = 6 + 1 = 7

因此 dp[i][j] = arr[i][j] + min(dp[i-1][k : 0 到 m-1 除了 j]) (上一行中,除了相同列的最小值)

3、为了方便处理, dp的大小是 n+1行,m+1列,第 0 行的初始值为 0。

 

最后得到了 dp[n][ 1 到 m] ,这个只是从上往下,到了最后一行的时候,每个列的最小值,我们再从中取最小值,就是最后的答案。

 

AC代码( C++)

class Solution {
public:
    int dp[210][210];
    
    int minFallingPathSum(vector<vector<int>>& arr) {
        
        int n = arr.size();
        int m = arr[0].size();
        memset(dp, 0 ,sizeof(dp));
        
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= m;j++)
            {
                int minVal = 99*210;
                for(int k = 1;k <= m;k++)
                {
                    if(k==j)
                        continue;
                    minVal = min(minVal, arr[i-1][j-1] + dp[i-1][k]);
                }
                dp[i][j] = minVal;
            }
        }
        int ans = 99*210;  // 枚举最小值,所以设最大值,数据中每个数最大99,最大行为200,所以这个就是最大值。
        for(int j = 1;j <= m;j++)
        {
            ans = min(ans, dp[n][j]);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值