Leetcode 双子问题

先上题号 1029 1686 1058

Follow-up 1:

1058是我后面加进来的,这题抛开细节处理不谈,他的本质是终局思维反推问题。dp的解法也是可行的,但是你的搜索空间会非常大。但是用终局思维反推,把两个决策组分好,可以通过贪心算法解决,代码和之前的几道题及其相似。

 

这类问题我也不知道怎么归类,它具有这样一种模式,是交互性的,一方做决定会影响另一方的决定,也会影响最终的结果,但是他们不会同时做决定。这种题最核心的思想是,通过最终态来反推选择规律。

我们先不讨论其中有一道题能够用动态规划解决的事情。这篇文章里只会讲贪心的算法,以及背后的数学证明。

我们拿1686举例子。这两道题能够用贪心解决的前提是,你能给出背后的数学证明。这个数学证明给出的过程,必须通过从最终态反推的方法得出,你从零开始一步步推只能得到递归的方式,并不能理解为什么这个题能用贪心解决。OK,下面我们从最终态开始。最终态是什么形态,Alice和Bob把石头都轮流拿完了,然后第一个小技巧(这个技巧在轮流算分里用的很多,把另一个人的分数从自己的分数中减去)。下面是我从Leetcode的上截下来的图,因为我写这些数学符号写不好怕误导大家..这个证明还是很简单显而易见的,不管是谁拿,都希望能拿到当前所剩下的石头中,Alice和Bob拿起该石头获得的总分数最大的那个。于是从最终态我们可以得出,只要对原始数组通过这样的标准进行排序就能够知道每一步Alice或者Bob拿到的石头分数是多少。接下来我们把一个石头被Alice和Bob同时拿取的总分数称为这个石头的权值。

看上去很简单,但是这里有个坑,我钻进这个坑2个小时才钻出来。这个最终态的函数形式是可以整理成另外一个形式的。用文字来描述就是,所有石头被Alice拿的分数之和 - 那些被Bob拿取的石头的权值之和。我第一次就是整理成了这样的形式,然我我的想法是,既然Alice想要自己的分数最大,那么Bob拿取的石头的权值之和就应该变小,然后我就很自然地用递增序进行排序。看到没有,正确解法是按权值递减排序,而我的解法是按权值递增排序,这很显然是错误的。我犯的错误在哪里,我是站在Alice的角度帮Bob思考。我希望通过Alice将Bob拿到石头的权值降低。但是,这个游戏是轮流的游戏,也就是说,不管Alice怎么拿,在下一次Bob拿好之前他是无法干扰Bob的行动的。那么Bob的想法是什么,他希望通过自己的判断降低Alice的得分。于是,他依然会选权值最大的那些,所以我们依然需要通过递减序排序。

下面贴代码,你如果不通过数学证明,是写不出这种代码的。我相信你不给数学证明,面试官也未必会让你过,所以背答案没用。

public int stoneGameVI(int[] aValues, int[] bValues) {
        PriorityQueue<Integer> pick = new PriorityQueue<>((i1, i2) -> {
            int sum1 = aValues[i1] + bValues[i1];
            int sum2 = aValues[i2] + bValues[i2];
            return sum2 - sum1;
        });
        int a = 0;
        for(int i = 0; i < aValues.length; i++) {
            a += aValues[i];
            pick.add(i);
        }
        int round = 0;
        for(int i = 0; i < aValues.length; i++) {
            if (round == 0) {
                pick.poll();
            } else {
               a -= (aValues[pick.peek()] + bValues[pick.poll()]);
            }
            round ^= 1;
        }
        if (a == 0) {
            return 0;
        }
        return a > 0 ? 1 : -1;
    }

1029可以用一模一样的思路解决,而且代码及其类似。

扩展:1686可以用动态规划吗。可以,但是从leetcode的角度讲会造成TLE因为测试的N最大可以到10^5级别。可以从递归的角度看看我们需要存储多少个状态,因为是从石头堆里任意拿的,所以我们不能通过下标定界。那么我们可以通过函数形式表达 F(n) = F(n - 1)* n,第一个是Alice拿,他有n个石头可以选。你可以发现你无法通过任何方式跳过这n个子问题,换句话说,对于每一个长度确定的原始数组的subsequence,当其中有一个元素不同的时候,你都需要进行考虑。我举个简单的例子,[1,2,3,4] -> 你需要分别记录[1,2,3], [1,2,4], [1,3,4], [2,3,4]的状态才可以达到最优解,这和没有优化是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值