leetcode 第 436 场算法比赛

作者: | 更新日期:

没参加比赛,回头做一下。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

背景

我多年来一直在坚持参加 leetcode 周赛,并发布题解。
现在整理了一个leetcode 周赛题解大全
地址为:https://mp.weixin.qq.com/s/64TblIROBAfZNim89kylHw

春节后一周我请了几天假,周日还在路上,所以没有参加比赛。
现在趁着周末,把比赛补一下。

A: 模拟
B: 筛法
C: DP
D: 二分

排名:未参加比赛
代码地址: https://github.com/tiankonguse/leetcode-solutions/tree/master/contest

一、按对角线进行矩阵排序

题意:给一个矩阵,按斜线排序,左下部分降序,右上部分升序。

思路:按题意扫描所有斜线,按要求升序或降序排序,然后按扫描规则填充回矩阵。

二、将元素分配给有约束条件的组

题意:给两个数组 A 和 B, 问对于 A 的每个元素,求 B 中第一个可以整除的下标,如果不存在返回 -1。
数组大小:10^5
值范围:10^5

思路:筛法。

对 B 数组求筛法,计算出 10^5 范围内所有值的答案,然后填充 A 数组的答案即可。
小技巧:只需要对 A 数组最大值范围内的数据进行筛法即可。

vector<int> G(maxVal + 1, N);
int en = elements.size();
for (int i = 0; i < en; i++) {
  int v = elements[i];
  if(v > maxVal) continue;
  if (G[v] != N) continue;
  for (int j = v; j <= maxVal; j += v) {
    G[j] = min(G[j], i);
  }
}

三、统计可以被最后一个数位整除的子字符串数目

题意:给一个数字字符串,问所有子串中,有多少子串可以整除子串的个位数字。

思路:枚举+DP

数字只有10个,0 不可能有答案,枚举1到9。
此时问题转化为了:有多少子串个位为 x 且取模 x 后为 0。

我们可以通过 DP 计算出取模 x 为所有值的个数。

状态定义:nums[i][k] 以第i个位置为个位的所有子串取模 x 值为 k 的个数。
状态转移方程如下:

// v 第 i+1 位的值
nums[i+1][v % p]++; 
nums[i+1][(k * 10 + v) % p] += nums[i+1][k];

四、最大化游戏分数的最小值

题意:给一个一维坐标轴各位置的单位分数,起始在坐标-1,可以左移与右移,进入某个坐标,坐标就加上单位分数。
问最多移动 m 次,问如何移动,才能使得所有坐标里的最小值最大。

思路:最小值最大,二分。

具体来说,是二分答案,判断满足答案至少需要移动多少次。

ll l = 0, r = V * m;  // 只有2个,来回跑,最大值为 V*(m+1)/2
while (l < r) {       // [l, r)
  ll mid = (l + r) / 2;
  if (Check(mid)) {
    l = mid + 1;
  } else {
    r = mid;
  }
}
return r - 1;

最小值确定后,可以计算出坐标轴每个点最少到达的次数。

for (int i = 0; i < n; i++) {
  ll v = points[i];
  nums[i] = (minVal + v - 1) / v;  // 至少需要到达多少次,才能不小于最小值
  sum += nums[i];
  if (sum > m) return false;
}

另外分析移动路线,可以发现多个节点的来回移动与两个节点间来回移动是等价的。

如果结束位置在最后一个节点,当前节点到达 a 次,则下个节点至少需要为 a 次才能来回移动,如果不够,就需要额外补充至 a 次。
来回移动后,记录下个节点占用的次数,从而可以计算出剩余的次数。

// 优先贪心计算,在 n-1 结束,需要多少次操作
for (int i = 0; i < n; i++) {
  if (i + 1 < n) {  // 不是最后一个, 前面与下一个来回移动
    ll now = adds[i] + nums[i];
    ll left = now - uses[i];
    if (left > nums[i + 1]) {
      adds[i + 1] += left - nums[i + 1];
      addNum += left - nums[i + 1];
    }
    uses[i + 1] += left - 1;  // 来回移动
  } else {                   
     // 最后一位
  }
}

对于最后一位,如果有剩余的,需要与前一位来回移动。

if (i + 1 < n) {
  // 不是最后一个, 前面与下一个来回移动
} else {                    // 最后一位
  ll now = adds[i] + nums[i];
  ll left = now - uses[i];
  if (left > 1) {  // 与左边的来回移动
    adds[i - 1] += left - 1;
    addNum += left - 1;
  }
}

结束位置并没有要求一定要在最后,所以对于额外增加的次数,可以反悔贪心。

for (int i = n - 1; i > 0; i--) {
  if (adds[i] > 0) {
    adds[i]--;
    addNum--;
  } else {
    break;
  }
}

最后判断个数是否大于 m。

return sum + addNum <= m;

五、最后

这个比赛最后一题二分后的构造挺不好想的,需要先贪心构造到最后的答案,然后返回贪心,构造出正确的答案。

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号: tiankonguse
公众号ID: tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越