经过努力+作弊,我终于完成了leetcode通过率最低的一道题

前两天刷leetcode的时候,突发奇想,leetcode中最难的一道题是什么样子的呢?

于是,我就将所有题目(https://leetcode-cn.com/problemset/all/ )按照通过率排了个序(中英文网站题目不同),找到了它(截止到目前

2021年4月5日,它的通过率依然是最低的):

image.png

题目描述如下:

请你实现三个 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;
    }
}

执行一下...

然后,报错了...

image.png

得到的结果居然有负值?肯定是溢出了,再次调整代码,计算的时候,将运算数与结果设置为 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);
    }
}

再次运行,嗯,很好,不报错了,但是...超时了:

image.png

这,还能怎么办?经过一晚上的思考,也没有得到一个完美的答案,怎么办?第二天,突然有了新想法,既然每次调用 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);
}

运行,然后,就通过了:

image.png

终于通过了,终于可以看答案了,又涨知识,原来这个是有算法上的解决方法的,也贴到下面吧:

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查看吧
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容