消除游戏:leetcode-390

111 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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
    • 更新 leftleft = 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 不变
    • stepstep = step * 2 = 4
    • n = Math.floor(n/2) = 6
  • 第三次移除:从左边开始
    • 开始:2 6 10 14 18 22
    • 结束:6 14 18
    • head = head + step = 2 + 4 = 6
    • step = step * 2 = 4 * 2 = 8
    • n = Math.floor(n/2) = Math.floor(6/2) = 3
  • 第四次移除:从右边开始
    • 开始:6 14 18
    • 结束:14
    • head = head + step = 6 + 8 = 14
    • step = step * 2 = 8 * 2 = 16
    • n = 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;
}