前兩天刷leetcode的時候,突發奇想,leetcode中最難的一道題是什么樣子的呢?
於是,我就將所有題目(https://leetcode-cn.com/problemset/all/ )按照通過率排了個序(中英文網站題目不同),找到了它(截止到目前
2021年4月5日,它的通過率依然是最低的):
題目描述如下:
請你實現三個 API append,addAll 和 multAll 來實現奇妙序列。
請實現 Fancy 類 :
- Fancy() 初始化一個空序列對象。
- void append(val) 將整數 val 添加在序列末尾。
- void addAll(inc) 將所有序列中的現有數值都增加 inc 。
- void multAll(m) 將序列中的所有現有數值都乘以整數 m 。
- int getIndex(idx) 得到下標為 idx 處的數值(下標從 0 開始),並將結果對 109 + 7 取余。
- 如果下標大於等於序列的長度,請返回 -1 。
力扣(LeetCode)
具體描述,請參考:https://leetcode-cn.com/problems/fancy-sequence/
這里,我們從題目給定的模板開始:
public class Fancy {
public Fancy() {
}
public void Append(int val) {
}
public void AddAll(int inc) {
}
public void MultAll(int m) {
}
public int GetIndex(int idx) {
}
}
/**
* Your Fancy object will be instantiated and called as such:
* Fancy obj = new Fancy();
* obj.Append(val);
* obj.AddAll(inc);
* obj.MultAll(m);
* int param_4 = obj.GetIndex(idx);
*/
我們可以看到,最后的注釋說明了代碼的使用方法,以下再次引用代碼時,我們將忽略它們。
看題目描述,需要我們提供一個類似於鏈表/數組的功能,可以向集合末尾添加一個整數,可以對集合中所有元素執行加上一個屬或乘上一個數的操作,最后還能通過索引獲取到對應索引處的值。
嗯。。。
聽起來不是很難嘛,代碼改成下面試試?
public class Fancy {
private List<int> list = new List<int>();
public Fancy() {
}
public void Append(int val) {
list.Add(val);
}
public void AddAll(int inc) {
int cnt = this.list.Count;
for(int i = 0; i < cnt; i++) {
this.list[i] += inc;
}
}
public void MultAll(int m) {
int cnt = this.list.Count;
for(int i = 0; i < cnt; i++) {
this.list[i] *= m;
}
}
public int GetIndex(int idx) {
if(idx < 0 || idx >= this.list.Count) {
return -1;
}
return this.list[idx] % 1000000007;
}
}
執行一下...
然后,報錯了...
得到的結果居然有負值?肯定是溢出了,再次調整代碼,計算的時候,將運算數與結果設置為 long,改成如下形式:
public class Fancy {
private List<int> list = new List<int>();
public Fancy() {
}
public void Append(int val) {
list.Add(val);
}
public void AddAll(int inc) {
int cnt = this.list.Count;
for(int i = 0; i < cnt; i++) {
long val = this.list[i] + (long)inc;
this.list[i] = (int)(val % 1000000007);
}
}
public void MultAll(int m) {
int cnt = this.list.Count;
for(int i = 0; i < cnt; i++) {
long val = this.list[i] * (long)m;
this.list[i] = (int)(val % 1000000007);
}
}
public int GetIndex(int idx) {
if(idx < 0 || idx >= this.list.Count) {
return -1;
}
return (this.list[idx] % 1000000007);
}
}
再次運行,嗯,很好,不報錯了,但是...超時了:
這,還能怎么辦?經過一晚上的思考,也沒有得到一個完美的答案,怎么辦?第二天,突然有了新想法,既然每次調用 AddAll 或者 MultAll 的時候,都要計算所有元素,性能瓶頸會不會在這里?
是不是只有在 GetIndex 的時候再計算可以避免執行不必要的計算?但,怎么做呢?咱有閉包啊,只要記錄所有的AddAll, MultAll 的調用,並使用閉包記錄其參數,這樣就可以只在調用 AddAll 或 MultAll 的時候創建閉包就好了。
於是,代碼就變成了下面這個樣子:
public class Fancy
{
private List<Entry> list = new List<Entry>();
private List<Func<int, int>> funcs = new List<Func<int, int>>();
public Fancy() { }
public void Append(int val)
{
// 在這里,我們只要將節點直接添加到
// 數據列表中即可。
// 由於在本節點添加之前的所有AddAll/MultAll
// 調用,均不應該計算,所以這里我們將需要調用
// 的索引指向調用函數列表最后一個元素的后面。
this.list.Add(new Entry
{
Val = val,
StartIndex = funcs.Count
});
}
public void AddAll(int inc)
{
// 由於可能在調用Append之前調用本方法,
// 但是由於此時列表中沒有任何元素,所以
// 應忽略調用。
if (this.list.Count <= 0)
{
return;
}
// 這里並不執行計算操作,只是創建了一個
// 閉包,並將其添加到函數列表中。
this.funcs.Add(val =>
{
long v = val + (long)inc;
return (int)(v % 1000000007);
});
}
public void MultAll(int m)
{
// 由於可能在調用Append之前調用本方法,
// 但是由於此時列表中沒有任何元素,所以
// 應忽略調用。
if (this.list.Count <= 0)
{
return;
}
// 這里並不執行計算操作,只是創建了一個
// 閉包,並將其添加到函數列表中。
this.funcs.Add(val =>
{
long v = val * (long)m;
return (int)(v % 1000000007);
});
}
public int GetIndex(int idx)
{
// 如果沒有任何元素,直接返回 -1。
if (idx < 0 || idx >= this.list.Count)
{
return -1;
}
// 這里,我們獲取到了要找的目標節點,但是由於
// 節點保存的值為計算之前的值,所以這里需要從
// StartIndex 索引處開始計算值,並獲取到結果。
Entry entry = this.list[idx];
int val = entry.Val;
int cnt = this.funcs.Count;
for (int i = entry.StartIndex; i < cnt; i++)
{
val = this.funcs[i](val);
}
return val;
}
private struct Entry
{
public int Val { get; set; }
public int StartIndex { get; set; }
}
}
努力了這么久,再執行一下試試:
沒天理啊,還是超時...
但題目還是要做的,繼續修改代碼,我們這里雖然進行了延遲求值,但是終歸每次 GetIndex 都要計算的,能不能減少這里的運算呢?
於是,每次計算之后,我都更新了StartIndex值,代碼改成了這樣:
public class Fancy
{
private List<Entry> list = new List<Entry>(10000);
private List<Func<long, long>> funcs =
new List<Func<long, long>>(10000);
public Fancy() { }
public void Append(int val)
{
this.list.Add(new Entry
{
Value = val,
LastIndex = funcs.Count
});
}
public void AddAll(int inc)
{
if (this.list.Count <= 0)
{
return;
}
this.funcs.Add(val =>
{
long v = val + inc;
if (v > 1000000007)
{
return v % 1000000007;
}
return v;
});
}
public void MultAll(int m)
{
if (this.list.Count <= 0)
{
return;
}
this.funcs.Add(val =>
{
long v = val * m;
if (v > 1000000007)
{
return v % 1000000007;
}
return v;
});
}
public int GetIndex(int idx)
{
if (idx >= this.list.Count || idx < 0)
{
return -1;
}
Entry entry = this.list[idx];
// 這里我們判斷最后執行函數的索引,如果已經
// 執行了所有要執行的函數,那么 entry.Value
// 存放的就是目標值,直接返回。
if (entry.LastIndex >= this.funcs.Count)
{
return entry.Value;
}
// 如果需要計算,就執行所有計算,然后返回最后計算的值。
long val = entry.Value;
for (int i = entry.LastIndex; i < funcs.Count; i++)
{
val = funcs[i](val);
}
entry.LastIndex = funcs.Count;
entry.Value = (int)val;
return entry.Value;
}
private struct Entry
{
public int Value { get; set; }
public int LastIndex { get; set; }
}
}
經過多次修改,滿懷希望的再次運行,但是結果再次讓我失望,再一次的超時了。
真是讓人絕望...
好在,我想到了一個作弊的方法,把代碼從C#改成了C語言...
typedef long (*fun)(long, long);
typedef struct {
int size;
int funSize;
int indexes[100000];
int vals[100000];
fun funcs[100000];
int ps[100000];
} Fancy;
Fancy* fancyCreate() {
Fancy* fancy = (Fancy*)malloc(sizeof(Fancy));
if (fancy) {
memset(fancy, 0, sizeof(Fancy));
}
return fancy;
}
void fancyAppend(Fancy* obj, int val) {
obj->vals[obj->size] = val;
obj->indexes[obj->size] = obj->funSize;
obj->size++;
}
long add(long val, long inc) {
val += inc;
if (val < 1000000007) {
return val;
}
return val % 1000000007;
}
long mult(long val, long m) {
val *= m;
if (val < 1000000007) {
return val;
}
return val % 1000000007;
}
void fancyAddAll(Fancy* obj, int inc) {
obj->funcs[obj->funSize] = add;
obj->ps[obj->funSize] = inc;
obj->funSize++;
}
void fancyMultAll(Fancy* obj, int m) {
obj->funcs[obj->funSize] = mult;
obj->ps[obj->funSize] = m;
obj->funSize++;
}
int fancyGetIndex(Fancy* obj, int idx) {
if (idx >= obj->size) {
return -1;
}
int last = obj->indexes[idx];
long val = obj->vals[idx];
if (last >= obj->funSize) {
return (int)val;
}
while (last < obj->funSize) {
val = obj->funcs[last](val, obj->ps[last]);
last++;
}
obj->vals[idx] = val;
obj->indexes[idx] = obj->funSize;
return (int)val;
}
void fancyFree(Fancy* obj) {
free(obj);
}
運行,然后,就通過了:
終於通過了,終於可以看答案了,又漲知識,原來這個是有算法上的解決方法的,也貼到下面吧:
class Fancy {
private:
static constexpr int mod = 1000000007;
vector<int> v, a, b;
public:
Fancy() {
a.push_back(1);
b.push_back(0);
}
// 快速冪
int quickmul(int x, int y) {
int ret = 1;
int cur = x;
while (y) {
if (y & 1) {
ret = (long long)ret * cur % mod;
}
cur = (long long)cur * cur % mod;
y >>= 1;
}
return ret;
}
// 乘法逆元
int inv(int x) {
return quickmul(x, mod - 2);
}
void append(int val) {
v.push_back(val);
a.push_back(a.back());
b.push_back(b.back());
}
void addAll(int inc) {
b.back() = (b.back() + inc) % mod;
}
void multAll(int m) {
a.back() = (long long)a.back() * m % mod;
b.back() = (long long)b.back() * m % mod;
}
int getIndex(int idx) {
if (idx >= v.size()) {
return -1;
}
int ao = (long long)inv(a[idx]) * a.back() % mod;
int bo = (b.back() - (long long)b[idx] * ao % mod + mod) % mod;
int ans = ((long long)ao * v[idx] % mod + bo) % mod;
return ans;
}
};
// 作者:zerotrac2
// 鏈接:https://leetcode-cn.com/problems/fancy-sequence/solution/qi-miao-xu-lie-by-zerotrac2/
// 來源:力扣(LeetCode)
// 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
至於具體的說明,就請移步leetcode查看吧。