LeetCode 第275场周赛

本次周赛题目比较简单,比赛链接位于这里

No.1 检查是否每一行每一列都包含全部整数

对一个大小为 n x n 的矩阵而言,如果其每一行和每一列都包含从 1 到 n 的 全部 整数(含 1 和 n),则认为该矩阵是一个 有效 矩阵。

给你一个大小为 n x n 的整数矩阵 matrix ,请你判断矩阵是否为一个有效矩阵:如果是,返回 true ;否则,返回 false 。

示例 1:
在这里插入图片描述
输入:matrix = [[1,2,3],[3,1,2],[2,3,1]]
输出:true
解释:在此例中,n = 3 ,每一行和每一列都包含数字 1、2、3 。
因此,返回 true 。

解析

本题给定一个方阵,要求我们判断是否每一行、每一列都包含1-n全部数字。
这道题不需要什么技巧,按照题设判断即可。先逐行遍历,再逐列遍历,依次判断是否包含全部的数字即可。判断的方式,可以通过数组记录每个数字出现个数,或者通过集合记录出现的数字统计数量。
C++代码如下:

 //No 1
  bool checkValid(vector<vector<int>>& matrix) {
    int n = matrix.size();
    for (int i = 0; i < n; ++i) {
      vector<int>m(n, 0);
      for (int j = 0; j < n; ++j) {
        if (m[matrix[i][j] - 1] > 0) return false;
        m[matrix[i][j] - 1]++;
      }
      for (int j = 0; j < n; ++j) {
        if (m[j] == 0) return false;
      }
    }
    for (int i = 0; i < n; ++i) {
      vector<int>m(n, 0);
      for (int j = 0; j < n; ++j) {
        if (m[matrix[j][i] - 1] > 0) return false;
        m[matrix[j][i] - 1]++;
      }
      for (int j = 0; j < n; ++j) {
        if (m[j] == 0) return false;
      }
    }
    return true;
  }

No.2 最少交换次数来组合所有的 1 II

交换 定义为选中一个数组中的两个 互不相同 的位置并交换二者的值。

环形 数组是一个数组,可以认为 第一个 元素和 最后一个 元素 相邻 。

给你一个 二进制环形 数组 nums ,返回在 任意位置 将数组中的所有 1 聚集在一起需要的最少交换次数。

示例 1:

输入:nums = [0,1,0,1,1,0,0]
输出:1
解释:这里列出一些能够将所有 1 聚集在一起的方案:
[0,0,1,1,1,0,0] 交换 1 次。
[0,1,1,1,0,0,0] 交换 1 次。
[1,1,0,0,0,0,1] 交换 2 次(利用数组的环形特性)。
无法在交换 0 次的情况下将数组中的所有 1 聚集在一起。
因此,需要的最少交换次数为 1 。

解析

本题给定一个仅有1和0构成的环形数组,首尾是相连的。我们可以交换任意2个元素,求把所有的1聚集在一起(相邻)需要的最小交换次数。

通过一次遍历我们可以知道数组一共有多少个1,显然,交换的最终目标就是得到这个长度的全1部分。因此我们可以通过滑动窗口,定义一个长度等于1的个数的窗口,这个窗口在整个数组上滑动,在窗口内实现全都是1.为了使得窗口内全部为1,就需要进行交换,将0换走,因次交换次数就是窗口内的0的个数。

考虑到以1开头的滑窗,其交换次数不多于0开头的(小于等于),一次我们可以简单一些,只讨论1开头的窗口位置,统计窗口内的0的个数。

但是,如果我们对于每个滑窗位置,都遍历其全部元素统计0的数量,复杂度会达到 O ( N 2 ) O(N^2) O(N2)超时。因此需要一些优化。
主要有两种方式:

  1. 我们只遍历1开头的窗口,由于窗口不是连续滑动,无法直接动态规划,考虑到数组只有0和1,求和等价于求1的个数,我们遍历一次计算前缀和,通过前缀和相减计算1的个数,那么总数减去1的个数就是0的个数。
  2. 遍历所有的窗口,此时窗口连续滑动,0的个数等于前一个位置的窗口0个数减去滑走的,加上新加入的,标准的动态规划。
    这里将思路1的代码放在下面,思路2实现起来也比较容易。两个思路基本没什么大的区别。
//No 2
  int minSwaps(vector<int>& nums) {
    int num1 = 0,n=nums.size();
    vector<int>sumIn(n + 1, 0);
    for (int i = 0; i < n; ++i) {
      sumIn[i + 1] = sumIn[i] + nums[i];
      num1 += nums[i];
    }
    if (num1 == 0) return 0;
    int ans = n;
    for (int i = 0; i < n; ++i) {
      if (nums[i] == 1) {
        int t = i + num1 - 1,num0=n;
        if (t < n) {
          num0 = num1-(sumIn[t + 1] - sumIn[i]);
        }
        else {
          num0 = num1-(sumIn[n] - sumIn[i] + sumIn[t - n + 1]);
        }
        ans = min(ans, num0);
      }

    }
    return ans;
  }

No.3 统计追加字母可以获得的单词数

给你两个下标从 0 开始的字符串数组 startWords 和 targetWords 。每个字符串都仅由 小写英文字母 组成。

对于 targetWords 中的每个字符串,检查是否能够从 startWords 中选出一个字符串,执行一次 转换操作 ,得到的结果与当前 targetWords 字符串相等。

转换操作 如下面两步所述:

追加 任何 不存在 于当前字符串的任一小写字母到当前字符串的末尾。
例如,如果字符串为 “abc” ,那么字母 ‘d’、‘e’ 或 ‘y’ 都可以加到该字符串末尾,但 ‘a’ 就不行。如果追加的是 ‘d’ ,那么结果字符串为 “abcd” 。
重排 新字符串中的字母,可以按 任意 顺序重新排布字母。
例如,“abcd” 可以重排为 “acbd”、“bacd”、“cbda”,以此类推。注意,它也可以重排为 “abcd” 自身。
找出 targetWords 中有多少字符串能够由 startWords 中的 任一 字符串执行上述转换操作获得。返回 targetWords 中这类 字符串的数目 。

注意:你仅能验证 targetWords 中的字符串是否可以由 startWords 中的某个字符串经执行操作获得。startWords 中的字符串在这一过程中 不 发生实际变更。

示例 1:

输入:startWords = [“ant”,“act”,“tack”], targetWords = [“tack”,“act”,“acti”]
输出:2
解释:

  • 为了形成 targetWords[0] = “tack” ,可以选用 startWords[1] = “act” ,追加字母 ‘k’ ,并重排 “actk” 为 “tack” 。
  • startWords 中不存在可以用于获得 targetWords[1] = “act” 的字符串。
    注意 “act” 确实存在于 startWords ,但是 必须 在重排前给这个字符串追加一个字母。
  • 为了形成 targetWords[2] = “acti” ,可以选用 startWords[1] = “act” ,追加字母 ‘i’ ,并重排 “acti” 为 “acti” 自身。

1 < = s t a r t W o r d s . l e n g t h , t a r g e t W o r d s . l e n g t h < = 5 ∗ 1 0 4 1 <= startWords.length, targetWords.length <= 5 * 10^4 1<=startWords.length,targetWords.length<=5104
1 <= startWords[i].length, targetWords[j].length <= 26
startWords 和 targetWords 中的每个字符串都仅由小写英文字母组成
在 startWords 或 targetWords 的任一字符串中,每个字母至多出现一次

解析

本题给定2组字符串 startWords 和 targetWords ,以及一种操作:即对一个字符串添加一个它本身没有的字母并重排,求targetWords 数组的字符串中有多少个能够通过startWords 中的某个字符串通过一次上述操作得到。

根据题意和提示我们发现,重排指的是任意排列,就是说只要组成的字母一样就视为能够重排得到;而操作第一步是添加一个原先不存在的字母,又知字符串数组的每个字符串中,每个字母至多出现一次,那么这题就可以转化为:targetWords中的字符串st,是否startWords存在一个字符串ss,st恰好比ss多一个字母,除了多出来的字母,剩下的组成一致(顺序无所谓)。由于字符串组成只有26个字母,而数组很长,因此,我们应当把遍历操作放在字母集,而查询放在数组上,因此修改为:对于targetWords中的字符串st,去掉其中一个字母,得到ss,这个ss是否在startWords中。

由于字符串顺序无所谓,为了进一步简化,我们将字母进行编码,每个字符对应一个二进制位,包含该字母这一位就是1,反之是0,这样通过一个32位整型数字,就能够表示一个原来的字符串。
那么单个字母就是只有1位是1的整型,即2的幂。我们将这些单个字母编码构成的整数放在数组中。将startWords的字符串编码后放在集合中,便于O(1)的查询。

接下来对于targetWords中的每个字符串,首先进行编码,然后对于其包含的每个字母,依次剔除,查询剔除后的编码是否在startWords编码集合中,只要找到一个满足的字母和对应的字符串,就表明能够通过一次操作得到;每个字母都尝试过均找不到,就说明这个字符串无法通过操作得到。记录能够得到的字符串综述返回即可。

这里要注意剔除字母一定要是字符串本身就存在的,对于编码后的整数,就是二者做按位与运算,结果等于单个字母的编码,否则会出错。例如编码4代表字母’c’,2代表 ‘b’,4-2=2,但显然’c’不能通过 'b’得到。

C++代码如下:

vector<int>alpha;
    for (int i = 0; i < 26; ++i) {
      alpha.push_back((1 << i));
    }
    unordered_set<int>start;
    for (auto s : startWords) {
      int tmp = 0;
      for (auto c : s) {
        tmp += (1 << (c - 'a'));
      }
      start.insert(tmp);
    }
    int count = 0;
    for (auto s : targetWords) {
      int tmp = 0;
      for (auto c : s) {
        tmp += (1 << (c - 'a'));
      }
      for (auto a : alpha) {
        if ((tmp & a) == a && start.count(tmp ^ a)) {
          ++count;
          break;
        }
      }
    }
    return count;

No.4 全部开花的最早一天

你有 n 枚花的种子。每枚种子必须先种下,才能开始生长、开花。播种需要时间,种子的生长也是如此。给你两个下标从 0 开始的整数数组 plantTime 和 growTime ,每个数组的长度都是 n :

plantTime[i] 是 播种 第 i 枚种子所需的 完整天数 。每天,你只能为播种某一枚种子而劳作。无须 连续几天都在种同一枚种子,但是种子播种必须在你工作的天数达到 plantTime[i] 之后才算完成。
growTime[i] 是第 i 枚种子完全种下后生长所需的 完整天数 。在它生长的最后一天 之后 ,将会开花并且永远 绽放 。
从第 0 开始,你可以按 任意 顺序播种种子。

返回所有种子都开花的 最早 一天是第几天。

示例 1:
在这里插入图片描述

输入:plantTime = [1,4,3], growTime = [2,3,1]
输出:9
解释:灰色的花盆表示播种的日子,彩色的花盆表示生长的日子,花朵表示开花的日子。
一种最优方案是:
第 0 天,播种第 0 枚种子,种子生长 2 整天。并在第 3 天开花。
第 1、2、3、4 天,播种第 1 枚种子。种子生长 3 整天,并在第 8 天开花。
第 5、6、7 天,播种第 2 枚种子。种子生长 1 整天,并在第 9 天开花。
因此,在第 9 天,所有种子都开花。

解析

本题给定了若干花,我们需要先播种,消耗一定的时间,播种成功后的话会自己生长,生长结束会开花。播种过程是“串行”的,同一时间只能有一朵花在播种,但生长过程可以同时进行。我们可以自定义播种的顺序,求最短的全部开花时间。
本题作为最后一题,加上题目的描述,本以为是一道很难的题,但实际上可以通过贪心解决。一种朴素的思想是,既然生长过程可以并行,那我们应该先把生长时间较长的花种下去,这样它自己慢慢生长,与此同时我们种植其他花,是不是比较快呢?
我们以两朵花为例,花1:种植时间,生长时间=a, b, 花2:种植时间,生长时间=c, d,不妨设b>d
如果先1后2,则总时间是 m a x ( a + c + d , a + b ) max(a+c+d,a+b) max(a+c+d,a+b);反之则是 m a x ( c + a + b , c + d ) max(c+a+b,c+d) max(c+a+b,c+d),由于b>d,则 c + a + b > c + b > c + d c+a+b>c+b>c+d c+a+b>c+b>c+d,所以先2后1的时间一定是c+a+b。而c+a+b一定大于a+c+d与a+b,所以无论ac如何,无论具体数字,先1后2更快,也就是先种生长时间长的更快。

综上,我们按照生长时间从大到小对植物排序(种植时间无所谓),然后依次种植即可

C++代码如下:

int earliestFullBloom(vector<int>& plantTime, vector<int>& growTime) {
    vector<pair<int, int>>flowers;
    int n = plantTime.size();
    for (int i = 0; i < n; ++i) {
      flowers.push_back({ plantTime[i],growTime[i] });
    }
    sort(flowers.begin(), flowers.end(), [](pair<int, int>& p1, pair<int, int>p2) {return p1.second > p2.second || (p1.second == p2.second) && (p1.first < p2.first); });
    int day = 0,s=0;
    for (int i = 0; i < n; ++i) {
      int dayi = flowers[i].first + flowers[i].second + s;
      s += flowers[i].first;
      day = max(day, dayi);
    }
    return day;
  }
### LeetCode周赛概述 LeetCode周赛是一项在线编程竞赛活动,旨在帮助程序员提升算法能力并准备技术面试。参与者可以在规定的时间内解决一系列具有挑战性的编程问题[^1]。 ### 参加方法 为了参与LeetCode周赛,用户需先注册一个LeetCode账号,并定期关注官方公告获取最新的赛事通知。比赛通常会在周末举行,在比赛当天登录网站进入指定的比赛页面即可开始作答。对于初次参赛者来说,可能会遇到一些操作上的不熟悉,比如不清楚在哪里提交解答等问题,但随着经验积累这些问题都会迎刃而解[^2]。 ### 比赛时间安排 LeetCode周赛一般固定在北京时间每周日凌晨01:00(UTC/GMT+8)准时开赛,持续时间为两小时。在此期间,选手可以自由选择任意连续的两个小时完成比赛中的题目。需要注意的是,具体的比赛日期和时间可能因节假日等因素有所调整,请务必留意官网发布的最新消息[^4]。 ### 题目类型分析 比赛中涉及的题目种类繁多,涵盖了数据结构、算法计等多个方面。常见的题目形式包括但不限于: - 数组与字符串处理 - 动态规划 - 图论及其应用 - 排序与查找技巧 - 栈队列等高级数据结构的应用 这些题目往往要求较高的逻辑思维能力和扎实的基础知识掌握程度。例如,在某些情况下,一道看似简单的数组运算题也可能隐藏着深层次的数据结构优化思路;而在另一些景下,则需要运用到复杂的动态规划策略来解决问题[^5]。 ```python def example_function(input_data): """ 这是一个示例函数,用于展示如何编写Python代码。 参数: input_data (list): 输入的数据列表 返回: result (int): 计算后的结果值 """ # 处理输入数据... processed_data = sorted(input_data) # 执行核心计算逻辑... result = sum(processed_data[:3]) return result ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值