390. Elimination Game

There is a list of sorted integers from 1 to n. Starting from left to right, remove the first number and every other number afterward until you reach the end of the list.

Repeat the previous step again, but this time from right to left, remove the right most number and every other number from the remaining numbers.

We keep repeating the steps again, alternating left to right and right to left, until a single number remains.

Find the last number that remains starting with a list of length n.

Example:

Input:
n = 9,
1 2 3 4 5 6 7 8 9
2 4 6 8
2 6
6

Output:
6

这是leetcode第二次周赛的中等难度题,看上去很简单,做法很多样,可是并不好通过。楼主的第一种做法是按照题目里的走法,一个指针按照规律在数组里往返跳来跳去,规律在于每次跳动的距离成2的指数倍增长,把握好每次跳动的距离,和每次折返的起点就行,这样做,最后可以把内存使用到常数级,时间使用为线性,可是题目的判断上线性也是无法通过的,最后的测试用例里有个一亿和十亿。当然,你如果无聊的把这俩单独算一遍,用if直接给出答案,也是可以通过的,不要问我是怎么知道的。。。

言归正传,代码还是要好好写,算法还是要认真琢磨。这种题要么就是有数学公式可以直接得到结果,要么就是有递归类型的解法。第一个不好推导,第二个还是很好想到的。
因为这里的数组结构性很强,递归的话就要找到同种类型的子问题,这是关键。虽然每次减少一半,可是剩下的数的间距总在变化,如何归类的同种子问题呢?
我们发现,第一次从左向右检索完,剩下,2 4 6 8, 其实这跟1 2 3 4的信息几乎是一样的,只是差了倍数2,所以问题就变为从右往左对规模4的问题进行操作,找到答案乘以2就行。对于从右往左,如果是1 2 3 4 5的话,检索完还剩2 4,同样是1 2的问题,如果是 1 2 3 4,剩 1 3,我们可以认为是1 2乘以2减一,总之,我们可以找到将每次的剩余子序列转化为同类子问题的方法。

需要讨论的有,每次开始的子问题是奇数还是偶数,决定下一次子问题的规模,然后这次的子问题和下次的子问题是什么递推关系。

代码如下:

class Solution {
public:
    int lastRemaining(int n) {
        return f(n);
    }
    int f (int n) { //从左往右
        if (n == 1) return 1;
        if (n == 2) return 2;
        if (n & 1 == 1) {
            return 2*g((n-1)/2);
        }
        else {
            return 2*g(n/2);
        }
    }
    int g (int n) { //从右往左
        if (n == 1) return 1;
        if (n == 2) return 1;
        if (n & 1 == 1) {
            return 2*f((n-1)/2);
        }
        else {
            return 2*f(n/2)-1;
        }
    }
};

时间复杂度O(logn),可以通过。如有更好的解法,欢迎回复讨论。

补充一个同样是O(logn)的非递归做法,每次记录删除完一遍后剩下序列的首尾两项,当然每次的删除方向和跳动距离也要不停更新,具体代码如下:

int lastRemaining(int n) {
    int base = 1, first = 1, last = n;
    bool check_first = false;
    while (first < last) { //remaining number is first, first + base, ..., last
        base *= 2;
        check_first = !check_first;
        int r = (check_first?first:last) % base;
        if (first % base == r) first += base/2;
        if (last % base == r) last -= base/2;
    }
    return first;
}

比较不好理解的就是为什么每次判断first和last除以base 的余数是否等于起始项对应的余数,判断是否保留或跳动。其实自己举几个例子就会发现,只有首尾两项的余数相等的时候才会都被删除,否则只有起始项删除。最后剩下的first和last相等,就是我们要找的值。
这里之所以这么判断,是在另一个层面观察的结果。因为从删除的那一项开始,删除的数都是跟他对于base 有同样余数的数!!!所以这就是为什么判断余数来看下次的起始项。非常巧妙。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值