c++怎么实现数字数组的删除数字_【Leetcode每日打卡】圆圈中最后剩下的数字, 秒懂约瑟夫环~...

255fc62142276daf212f8039df7bac8a.png

干货预警:所有文章都会首发于我的公众号【甜姨的奇妙冒险】,欢迎watch。

3df5cca33722c13c92453a55ecd36911.png
这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。——【约瑟夫问题】维基百科

我的原文链接:Java解决约瑟夫环问题,告诉你为什么模拟会超时!

一、原题描述

95f5de980a97768b15797eed438a1ee3.png

二、链表模拟 O(N^2),告诉你为啥模拟会超时!

如果单纯用链表模拟的话,时间复杂度是 O(NM)(其中每次找到被删除的数字,需要O(M)的时间复杂度,一共需要删除 N-1 次)。可以看下本题的数据范围,1 <= m <= 10^6,所以O(NM)肯定是超时的。(关于运行时间的预估,经验是如果 N < 10^5, 那么 O(N^2) 的耗时大概是几秒左右。当然因为时间复杂度会忽略常数,而且执行程序的机器性能也不同,因此实际上 O(N^2) 的实际耗时可能1秒多,也可能十几秒)

实际上不需要O(M)去找下一个被删除的数字,我们可以直接求得下一个被删除的索引位置!

假设当前删除的位置是 idx,下一个删除的数字的位置就是 idx + m。但是,由于把当前位置的数字删除了,后面的数字会前移一位,所以实际上下一个删除的位置是 idx + m - 1。由于数到末尾会从头重新数,这里可以通过取模来完美解决,假设当前删除的位置是 idx,那么下一个被删除的位置就是 (idx + m - 1)(mod n)

基于上述思路,我分别用 LinkedList 和 ArrayList 进行实现。其中,LinkedList 会超时,因为 LinkedList 虽然删除节点的时间复杂度是 O(1),但是因为需要从头遍历到需要删除的位置,因此单次删除的时间复杂度仍然是 O(N);那 ArrayList 呢?索引到指定删除位置是 O(1),但因为删除当前位置的元素后,后续位置的元素均需要向前移位,因此单次删除的时间复杂度也是O(N)。

看起来 LinkedList 和 ArrayList 单次删除操作的时间复杂度是一样的?所累哇多卡纳!

ArrayList 的删除操作在后续移位的时候,其实是内存连续空间的拷贝,对于CPU缓存友好,所以相比于 LinkedList 找到指定索引时的大量非连续地址访问,ArrayList的性能是好一些的!

以下是 ArrayList 的 remove 源码(JDK 8):

public 

单次删除的时间复杂度是 O(N), 一共删除了 n - 1次,所以整体时间复杂度是O(N^2)。Leetcode上该方法勉强可以通过,大概是1秒多一点。

上述基于 ArrayList 模拟的AC代码如下:

class 

三、数学解法 O(N)

这么著名的约瑟夫环问题,是有数学解法的!

因为数据是放在数组里,所以我在数组后面加上了数组的复制,以体现是环状的。我们先忽略图片里的箭头:

efceaa119d69edb49c069c22ed34b427.png

很明显我们每次删除的是第m个数字,我都标红了。

第一轮是 [0, 1, 2, 3, 4] ,所以是 [0, 1, 2, 3, 4] 这个数组的多个复制。这一轮 2 删除了。

第二轮开始时,从 3 开始,所以是 [3, 4, 0, 1] 这个数组的多个复制。这一轮 0 删除了。

第三轮开始时,从 1 开始,所以是 [1, 3, 4] 这个数组的多个复制。这一轮 4 删除了。

第四轮开始时,还是从 1 开始, 所以是 [1, 3] 这个数组的多个复制。这一轮 1 删除了。

最后剩下的数字是 3

图中的绿色的线指的是新的一轮的开头是怎么指定的,每次都是固定地向前移动m个位置。

最后剩下的 3 的下标是 0

第四轮反推,补上m个位置,然后模上当时的数组大小 2, 位置是 (0 + 3 ) % 2 = 1

第三轮反推,补上m个位置,然后模上当时的数组大小 3, 位置是 (1 + 3) % 3 = 1

第二轮反推,补上m个位置,然后模上当时的数组大小 4, 位置是 (1 + 3) % 4 = 0

第一轮反推,补上m个位置,然后模上当时的数组大小 5, 位置是 (0 + 3) % 5 = 3

所以最终剩下的数字的下标就是 3 。因为数组是从0开始的,所以最终答案就是 3

总结一下反推的过程,就是 (当前index + m) % 上一轮剩余数字的个数

代码就很简单了

class 

锵锵锵![✅] 每日营业1/1

我的多篇题解被Leetcode官网评为“精选题解”

大佬们随手扫一下文章上方二维码关注我的公众号鸭 (●˙ε˙●)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值