LeetCode 495. 提莫攻击 / 629. K个逆序对数组(动规,不会) / 375. 猜数字大小 II(区间dp)

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 495. 提莫攻击 / 629. K个逆序对数组(动规,不会) / 375. 猜数字大小 II(区间dp)相关的知识,希望对你有一定的参考价值。

495. 提莫攻击

2021.11.10 每日一题

题目描述

在《英雄联盟》的世界中,有一个叫 “提莫” 的英雄。他的攻击可以让敌方英雄艾希(编者注:寒冰射手)进入中毒状态。

当提莫攻击艾希,艾希的中毒状态正好持续 duration 秒。

正式地讲,提莫在 t 发起发起攻击意味着艾希在时间区间 [t, t + duration - 1](含 t 和 t + duration - 1)处于中毒状态。如果提莫在中毒影响结束 前 再次攻击,中毒状态计时器将会 重置 ,在新的攻击之后,中毒影响将会在 duration 秒后结束。

给你一个 非递减 的整数数组 timeSeries ,其中 timeSeries[i] 表示提莫在 timeSeries[i] 秒时对艾希发起攻击,以及一个表示中毒持续时间的整数 duration 。

返回艾希处于中毒状态的 总 秒数。

示例 1:

输入:timeSeries = [1,4], duration = 2
输出:4
解释:提莫攻击对艾希的影响如下:
-第 1 秒,提莫攻击艾希并使其立即中毒。中毒状态会维持 2 秒,即第 1 秒和第 2 秒。
-第 4 秒,提莫再次攻击艾希,艾希中毒状态又持续 2 秒,即第 4 秒和第 5 秒。
艾希在第 1、2、4、5 秒处于中毒状态,所以总中毒秒数是 4 。

示例 2:

输入:timeSeries = [1,2], duration = 2
输出:3
解释:提莫攻击对艾希的影响如下:
-第 1 秒,提莫攻击艾希并使其立即中毒。中毒状态会维持 2 秒,即第 1 秒和第 2 秒。
-第 2 秒,提莫再次攻击艾希,并重置中毒计时器,艾希中毒状态需要持续 2 秒,即第 2 秒和第 3 秒。
艾希在第 1、2、3 秒处于中毒状态,所以总中毒秒数是 3 。

提示:

1 <= timeSeries.length <= 10^4
0 <= timeSeries[i], duration <= 10^7
timeSeries 按 非递减 顺序排列

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/teemo-attacking
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

class Solution {
    public int findPoisonedDuration(int[] timeSeries, int duration) {
        int n = timeSeries.length;
        int ans = 0;
        for(int i = 1; i < n; i++){
            if(timeSeries[i] - timeSeries[i - 1] >= duration)
                ans += duration;
            else
                ans += timeSeries[i] - timeSeries[i - 1];
        }
        ans += duration;
        return ans;
    }
}

629. K个逆序对数组

2021.11.11 每日一题

题目描述

给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。

逆序对的定义如下:对于数组的第i个和第 j个元素,如果满i < j且 a[i] > a[j],则其为一个逆序对;否则不是。

由于答案可能很大,只需要返回 答案 mod 109 + 7 的值。

示例 1:

输入: n = 3, k = 0
输出: 1
解释:
只有数组 [1,2,3] 包含了从1到3的整数并且正好拥有 0 个逆序对。

示例 2:

输入: n = 3, k = 1
输出: 2
解释:
数组 [1,3,2] 和 [2,1,3] 都有 1 个逆序对。

说明:

n 的范围是 [1, 1000] 并且 k 的范围是 [0, 1000]。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/k-inverse-pairs-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

看答案写的,不太会
主要是要想到对于新加的第 i 个数如何放置,放置在不同的位置可以由之前的动规结果得到
官解这个思路很好理解,写起来也比较方便

还有就是这个前缀和,很巧妙
因为之前的状态是连续的,所以可以用前缀和来保存之前的状态,从而保证每个状态计算的复杂度是O(1),关于这个前缀和具体的,还是看三叶姐和华大佬的题解吧,官解只是采用了一个数学推导

class Solution {
    public static final int MOD = (int)1e9 + 7;
    public int kInversePairs(int n, int k) {
        //咋做呢,判断一个数组的逆序对我记得用的是归并排序
        //想想动态规划
        //dp[i][j]表示1到i的整数,有j个逆序对的不同数组的个数
        //那么它怎么转移呢
        //n个数,最多有(n - 1 + 1)* (n - 1) / 2 个逆序对,也就是从大到小排序
        //不会,看了答案,还是巧啊
        
        //假设现在从1到i中取出一个数k,放在这个数组的最后一个位置,那么由这个数字k组成的逆序对个数就是i-k
        //而除了k其他数组成的逆序对,因为逆序对只关注相对大小,所以可以由dp[i - 1][j - i + k]得到
        //而k可以从1到i中任意取一个,所以情况是这些所有情况的和
        //这样时间复杂度是n方k,过不了
        //那么怎么优化呢
        //再考虑dp[i][j]和dp[i][j - 1]的展开式,dp[i][j] = dp[i][j - 1] - dp[i - 1][j - i] + dp[i - 1][j]
        //这样将计算dp[i][j]变成O1的复杂度就可以了

        //确实难
        int[][] dp = new int[n + 1][k + 1];
        //0个数有0个逆序对的情况有1种
        dp[0][0] = 1;
        for(int i = 1; i <= n; i++){
            for(int j = 0; j <= k; j++){
                dp[i][j] = (j >= 1 ? dp[i][j - 1] : 0) - (j >= i ? dp[i - 1][j - i] : 0) + dp[i - 1][j];
                if(dp[i][j] >= MOD)
                    dp[i][j] -= MOD;
                else if(dp[i][j] < 0)
                    dp[i][j] += MOD;
            }
        }
        return dp[n][k];
    }
}

375. 猜数字大小 II

2021.11.12 每日一题

题目描述

我们正在玩一个猜数游戏,游戏规则如下:

  1. 我从 1 到 n 之间选择一个数字。
  2. 你来猜我选了哪个数字。
  3. 如果你猜到正确的数字,就会 赢得游戏 。
  4. 如果你猜错了,那么我会告诉你,我选的数字比你的 更大或者更小 ,并且你需要继续猜数。
  5. 每当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。如果你花光了钱,就会 输掉游戏 。

给你一个特定的数字 n ,返回能够 确保你获胜 的最小现金数,不管我选择那个数字 。

示例 1:

输入:n = 10
输出:16
解释:制胜策略如下:

  • 数字范围是 [1,10] 。你先猜测数字为 7 。
    • 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $7 。
    • 如果我的数字更大,则下一步需要猜测的数字范围是 [8,10] 。你可以猜测数字为 9 。
      • 如果这是我选中的数字,你的总费用为 $7 。否则,你需要支付 $9 。
      • 如果我的数字更大,那么这个数字一定是 10 。你猜测数字为 10 并赢得游戏,总费用为 $7 + $9 = $16 。
      • 如果我的数字更小,那么这个数字一定是 8 。你猜测数字为 8 并赢得游戏,总费用为 $7 + $9 = $16 。
    • 如果我的数字更小,则下一步需要猜测的数字范围是 [1,6] 。你可以猜测数字为 3 。
      • 如果这是我选中的数字,你的总费用为 $7 。否则,你需要支付 $3 。
      • 如果我的数字更大,则下一步需要猜测的数字范围是 [4,6] 。你可以猜测数字为 5 。
        • 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $5 。
        • 如果我的数字更大,那么这个数字一定是 6 。你猜测数字为 6 并赢得游戏,总费用为 $7 + $3 + $5 = $15 。
        • 如果我的数字更小,那么这个数字一定是 4 。你猜测数字为 4 并赢得游戏,总费用为 $7 + $3 + $5 = $15 。
      • 如果我的数字更小,则下一步需要猜测的数字范围是 [1,2] 。你可以猜测数字为 1 。
        • 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $1 。
        • 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $7 + $3 + $1 = $11 。
          在最糟糕的情况下,你需要支付 $16 。因此,你只需要 $16 就可以确保自己赢得游戏。

示例 2:

输入:n = 1
输出:0
解释:只有一个可能的数字,所以你可以直接猜 1 并赢得游戏,无需支付任何费用。

示例 3:
输入:n = 2
输出:1
解释:有两个可能的数字 1 和 2 。

  • 你可以先猜 1 。
    • 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $1 。
    • 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $1 。
      最糟糕的情况下,你需要支付 $1 。

提示:

1 <= n <= 200

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/guess-number-higher-or-lower-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

刚开始我是这样想的,也是动规,比如n等于6的时候,先选3,那么剩下的456里面选,就相当于在123里面选,所以相当于用到了前面的状态,这样写了如下的代码,结果发现18这个例子过不了

class Solution {
    public int getMoneyAmount(int n) {
        //刚开始想的是二分的猜,但是那样好像不是最小的花费,因为大数肯定比小数花费多,所以需要先从较大数开始猜
        //想想
        //如果一个数,那么直接猜就好了0
        //如果两个数的情况下,那么肯定猜较小的数1
        //如果三个数的情况,最坏的情况是2
        //如果四个数,先猜3再猜1,最少是4,猜了两次
        //五个数,先猜4,再猜2,6
        //六个数,先猜3,那么 4 5 6,就是猜三个数的情况,只不过数变大了,也就是2 + 3,所以最后是3 + 5 = 8
        //常规就是动态规划
        //规律好像后面就不对了,哈哈
        //那么就动态规划
        //还得记录猜了几次,因为后面计算跟次数还有关系
        int[] dp = new int[n + 1];
        int[] count = new int[n + 1];
        //初始化
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        dp[1] = 0;
        count[1] = 0;
        for(int i = 2; i <= n; i++){
            //第一个分割点选 j
            for(int j = 1; j < i; j++){
                int temp = i - j == 1 ? 0 : dp[i - j] + j * count[i - j];
                
                if(dp[j - 1] >= temp){
                    if(j + dp[j - 1] < dp[i]){
                        dp[i] = j + dp[j - 1];
                        count[i] = 1 + count[j - 1];
                    }
                }else{
                    if(temp + j < dp[i]){
                        dp[i] = j + temp;
                        count[i] = 1 + count[i - j];
                    }
                }
            }
        }
        return dp[n];
    }
}

去看了一下,18,上面代码选的是8,按照我的思路,就是在9 - 18 之间选,相当于在1 - 10之间选,所以选的是第七个数,也就是15,然后,选的是17,结果就是40;但是很明显,9-14中选两个数加起来肯定比17大,所以这样的结果是错误的,原因就是每个数都增大了相同的数,结果就更大了,不能以小数来推断大数
所以再看了一下数据范围,用区间dp,3次方的复杂度写一下

class Solution {
    public int getMoneyAmount(int n) {
        //想想区间怎么dp
        //还是遍历每个数字作为第一个分割点
        //dp[i][j]表示i到j区间内需要的金额
        //dp[i][j] = k + dp[i][k - 1] + dp[k + 1][j];
        int INF = 0x3f3f3f3f;
        int[][] dp = new int[n + 1][n + 1];
        for(int i = n - 1; i >= 1; i--){
            for(int j = i + 1; j <= n; j++){
                dp[i][j] = INF;
                for(int k = i; k < j; k++){
                    dp[i][j] = Math.min(dp[i][j], k + Math.max(dp[k + 1][j], dp[i][k - 1]));
                }
            }
        }
        return dp[1][n];
    }
}

以上是关于LeetCode 495. 提莫攻击 / 629. K个逆序对数组(动规,不会) / 375. 猜数字大小 II(区间dp)的主要内容,如果未能解决你的问题,请参考以下文章

leetcode每日一题-495:提莫攻击

LeetCode 495. 提莫攻击

LeetCode_495_数组_提莫攻击

LeetCode 495 提莫攻击[模拟] HERODING的LeetCode之路

leetcode打卡——区间维护问题——495. 提莫攻击

12行代码AC_Leecode 495. 提莫攻击——Leecode每日一题系列