2022.1.2>>390. 消除游戏

162 阅读3分钟

题目

列表 arr 由在范围 [1, n] 中的所有整数组成,并按严格递增排序。请你对 arr 应用下述算法:

从左到右,删除第一个数字,然后每隔一个数字删除一个,直到到达列表末尾。 重复上面的步骤,但这次是从右到左。也就是,删除最右侧的数字,然后剩下的数字每隔一个删除一个。 不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。 给你整数 n ,返回 arr 最后剩下的数字。

示例 1:

输入:n = 9
输出:6
解释:
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
arr = [2, 4, 6, 8]
arr = [2, 6]
arr = [6]

示例 2:

输入:n = 1
输出:1

题解

这个题我一开始找的规律是:假如n大于1,那么第一次遍历之后肯定是没有基数,所以我创建了一个 for(let i = 0; i < n - 1; i+=2)循环得到的数组.在后面的步骤中,其实它删除的规律等于两个栈互相倒腾的过程,以9为例,创建得到的栈是stack1[2,4,6,8],我们创建多一个栈stack2[],第一个倒腾:8出栈,6入栈,4出栈,2入栈,这样stack2[6,2],就可以发现其实它所谓的从左到右,从右到左,被栈的特点破解了,我们每次只需要持续出栈,入栈的操作就行,但是这个题是需要找数学规律的,并不是要我们真的实操去删除,否则时间超时

看示例一:可以发现一开始数组是以1为开头,1为公差的等差数列,后续就是以2开头,2为公差的等差数列 ,所以我们可以模拟一个不断变化的等差数列,只需要记住每个等差数列的头,尾以及公差即可

先看代码

var lastRemaining = function(n) {
    if(n == 1) return 1;
    let min = 1, max = n,step = 1;
    // 为true时,是基数
    let change = true
    for (let i = 1; i <= Math.log2(n); i++) {
      if(change){
        max = (((max - min)/step + 1) % 2) === 0 ? max : max - step
        min +=step;
      }else{
        min = (((max - min)/step + 1) % 2) === 0 ? min : min + step
        max -= step;
      }
      step = step << 1
      change = !change
    }

   return max;
};

首先,Math.log2(n)是根据题目给的条件得出来的,每次循环都减去数组的一半,直到剩下一个数,这就和二分查找最坏的情况一样了,所以循环次数是log2(n),再者,当从左向右删除时,最小值min是一定会变化的,而最大值max只有当此时的数列长度为基数时才会发生变化,从右向左则相反,最大值max是一定会变化的, 而最小值Min只有当此时的数列长度为基数时才会发生变化,如何知道等差数列的项数呢,用到数学公式:(首项-末项) / 公差 + 1

时间复杂度

这里用了一个for循环,所以时间复杂度是O(log n)

空间复杂度

 这里没有额外开辟空间,所以空间复杂度是O(1)