LeetCode之Weekly Contest 101
前一段時間比較忙,而且做這個對於我來說挺耗時間的,已經間隔了幾期的沒做總結了,後面有機會補齊。而且本來做這個的目的就是為了防止長時間不做把編程拉下,不在追求獨立作出所有題了。以後完賽後稍微嘗試下,做不出來的直接放棄。
第一題:問題
問題: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那一次沒參加周賽,其他的幾次都參加了,但是總結沒做,有的是有完成四道題,有的還沒做完,還有一個有寫到草稿箱,第四個沒完成所以沒發布。工作了,有很多事情要做,很難專註算法的提高與練習,而且本身天賦有限,有些問題如果想搞懂需要花太多的時間與精力,雖說當初開始的目的就是增長見識,性格使然還是一直在盡量做出所有問題,努力追求完美,結果更不完美。以後要逐漸學會放棄,不管做到什麽程度,盡量周二晚前完成總結。
學習不易,堅持更難,繼續努力吧。
LeetCode之Weekly Contest 101