前两天刷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查看吧

经过努力+作弊,我终于完成了leetcode通过率最低的一道题的更多相关文章

  1. LeetCode 871 - 最低加油次数 - [贪心+优先队列]

    汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处. 沿途有加油站,每个 station[i] 代表一个加油站,它位于出发位置东面 station[i][0] 英里处,并且有 s ...

  2. 力扣Leetcode 983. 最低票价

    最低票价 在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行.在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出.每一项是一个从 1 到 365 的整数. 火车票有三种不同的销 ...

  3. leetcode 解题报告 Word Ladder II

    题目不多说了.见https://oj.leetcode.com/problems/word-ladder-ii/ 这一题我反复修改了两天半.尝试过各种思路,总是报TLE.终于知道这一题为什么是leet ...

  4. [leetcode]Word Ladder II @ Python

    [leetcode]Word Ladder II @ Python 原题地址:http://oj.leetcode.com/problems/word-ladder-ii/ 参考文献:http://b ...

  5. Decode Ways -- LeetCode

    原题链接: http://oj.leetcode.com/problems/decode-ways/  这道题要求解一个数字串依照字符串编码方式可解析方式的数量.看到这样的求数量的,我们非常easy想 ...

  6. C#解leetcode 228. Summary Ranges Easy

    Given a sorted integer array without duplicates, return the summary of its ranges. For example, give ...

  7. Leetcode解题思路总结(Easy篇)

    终于刷完了leetcode的前250道题的easy篇.好吧,其实也就60多道题,但是其中的套路还是值得被记录的. 至于全部code,请移步github,题目大部分采用python3,小部分使用C,如有 ...

  8. 终于等到你!阿里正式向 Apache Flink 贡献 Blink 源码

    摘要: 如同我们去年12月在 Flink Forward China 峰会所约,阿里巴巴内部 Flink 版本 Blink 将于 2019 年 1 月底正式开源.今天,我们终于等到了这一刻. 阿里妹导 ...

  9. 刷leetcode是什么样的体验?【转】

    转自:https://www.zhihu.com/question/32322023 刷leetcode是什么样的体验? https://leetcode.com/ 1 条评论   默认排序 按时间排 ...

随机推荐

  1. React Hooks vs React Class vs React Function All In One

    React Hooks vs React Class vs React Function All In One React Component Types React Hooks Component ...

  2. flutter package & pub publish

    flutter package & pub publish dart-library-package https://pub.dev/packages/dart_library_package ...

  3. macOS & Xnip

    macOS & Xnip close finished notation cancel checked xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许 ...

  4. nasm astrcat函数 x86

    xxx.asm %define p1 ebp+8 %define p2 ebp+12 %define p3 ebp+16 section .text global dllmain export ast ...

  5. 「NGK每日快讯」12.15日NGK公链第42期官方快讯!

  6. spring学习路径

    1.https://zhuanlan.zhihu.com/p/72581899 spring 要点记录: (1)Web服务器的作用说穿了就是:将某个主机上的资源映射为一个URL供外界访问. (2)通过 ...

  7. 1107 Social Clusters——PAT甲级真题

    1107 Social Clusters When register on a social network, you are always asked to specify your hobbies ...

  8. java 阿里云短信发送

    记录自己的足迹,学习的路很长,一直在走着呢~ 第一步登录阿里云的控制台,找到此处: 点击之后就到此页面,如果发现账号有异常或者泄露什么,可以禁用或者删除  AccessKey: 此处方便测试,所以就新 ...

  9. Java基础语法:基本数据类型

    Java是一种强类型语言,每个变量都必须声明其类型. Java的数据类型 分为两大类:基本类型(primitive type)和引用类型(reference type). Java的所有八种基本类型的 ...

  10. idea更改包名无法加载主类解决

    把工程下面的.idea目录下的workspace.xml里面的路径改成你最新的路径即可. <option name="SPRING_BOOT_MAIN_CLASS" valu ...