前一段时间比较忙,而且做这个对于我来说挺耗时间的,已经间隔了几期的没做总结了,后面有机会补齐。而且本来做这个的目的就是为了防止长时间不做把编程拉下,不在追求独立作出所有题了。以后完赛后稍微尝试下,做不出来的直接放弃。
第一题:问题
问题:900. RLE 迭代器
编写一个遍历游程编码序列的迭代器。
迭代器由 RLEIterator(int[] A)
初始化,其中 A
是某个序列的游程编码。更具体地,对于所有偶数i
,A[i]
告诉我们在序列中重复非负整数值 A[i + 1]
的次数。
迭代器支持一个函数:next(int n)
,它耗尽接下来的 n
个元素(n >= 1
)并返回以这种方式耗去的最后一个元素。如果没有剩余的元素可供耗尽,则 next
返回 -1
。
例如,我们以 A = [3,8,0,9,2,5]
开始,这是序列 [8,8,8,5,5]
的游程编码。这是因为该序列可以读作 “三个零,零个九,两个五”。
示例:
输入:["RLEIterator","next","next","next","next"], [[[3,8,0,9,2,5]],[2],[1],[1],[2]]
输出:[null,8,8,5,-1]
解释:
RLEIterator 由 RLEIterator([3,8,0,9,2,5]) 初始化。
这映射到序列 [8,8,8,5,5]。
然后调用 RLEIterator.next 4次。
.next(2) 耗去序列的 2 个项,返回 8。现在剩下的序列是 [8, 5, 5]。
.next(1) 耗去序列的 1 个项,返回 8。现在剩下的序列是 [5, 5]。
.next(1) 耗去序列的 1 个项,返回 5。现在剩下的序列是 [5]。
.next(2) 耗去序列的 2 个项,返回 -1。 这是由于第一个被耗去的项是 5,
但第二个项并不存在。由于最后一个要耗去的项不存在,我们返回 -1。
提示:
0 <= A.length <= 1000
A.length
是偶数。0 <= A[i] <= 10^9
- 每个测试用例最多调用
1000
次RLEIterator.next(int n)
。 - 每次调用
RLEIterator.next(int n)
都有1 <= n <= 10^9
。
链接:https://leetcode-cn.com/contest/weekly-contest-101/problems/rle-iterator/
分析:
看到例子没怎么想,直接试图构建原始数据,然后next更新下标,根据下标直接返回,结果测试中数据很大,懒惰,想当然了。
最终做法是将原始数据建立坐标和值的映射关系,一个data数组,一个index数组(其实就相当于map),然后有变量记录下标,下标值大于index值后,更新到下一个,如果在其范围内,返回对应的数字。
比如例子给出的数据,处理后是:
3 8
5 5
表示坐标3一下的对应值是8,5一下的对应值是5。
AC Code:
class RLEIterator {
public:
vector<long long> datas; //保存数据
vector<long long> sizes; //保存坐标
long long location; //数组坐标
long long datalocation; //当前数字坐标
long long size; //总个数
int endflag; //一旦某个坐标返回-1,后面next都返回-1,做一个优化
public:
RLEIterator(vector<int> A)
{
this->location = 0; this->datalocation = 0; this->size = 0; this->endflag = 0; for (int i = 0; i < A.size(); i+=2) { this->size += A[i]; if (A[i] == 0) { continue; } if (i == 0) { this->sizes.emplace_back(A[i]); } else { this->sizes.emplace_back(A[i] + this->sizes[this->sizes.size()-1]); } this->datas.emplace_back(A[i+1]); } } int next(int n) { if (this->endflag == 1) { return -1; } if (this->datalocation + n>=this->size) { this->endflag = 1; return -1; } this->datalocation += n; while (this->datalocation > this->sizes[this->location]) { this->location += 1; } return this->datas[this->location]; } };
其他:
1.第一code
class RLEIterator {
int[] a;
int p = 0;
public RLEIterator(int[] A) {
a = A;
p = 0;
}
public int next(int n) {
if(p >= a.length)return -1;
n--;
int ret = -1;
while(n > 0){
if(p >= a.length)return -1;
int ex = Math.min(a[p], n);
n -= ex;
a[p] -= ex;
if(a[p] > 0){
break;
}else{
p += 2;
}
}
while(p < a.length && a[p] == 0)p += 2;
if(p < a.length)ret = a[p+1];
n = 1;
while(n > 0){
if(p >= a.length)return -1;
int ex = Math.min(a[p], n);
n -= ex;
a[p] -= ex;
if(n == 0)break;
p += 2;
}
return ret;
}
}
2.中间有遇到一个问题,思路什么都多,提交错误,然后调试发现数据范围int是不够的。
第二题:901. 股票价格跨度
问题:
编写一个 StockSpanner
类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。
今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
例如,如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85]
,那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]
。
示例:
输入:["StockSpanner","next","next","next","next","next","next","next"], [[],[100],[80],[60],[70],[60],[75],[85]]
输出:[null,1,1,1,2,1,4,6]
解释:
首先,初始化 S = StockSpanner(),然后:
S.next(100) 被调用并返回 1,
S.next(80) 被调用并返回 1,
S.next(60) 被调用并返回 1,
S.next(70) 被调用并返回 2,
S.next(60) 被调用并返回 1,
S.next(75) 被调用并返回 4,
S.next(85) 被调用并返回 6。
注意 (例如) S.next(75) 返回 4,因为截至今天的最后 4 个价格
(包括今天的价格 75) 小于或等于今天的价格。
提示:
- 调用
StockSpanner.next(int price)
时,将有1 <= price <= 10^5
。 - 每个测试用例最多可以调用
10000
次StockSpanner.next
。 - 在所有测试用例中,最多调用
150000
次StockSpanner.next
。 - 此问题的总时间限制减少了 50%。
链接:https://leetcode-cn.com/contest/weekly-contest-101/problems/online-stock-span/
分析:
如果前一天的股票不大于今天的股票,就可以将其添加的今天的上面,然后将前一天的边界处再次进行判断。
如给出的例子中,
1
2
3
4
5
6
7
100
80
60
70
60
75
85
第一天100,最大连续天数1
第二天80,小于昨天,最大连续天数1
第三条60,小于昨天,最大连续天数1
第四天70,大于昨天,最大连续天数为1+昨天的连续天数1=2,同时查看前一天的80,大于70,第四天最大连续天数2
第五天60,小于昨天,最大连续天数1
第六天75,大于昨天,加上昨天的连续天数1+1,同时查看边界,即第四天,70<75,加上第四天的连续长度2,查看这一天的边界,即第二天,80>75,则最终最大连续天数为1+1+2=4
第七天85,大于昨天,最大连续天数1+4,查看前一天的边界,即第2天,80<85,加上改天的长度1,查看对比边界100>85,最终连续长度1+4+1=6
AC Code:
class StockSpanner {
public:
private:
vector<long long> datas;
vector<long long> longest;
public:
StockSpanner()
{
}
int next(int price)
{
if (this->datas.size() == 0)
{
//diyici
this->datas.emplace_back(price);
this->longest.emplace_back(1);
return 1;
}
else
{
this->datas.emplace_back(price);
if (this->datas[this->datas.size() - 2] > price)
{
this->longest.emplace_back(1);
return 1;
}
else
{
int localindex = this->longest.size()-1;
int ret = 1+this->longest[localindex];
localindex -= this->longest[localindex];
while (localindex>=0 && price>= this->datas[localindex])
{
ret += this->longest[localindex];
localindex -= this->longest[localindex];
}
this->longest.emplace_back(ret);
return ret;
}
}
}
};
其他:
1.第一code
class StockSpanner {
int[] stack;
int[] value;
int sp;
int gen = 0;
public StockSpanner() {
stack = new int[11000];
value = new int[11000];
sp = 0;
gen = 0;
}
public int next(int price) {
while(sp > 0 && value[sp-1] <= price){
sp--;
}
int ret = gen - (sp == 0 ? -1 : stack[sp-1]);
stack[sp] = gen++;
value[sp] = price;
sp++;
return ret;
}
}
第三题:最大为 N 的数字组合
问题:
我们有一组排序的数字 D
,它是 {'1','2','3','4','5','6','7','8','9'}
的非空子集。(请注意,'0'
不包括在内。)
现在,我们用这些数字进行组合写数字,想用多少次就用多少次。例如 D = {'1','3','5'}
,我们可以写出像 '13', '551', '1351315'
这样的数字。
返回可以用 D
中的数字写出的小于或等于 N
的正整数的数目。
示例 1:
输入:D = ["1","3","5","7"], N = 100
输出:20
解释:
可写出的 20 个数字是:
1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.
示例 2:
输入:D = ["1","4","9"], N = 1000000000
输出:29523
解释:
我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,
81 个四位数字,243 个五位数字,729 个六位数字,
2187 个七位数字,6561 个八位数字和 19683 个九位数字。
总共,可以使用D中的数字写出 29523 个整数。
提示:
D
是按排序顺序的数字'1'-'9'
的子集。1 <= N <= 10^9
链接:https://leetcode-cn.com/contest/weekly-contest-101/problems/numbers-at-most-n-given-digit-set/
分析:
1.如果数字位数低于目标数字,肯定满足要求
2.如果位数相同,如果首位小于目标数字最高位,则剩下数字可以随意组合
通用的,如果最高位数字、次高位数字相同,剩下的位数可以随意组合,如此递归直到数字完全相同。
3.n个数字,组成m位数字,有n^m种组发。
AC Code:
class Solution {
public:
int atMostNGivenDigitSet(vector<string>& D, int N)
{
//先组成所有的数字,然后挑选满足条件的结果
//首先拿到N的位数length,从D中挑选任意数字累计重复length次,得到所有可能的数字
//大小为D.size^length,太多了,需要优化
//关键是位数相同的一个,低于的位数可以直接计算指数,位数低一定满足
//更关键的是最高位,
long long ret = 0;
vector<int> datas;
for (long long i = 0; i < D.size(); i++)
{
datas.emplace_back(D[i][0] - '0');
}
if (GetLength(N) == 1)
{
return GetLessOrEqlN(datas, N).size();
}
long long num1 = GetSameLengthNums(datas, N);
long long num2 = GetLowLengthNums(datas.size(), N / 10);
ret = num1 + num2;
return ret;
}
long long GetLowLengthNums(int size, int N)
{
long long length = GetLength(N);
if (size == 1) //1个数字组成任何位数数字都只有1种组发
{
return length;
}
long long ret = 0;
while (length>0)
{
ret += (int)pow(size, length);
length--;
}
return ret;
}
long long GetSameLengthNums(vector<int> datas, int N)
{
long long ret = 0;
while (N)
{
if (N < 10)
{
ret += GetLessOrEqlN(datas, N).size(); //如果是10以下的数字,直接找到所有不大于该数的个数即可
break;
}
int length = GetLength(N);
int num = N / ((int)pow(10, length-1));
ret += GetLessN(datas, num).size()*(int)pow(datas.size(), length-1);
int findflag = 0; //如果高位有相同的,还需要对比次高位,直到拼出相等数字
for (int i = 0; i < datas.size(); i++)
{
int tmp = datas[i] - num;
if (tmp==0)
{
findflag = 1;
break;
}
}
if (findflag == 0)
{
//cout << "no target,break" << endl;
break;
}
N %= ((int)pow(10, length - 1));
if (GetLength(N) + 1 != length)
{
break;
}
}
return ret;
}
vector<int> GetLessN(vector<int> org, int n)
{
vector<int> ret;
for (int i = 0; i < org.size(); i++)
{
if (org[i] < n)
{
ret.emplace_back(org[i]);
}
}
return ret;
}
vector<int> GetLessOrEqlN(vector<int> org, int n)
{
vector<int> ret;
for (int i = 0; i < org.size(); i++)
{
if (org[i] <= n)
{
ret.emplace_back(org[i]);
}
}
return ret;
}
int GetLength(int N)
{
int ret = 0;
while (N)
{
ret++;
N /= 10;
}
return ret;
}
};
其他:
1.第一code
class Solution {
public int atMostNGivenDigitSet(String[] D, int N) {
int mask = 0;
for(String s : D){
mask ^= 1<<s.charAt(0)-'0';
}
char[] s = Integer.toString(N+1).toCharArray();
long dp = 0;
int e = 1;
for(int i = 0;i < s.length;i++){
dp *= Integer.bitCount(mask);
for(int j = 1;j < s[i]-'0';j++){
if(mask<<~j<0){
dp+=e;
}
}
if(i > 0){
dp += Integer.bitCount(mask);
}
if(mask<<~(s[i]-'0')>=0){
e = 0;
}
}
return (int)dp;
}
}
直接将N+1,避免了==的额外处理,这个思路非常好。
2.手残将复制写作==,折腾了半天才发现,单步调试直接优化跳过了,还以为数据类型问题导致不能直接对比。
3.做的时候大体code完成之后简单测试通过,提价错误,解决提交,又有错误,再次解决,感觉在不停的在打补丁,一方面说明思路不完整,没能直接覆盖到所有情况,另一方面进一步暴露出测试数据设计上的不足,之前就有发现这个问题,不过感觉没多少提高,实际上在周赛的时候一般只是测试下例子,基本没有构造数据测试,关键是没时间,其次能力不足,难以短时间构建出合理的数据。好在编写的测试函数能够将所有的测试用例都保存下,一旦修改code可以将之前测过的所有数据测一遍,避免解决一个问题导致前面的测例failed。
第四题:DI 序列的有效排列
问题:
我们给出 S
,一个源于 {'D', 'I'}
的长度为 n
的字符串 。(这些字母代表 “减少” 和 “增加”。)
有效排列 是对整数 {0, 1, ..., n}
的一个排列 P[0], P[1], ..., P[n]
,使得对所有的 i
:
- 如果
S[i] == 'D'
,那么P[i] > P[i+1]
,以及; - 如果
S[i] == 'I'
,那么P[i] < P[i+1]
。
有多少个有效排列?因为答案可能很大,所以请返回你的答案模 10^9 + 7
.
示例:
输入:"DID"
输出:5
解释:
(0, 1, 2, 3) 的五个有效排列是:
(1, 0, 3, 2)
(2, 0, 3, 1)
(2, 1, 3, 0)
(3, 0, 2, 1)
(3, 1, 2, 0)
提示:
1 <= S.length <= 200
S
仅由集合{'D', 'I'}
中的字符组成。
链接:https://leetcode-cn.com/contest/weekly-contest-101/problems/valid-permutations-for-di-sequence/
分析:
这种大数据类型一直都很头疼,放弃不折腾了
AC Code:
其他:
第一code
class Solution {
public int numPermsDISequence(String S) {
int n = S.length();
int[] a = new int[n+1];
int p = 0;
int len = 0;
for(int i = 0;i < n;i++){
if(S.charAt(i) == 'D'){
len++;
}else{
a[p++] = len+1;
len = 0;
}
}
a[p++] = len+1;
int mod = 1000000007;
long[][] C = new long[400 + 1][400 + 1];
for (int i = 0; i <= 400; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
if (C[i][j] >= mod)
C[i][j] -= mod;
}
}
a = Arrays.copyOf(a, p);
long[] dp = new long[p+1];
dp[0] = 1;
int all = 0;
for(int i = 1;i <= p;i++){
all += a[i-1];
int s = 0;
for(int j = i-1, sgn = 1;j >= 0;j--, sgn = -sgn){
s += a[j];
dp[i] += dp[j] * C[all][s] * sgn;
dp[i] %= mod;
}
if(dp[i] < 0)dp[i] += mod;
}
return (int)dp[p];
}
}
检举作弊
总结:
TI8那一次没参加周赛,其他的几次都参加了,但是总结没做,有的是有完成四道题,有的还没做完,还有一个有写到草稿箱,第四个没完成所以没发布。工作了,有很多事情要做,很难专注算法的提高与练习,而且本身天赋有限,有些问题如果想搞懂需要花太多的时间与精力,虽说当初开始的目的就是增长见识,性格使然还是一直在尽量做出所有问题,努力追求完美,结果更不完美。以后要逐渐学会放弃,不管做到什么程度,尽量周二晚前完成总结。
学习不易,坚持更难,继续努力吧。