LeetCode每日一题(20200901)

原题地址:

 

思考过程:

假如使用穷举,时间复杂度是2^n,题目说到n最大为20,似乎可行。按此实现代码。

 

代码实现:

     public boolean PredictTheWinner1(int[] nums) {
        int left = 0, right = nums.length - 1;
        int sum1 = 0, sum2 = 0;
        return calc(nums, sum1, sum2, left, right, 0);
    }

    private boolean calc(int[] nums, int sum1, int sum2, int left, int right, int index) {
        if (left > right) {
            return sum1 >= sum2;
        }
        if (index % 2 == 0) {//玩家1
            return calc(nums, sum1 + nums[left], sum2, left + 1, right, index + 1)
                    || calc(nums, sum1 + nums[right], sum2, left, right - 1, index + 1);
        } else {
            return calc(nums, sum1, sum2 + nums[left], left + 1, right, index + 1)
                    || calc(nums, sum1, sum2 + nums[right], left, right - 1, index + 1);
        }
    }

然后发现执行不通过,再看题目,每个玩家都要使自己的分数最大化,如果穷举,显然不能满足。所以这个思路行不通。

 

查看官方解法:

按照方法一的思路,实现了如下代码(也有借鉴官方代码):

    public boolean PredictTheWinner(int[] nums) {
        int left = 0, right = nums.length - 1;
        return total(nums, left, right, 1) >= 0;
    }


    /**
     * 当玩家1时,+当前值,当玩家2时,-当前值
     * 然后判断最终的值是否大于大于0
     * 玩家1希望这个值越大越好,大于等于0就赢了
     * 玩家2希望这个值越小越好,小于0就赢了
     */
    private int total(int[] nums, int left, int right, int turn) {

        //当左右指针相等,表示只剩一个值,如果当前是玩家1,返回正,是玩家2返回负
        if (left == right) {
            return nums[left] * turn;
        }

        //当前是玩家1,分为两种情况,取左值和取右值,然后取两种情况中的最大值
        if (turn > 0) {
            int leftTotal = nums[left] + total(nums, left + 1, right, -turn);
            int rightTotal = nums[right] + total(nums, left, right - 1, -turn);
            return Math.max(leftTotal, rightTotal);
        } else {

            //当前是玩家2,也是分两种情况,不同的是取两种情况的最小值,因为玩家2希望的是值越小越好
            int leftTotal = -nums[left] + total(nums, left + 1, right, -turn);
            int rightTotal = -nums[right] + total(nums, left, right - 1, -turn);
            return Math.min(leftTotal, rightTotal);
        }
    }

时间复杂度O(2^n)

 

方法一中,存在大量重复计算,可以用map保存值,减少重复计算,也可以使用动态规划的解法,方法二就是动态规划。

使用map保存计算值的代码实现:

 public boolean PredictTheWinner2(int[] nums) {
        int left = 0, right = nums.length - 1;
        Map<String, Integer> map = new HashMap<>();
        return total(nums, left, right, 1, map) >= 0;
    }


    /**
     * 当玩家1时,+当前值,当玩家2时,-当前值
     * 然后判断最终的值是否大于大于0
     * 玩家1希望这个值越大越好,大于等于0就赢了
     * 玩家2希望这个值越小越好,小于0就赢了
     */
    private int total(int[] nums, int left, int right, int turn, Map<String, Integer> map) {

        String s = left + "" + turn + "" + right;
        if (map.containsKey(s)) {
            return map.get(s);
        }
        //当左右指针相等,表示只剩一个值,如果当前是玩家1,返回正,是玩家2返回负
        if (left == right) {
            return nums[left] * turn;
        }


        //当前是玩家1,分为两种情况,取左值和取右值,然后取两种情况中的最大值
        if (turn > 0) {
            int leftTotal = nums[left] + total(nums, left + 1, right, -turn, map);
            int rightTotal = nums[right] + total(nums, left, right - 1, -turn, map);
            int ans = Math.max(leftTotal, rightTotal);
            map.put(s, ans);
            return ans;
        } else {

            //当前是玩家2,也是分两种情况,不同的是取两种情况的最小值,因为玩家2希望的是值越小越好
            int leftTotal = -nums[left] + total(nums, left + 1, right, -turn, map);
            int rightTotal = -nums[right] + total(nums, left, right - 1, -turn, map);
            int ans = Math.min(leftTotal, rightTotal);
            map.put(s, ans);
            return ans;
        }
    }

性能稍好。

 

动态规划代码实现:(官方实现)

    public boolean PredictTheWinner(int[] nums) {
        int length = nums.length;
        int[] dp = new int[length];
        for (int i = 0; i < length; i++) {
            dp[i] = nums[i];
        }
        for (int i = length - 2; i >= 0; i--) {
            for (int j = i + 1; j < length; j++) {
                dp[j] = Math.max(nums[i] - dp[j], nums[j] - dp[j - 1]);
            }
        }
        return dp[length - 1] >= 0;
    }

性能最佳,且代码最优雅。

 

总结:

这一类问题,一般有三种解法,1.递归;2.递归的基础上,保存计算过的值,减少重复计算;3动态规划。

其中最优解法是动态规划,动态规划的难点在于,找到状态转移方程。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值