携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情
列表 arr 由在范围 [1, n] 中的所有整数组成,并按严格递增排序。请你对 arr 应用下述算法:
-
从左到右,删除第一个数字,然后每隔一个数字删除一个,直到到达列表末尾。
-
重复上面的步骤,但这次是从右到左。也就是,删除最右侧的数字,然后剩下的数字每隔一个删除一个。
-
不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。
给你整数 n ,返回 arr 最后剩下的数字。
示例
输入:n = 9
输出:6
arr = [1,2,3,4,5,6,7,8,9]// 从左删除
arr = [2,4,6,8] // 从右删除
arr = [2,6] // 从左删除
arr = [6]
代码实现
方法一
遍历生成数组后,使用 filter 进行过滤,每次过滤完成后,对数组进行反转
var lastRemaining = function(n) {
let arr = [];
// 初始化数组
for(let i=1; i<=n; i++) {
arr.push(i);
}
// 开始进行过滤
while(n !== 1) {
arr = arr.filter((_, index) => index % 2) // 过滤
.reverse() // 反转
n = arr.length; // 更新n
}
return arr[0]
}
上述方法虽然可以解决问题,但是在 n 过大的情况下,效率会非常低下(leetcode会报错)
方法二:依据数字 n 以及规律,每次变化头部元素
每次更新和记录 head,当 n 变为 1 时,head 就是最后一个数
两种情况更新 head:
- 当我们从左边开始移除的时候
- 当我们从右边开始移除的时候,并且剩余的总数是奇数时(
n%2 === 1)
假设 n = 24,开始依次消除
更新步骤:
- 初始化变量
head = 1, left = true, step = 1, n = 24- 头节点目前值为
1 left表示是否从左开始移除step表示当前消除后的每一步的步长
- 头节点目前值为
- 第一次移除:从左边开始
- 第一次开始时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - 第一次结束时:
2 4 6 8 10 12 14 16 18 20 22 24 - 从左开始的话,
head一定会移动到下一个位置:head = head + step=>head = 2 - 更新
left:left = false(下次从右边开始) - 更新
step(步长):由于每次删除一半的数据,所有步长是2的倍数增加的step = 1 * 2=>step = 2 - 更新
n(总数量):n = Math.floor(n/2)=>n = 12
- 第一次开始时:
- 第二次移除:从右边开始
- 开始:
2 4 6 8 10 12 14 16 18 20 22 24 - 结束:
2 6 10 14 18 22 - 由于不是从左边移除,并且开始时
n不是奇数,所以head = 2不变 step:step = step * 2 = 4n = Math.floor(n/2) = 6
- 开始:
- 第三次移除:从左边开始
- 开始:
2 6 10 14 18 22 - 结束:
6 14 18 head = head + step = 2 + 4 = 6step = step * 2 = 4 * 2 = 8n = Math.floor(n/2) = Math.floor(6/2) = 3
- 开始:
- 第四次移除:从右边开始
- 开始:
6 14 18 - 结束:
14 head = head + step = 6 + 8 = 14step = step * 2 = 8 * 2 = 16n = Math.floor(n/2) = Math.floor(3/2) = 1
- 开始:
- 此时
n = 1,说明已经是最后一个数了,直接返回head,结果是14
var lastRemaining = function(n) {
let head = 1, step = 1, left = true;
while(n>1) {
// 如果从左边移除,或者n为奇数;则都需要修改head
if(left || n%2 !== 0) {
head += step;
}
step <<= 1; // 相当于 step = step * 2
n >>= 1; // 相当于 n = Math.floor(n/2)
left = !left; // 左右交替移除
}
return head;
}