双周赛109(哈希统计、模拟、记忆化搜索==> 动态规划)

文章目录

  • 双周赛109
    • [2784. 检查数组是否是好的](https://leetcode.cn/problems/check-if-array-is-good/)
      • 哈希表统计
      • O(n)一次循环
    • [2785. 将字符串中的元音字母排序](https://leetcode.cn/problems/sort-vowels-in-a-string/)
      • 模拟
    • [2786. 访问数组中的位置使分数最大](https://leetcode.cn/problems/visit-array-positions-to-maximize-score/)
      • 记忆化搜索(选或不选)
      • 动态规划(选或不选)
    • [2787. 将一个数字表示成幂的和的方案数](https://leetcode.cn/problems/ways-to-express-an-integer-as-sum-of-powers/)
      • 记忆化搜素(三维)
      • 01背包
  • 总结:枚举选哪个 vs 选或不选

双周赛109

2784. 检查数组是否是好的

难度简单0

给你一个整数数组 nums ,如果它是数组 base[n] 的一个排列,我们称它是个 数组。

base[n] = [1, 2, ..., n - 1, n, n] (换句话说,它是一个长度为 n + 1 且包含 1n - 1 恰好各一次,包含 n 两次的一个数组)。比方说,base[1] = [1, 1]base[3] = [1, 2, 3, 3]

如果数组是一个好数组,请你返回 true ,否则返回 false

**注意:**数组的排列是这些数字按任意顺序排布后重新得到的数组。

示例 1:

输入:nums = [2, 1, 3]
输出:false
解释:因为数组的最大元素是 3 ,唯一可以构成这个数组的 base[n] 对应的 n = 3 。但是 base[3] 有 4 个元素,但数组 nums 只有 3 个元素,所以无法得到 base[3] = [1, 2, 3, 3] 的排列,所以答案为 false 。

示例 2:

输入:nums = [1, 3, 3, 2]
输出:true
解释:因为数组的最大元素是 3 ,唯一可以构成这个数组的 base[n] 对应的 n = 3 ,可以看出数组是 base[3] = [1, 2, 3, 3] 的一个排列(交换 nums 中第二个和第四个元素)。所以答案为 true 。

示例 3:

输入:nums = [1, 1]
输出:true
解释:因为数组的最大元素是 1 ,唯一可以构成这个数组的 base[n] 对应的 n = 1,可以看出数组是 base[1] = [1, 1] 的一个排列。所以答案为 true 。

示例 4:

输入:nums = [3, 4, 4, 1, 2, 1]
输出:false
解释:因为数组的最大元素是 4 ,唯一可以构成这个数组的 base[n] 对应的 n = 4 。但是 base[n] 有 5 个元素而 nums 有 6 个元素。所以答案为 false 。

提示:

  • 1 <= nums.length <= 100
  • 1 <= num[i] <= 200

哈希表统计

class Solution {
    public boolean isGood(int[] nums) {
        int n = nums.length;
        int[] vis = new int[n];
        for(int num : nums){
            if(num >= n) return false;
            vis[num] += 1;
        }
        for(int i = 1; i < n-1; i++)
            if(vis[i] != 1) return false;
        return vis[n-1] == 2;
    }
}

O(n)一次循环

class Solution {
    public boolean isGood(int[] nums) {
        int n = nums.length - 1;
        int[] cnt = new int[n+1];
        for(int v : nums){
            if(v > n || (v < n && cnt[v] > 0) || (v == n && cnt[v] > 1))
                return false;
            cnt[v] += 1;
        }
        return true;
    }
}

2785. 将字符串中的元音字母排序

难度中等1

给你一个下标从 0 开始的字符串 s ,将 s 中的元素重新 排列 得到新的字符串 t ,它满足:

  • 所有辅音字母都在原来的位置上。更正式的,如果满足 0 <= i < s.length 的下标 i 处的 s[i] 是个辅音字母,那么 t[i] = s[i]
  • 元音字母都必须以他们的 ASCII 值按 非递减 顺序排列。更正式的,对于满足 0 <= i < j < s.length 的下标 ij ,如果 s[i]s[j] 都是元音字母,那么 t[i] 的 ASCII 值不能大于 t[j] 的 ASCII 值。

请你返回结果字母串。

元音字母为 'a''e''i''o''u' ,它们可能是小写字母也可能是大写字母,辅音字母是除了这 5 个字母以外的所有字母。

示例 1:

输入:s = "lEetcOde"
输出:"lEOtcede"
解释:'E' ,'O' 和 'e' 是 s 中的元音字母,'l' ,'t' ,'c' 和 'd' 是所有的辅音。将元音字母按照 ASCII 值排序,辅音字母留在原地。

示例 2:

输入:s = "lYmpH"
输出:"lYmpH"
解释:s 中没有元音字母(s 中都为辅音字母),所以我们返回 "lYmpH" 。

提示:

  • 1 <= s.length <= 105
  • s 只包含英语字母表中的 大写小写 字母。

模拟

class Solution {
    public String sortVowels(String s) {
        Set<Character> set = new HashSet<>();
        set.add('a');set.add('e');set.add('i');set.add('o');set.add('u');
        set.add('A');set.add('E');set.add('I');set.add('O');set.add('U');
        int n = s.length();
        StringBuilder sb = new StringBuilder();
        boolean[] isval = new boolean[n];
        for(int i = 0; i < n; i++){
            if(set.contains(s.charAt(i))){
                sb.append(s.charAt(i));
                isval[i] = true;
            }
        }
        char[] ns = sb.toString().toCharArray();
        Arrays.sort(ns);
        int idx = 0;
        sb = new StringBuilder();
        for(int i = 0; i < n; i++){
            if(isval[i]){
                sb.append(ns[idx++]);
            }else
                sb.append(s.charAt(i));
        }
        return sb.toString();
    }
}

2786. 访问数组中的位置使分数最大

难度中等1

给你一个下标从 0 开始的整数数组 nums 和一个正整数 x

一开始 在数组的位置 0 处,你可以按照下述规则访问数组中的其他位置:

  • 如果你当前在位置 i ,那么你可以移动到满足 i < j任意 位置 j
  • 对于你访问的位置 i ,你可以获得分数 nums[i]
  • 如果你从位置 i 移动到位置 jnums[i]nums[j]奇偶性 不同,那么你将失去分数 x

请你返回你能得到的 最大 得分之和。

注意 ,你一开始的分数为 nums[0]

示例 1:

输入:nums = [2,3,6,1,9,2], x = 5
输出:13
解释:我们可以按顺序访问数组中的位置:0 -> 2 -> 3 -> 4 。
对应位置的值为 2 ,6 ,1 和 9 。因为 6 和 1 的奇偶性不同,所以下标从 2 -> 3 让你失去 x = 5 分。
总得分为:2 + 6 + 1 + 9 - 5 = 13 。

示例 2:

输入:nums = [2,4,6,8], x = 3
输出:20
解释:数组中的所有元素奇偶性都一样,所以我们可以将每个元素都访问一次,而且不会失去任何分数。
总得分为:2 + 4 + 6 + 8 = 20 。

提示:

  • 2 <= nums.length <= 105
  • 1 <= nums[i], x <= 106

记忆化搜索(选或不选)

class Solution:
    def maxScore(self, nums: List[int], x: int) -> int:
        n = len(nums)

        @cache
        def dfs(i, j: int) -> int:
            if i == n:
                return 0
            v = nums[i]
            # 选
            res = dfs(i+1, v%2) + v
            # 如果v 的奇偶性与上一个选的不同,则需要减去x
            if v%2 != j: 
                res -= x
            # 不选,直接跳过
            res = max(res, dfs(i+1, j)) 
            return res
        return dfs(1, nums[0]%2) + nums[0] 

动态规划(选或不选)

dp[i][0] 表示前i个最后停到偶数的最大分数

dp[i][1] 表示前i个最后停到奇数位置的最大分数

则有如果nums[i]是奇数时

dp[i][1]考虑前i-1个最后在奇数位置转移过来,和从偶数转移过来的情况,其中偶数转移过来需要扣分.
因为当前位为奇数,则对dp[i][0]无影响,其等于dp[i-1][0]

  • dp[i][1] = max(dp[i-1][1] + nums[i] , dp[i - 1][0] + nums[i] - x)

  • dp[i][0] = dp[i-1][0]

反之考虑当前为偶数时

dp[i][0]考虑前面从偶数过来和奇数过来的情况,奇数需要扣分

dp[i][1]不受影响

  • dp[i][0] = max(dp[i-1][0] + nums[i] , dp[i - 1][1] + nums[i] - x)

  • dp[i][1] = dp[i-1][1]

常规状态压缩一下

class Solution:
    def maxScore(self, nums: List[int], x: int) -> int:
        n = len(nums)
        f = [[-inf] * 2 for _ in range(n)]
        f[0][nums[0] & 1] = nums[0]
        f[0][1 - (nums[0] & 1)] = -x # <= nums[0] - x即可
        for i in range(1, n):
            f[i][nums[i] & 1] = max(f[i-1][nums[i] & 1] + nums[i], f[i-1][1 - (nums[i] & 1)] + nums[i] - x)
            f[i][1 - (nums[i] & 1)] = f[i-1][1 - (nums[i] & 1)]
        return max(f[n-1][0], f[n-1][1])

2787. 将一个数字表示成幂的和的方案数

难度中等0

给你两个 整数 nx

请你返回将 n 表示成一些 互不相同 正整数的 x 次幂之和的方案数。换句话说,你需要返回互不相同整数 [n1, n2, ..., nk] 的集合数目,满足 n = n1x + n2x + ... + nkx

由于答案可能非常大,请你将它对 109 + 7 取余后返回。

比方说,n = 160x = 3 ,一个表示 n 的方法是 n = 23 + 33 + 53

示例 1:

输入:n = 10, x = 2
输出:1
解释:我们可以将 n 表示为:n = 32 + 12 = 10 。
这是唯一将 10 表达成不同整数 2 次方之和的方案。

示例 2:

输入:n = 4, x = 1
输出:2
解释:我们可以将 n 按以下方案表示:
- n = 41 = 4 。
- n = 31 + 11 = 4 。

提示:

  • 1 <= n <= 300
  • 1 <= x <= 5

记忆化搜素(三维)

在这种方法中,我们首先检查数字本身是否是任何num power的幂。如果是,则返回方式为1,如果不是,则递归检查num幂+(num + 1)幂。

  • 取两个整数num和power作为输入。
  • 功能 sum_of_powers(int num, int power, int val) 取一个num并返回表示“ num”的方式计数,即将自然数加到给定幂的和。
  • 进行check =(num- pow(val, power))。如果check为0,则返回1,因为数字本身为val power。
  • 如果校验小于0,则返回0。
  • 否则取temp = val + 1。
  • 返回( sum_of_powers(check, power, temp) + sum_of_powers(num, power, temp) )。
class Solution:
    def numberOfWays(self, n: int, x: int) -> int:
        mod = int(10 ** 9 + 7)
        
        @cache
        def f(n, p, val: int) -> int:
            c = n - pow(val, p)
            if c == 0:
                return 1
            elif c < 0:
                return 0
            else:
                t = val + 1
                return (f(c, p, t) + f(n, p, t)) % mod
        return f(n, x, 1)

01背包

https://leetcode.cn/problems/ways-to-express-an-integer-as-sum-of-powers/solution/0-1-bei-bao-mo-ban-ti-by-endlesscheng-ap09/

背包容量是 n,有若干个物品(n^x, n^x, n^x),恰好装满0-1背包的方案数

class Solution {
    public int numberOfWays(int n, int x) {
        int[] f = new int[n+1];
        f[0] = 1;
        for(int i = 1, p; (p = (int)Math.pow(i, x)) <= n; i++){
            for(int j = n; j >= p; j--){
                f[j] += f[j-p];
                f[j] %= 1000000007;
            }
        }
        return f[n];
    }
}

相似题目

  • 494. 目标和
  • 879. 盈利计划

总结:枚举选哪个 vs 选或不选

动态规划,什么时候用枚举选哪个?什么时候用选或不选?

  • 看数据范围,当 n = 1 0 5 n = 10^5 n=105 时,使用枚举选哪个会超时,因为状态个数 1 0 5 10^5 105 + 状态转移 1 0 5 10^5 105
  • 很多时候,问题描述:你可以从当前位置i,移动到满足 i < j 的任意位置 +【条件一、条件二】,不需要知道精确的信息nums[i],只需要抽象问题的要求。

枚举选哪个:适用于需要完全知道子序列相邻两数的信息。

  • 如最长递增子序列( O ( n 2 ) O(n^2) O(n2)

选或不选:适用于 ① 子序列相邻数字无关、② 子序列相邻数字弱关联(奇偶性或只需要知道 0 和 1的信息)

你可能感兴趣的:(算法刷题记录,哈希算法,动态规划,算法)