【周赛总结】周赛354

本文介绍了LeetCode周赛中的四道编程题目,包括特殊元素平方和、数组的最大美丽值、合法分割的最小下标和最长合法子字符串的长度。解题方法涉及简单的模拟、差分和前缀和、哈希表和双指针等算法,以及复杂度分析。
摘要由CSDN通过智能技术生成

周赛354

半个小时过了前三题,第四题超时看了好久,没看到限制字符串的长度最大只有10

特殊元素平方和【LC2778】

给你一个下标从 1 开始、长度为 n 的整数数组 nums

nums 中的元素 nums[i] 而言,如果 n 能够被 i 整除,即 n % i == 0 ,则认为 num[i] 是一个 特殊元素

返回 nums 中所有 特殊元素平方和

  • 思路

    简单模拟

  • 实现

    class Solution {
        public int sumOfSquares(int[] nums) {
            int n = nums.length;
            int res = 0;
            for (int i = 0; i < n; i++){
                if (n % (i + 1) == 0){
                    res += nums[i] * nums[i];
                }
            }
            return res;
    
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( n ) \mathcal{O}(n) O(n)
      • 空间复杂度: O ( 1 ) \mathcal{O}(1) O(1)

数组的最大美丽值【LC2779】

给你一个下标从 0 开始的整数数组 nums 和一个 非负 整数 k

在一步操作中,你可以执行下述指令:

  • 在范围 [0, nums.length - 1] 中选择一个 此前没有选过 的下标 i
  • nums[i] 替换为范围 [nums[i] - k, nums[i] + k] 内的任一整数。

数组的 美丽值 定义为数组中由相等元素组成的最长子序列的长度。

对数组 nums 执行上述操作任意次后,返回数组可能取得的 最大 美丽值。

**注意:**你 能对每个下标执行 一次 此操作。

数组的 子序列 定义是:经由原数组删除一些元素(也可能不删除)得到的一个新数组,且在此过程中剩余元素的顺序不发生改变。

  • 思路:差分+前缀和

    每个 nums[i] 可以替换为范围 [nums[i] - k, nums[i] + k] 内的任一整数,因此可以使用差分数组记录能被修改至每个区间的整数个数,最后将其累加,求出能修改至某个值的最大个数返回即可

  • 实现

    class Solution {
        public int maximumBeauty(int[] nums, int k) {
            // 每个数组能改变的范围[nums[i]-k, nums[i] + k]->差分统计->前缀和还原 求最大值
            int mn = Integer.MAX_VALUE, mx = Integer.MIN_VALUE;
            int n = nums.length;
            for (int i = 0; i < n; i++){
                mn = Math.min(mn, nums[i] - k);
                mx = Math.max(mx, nums[i] + k);
            }
            // mn负数-mn
            
            mx -= mn;
            
            int[] d = new int[mx + 2];//[0, mx] [mn, mx]->[0, mx-mn]
            for (int num : nums){
                int l = num - k - mn, r = num + k - mn;
                d[l] += 1;
                d[r + 1] -= 1;
            }
            int res = 0, sum = 0;
            for (int i = 0; i <= mx; i++){
                sum += d[i];
                res = Math.max(res, sum);
            }
            return res;
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( n ) \mathcal{O}(n) O(n)
      • 空间复杂度: O ( n ) \mathcal{O}(n) O(n)

合法分割的最小下标【LC2780】

如果元素 x 在长度为 m 的整数数组 arr 中满足 freq(x) * 2 > m ,那么我们称 x支配元素 。其中 freq(x)x 在数组 arr 中出现的次数。注意,根据这个定义,数组 arr 最多 只会有 一个 支配元素。

给你一个下标从 0 开始长度为 n 的整数数组 nums ,数据保证它含有一个支配元素。

你需要在下标 i 处将 nums 分割成两个数组 nums[0, ..., i]nums[i + 1, ..., n - 1] ,如果一个分割满足以下条件,我们称它是 合法 的:

  • 0 <= i < n - 1
  • nums[0, ..., i]nums[i + 1, ..., n - 1] 的支配元素相同。

这里, nums[i, ..., j] 表示 nums 的一个子数组,它开始于下标 i ,结束于下标 j ,两个端点都包含在子数组内。特别地,如果 j < i ,那么 nums[i, ..., j] 表示一个空数组。

请你返回一个 合法分割最小 下标。如果合法分割不存在,返回 -1

  • 思路

    • 分割数组后,分割后的数组的支配元素仍和原数组相等。
    • 先遍历数组,找到数组的支配元素及其出现次数
    • 然后枚举分割位置,记录分割数组中支配元素的次数,如果分割后的数组合法,那么返回当前分割点
  • 实现

    class Solution {
        public int minimumIndex(List<Integer> nums) {
            // 分割数组使分割后数组的支配元素和原数组相等
            Map<Integer,Integer> map = new HashMap<>();
            int val = -1, mx = -1;
            int n = nums.size();
            for (int i = 0; i < n; i++){
                int num = nums.get(i); 
                map.put(num, map.getOrDefault(num, 0) + 1);
                if (map.get(num) > mx){
                    val = num;              
                    mx = map.get(num);
                }
            }
            mx = map.get(val);
            int l = 0, r = mx;
            for (int i = 0; i < n - 1; i++){
                if (nums.get(i) == val){// [0, i][i + 1, n - 1]
                    l++;
                    r--;
                }
                if (l * 2 > (i + 1) && r * 2 > (n - i - 1)){
                    return i;
                }
            }
            return -1;
    
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( n ) \mathcal{O}(n) O(n)
      • 空间复杂度: O ( n ) \mathcal{O}(n) O(n)

最长合法子字符串的长度【LC2781】

给你一个字符串 word 和一个字符串数组 forbidden

如果一个字符串不包含 forbidden 中的任何字符串,我们称这个字符串是 合法 的。

请你返回字符串 word 的一个 最长合法子字符串 的长度。

子字符串 指的是一个字符串中一段连续的字符,它可以为空。

区间dp+字符串哈希[TLE]

我就是个努力优化的小废物hhh 刚开始写思路的时候,想到了用双指针找,最后直接用区间dp了

class Solution {
    int base = 7;
    public int longestValidSubstring(String word, List<String> forbidden) {
        // 字符串哈希 + 区间dp
        // dp[i][j]:字符串s[i, j]是否是合法字符串
        // 使用字符串哈希求出forbidden中的值,并快速求出子字符串的哈希值,快速判断是否存在子字符串
        // 使用双指针求出最长长度
        int n = word.length();      
        long[] h = new long[n + 1];
        long[] p = new long[n + 1];
        p[0] = 1L;
        for (int i = 1; i <= n; i++){
            h[i] = h[i - 1] * base + word.charAt(i - 1);
            p[i] = p[i - 1] * base;
        }
        Set<Long> ff = new HashSet<>();
        for (String s : forbidden){
            ff.add(hash(s));
        }
        boolean[][] dp = new boolean[n][n];
        int res = 0;
        for (int len = 1; len <= n; len++){
            for (int l = 0; l + len - 1 < n; l++){
                int r = l + len - 1;
                dp[l][r] |= r - 1 >= 0 ? dp[l][r - 1] : false;
                dp[l][r] |= l + 1 < n ? dp[l + 1][r] : false;
                if (ff.contains(hash(l, r, h, p))){
                    dp[l][r] = true;// 包含
                }
                if (!dp[l][r]){
                    res = len;
                }
                if (res == n){
                    return res;
                }
            }
        }
        return res;
    }
    private long hash(int l , int r, long[] h, long[] p){
        return h[r + 1] - h[l] * p[r - l + 1];
    }
    private long hash(String s){
        int n = s.length();
        long res = 0L;
        for (int i = 0; i < n; i++){
            res = res * base + s.charAt(i);
        }
        return res;
    }
}
  • 复杂度
    • 时间复杂度: O ( n 2 ) \mathcal{O}(n^2) O(n2)
    • 空间复杂度: O ( n 2 ) \mathcal{O}(n^2) O(n2)

哈希表+同向双指针

  • 思路:哈希表+同向双指针

    • 合法字符串:字符串中不含有forbidden中的任一字符串
    • 双指针:固定左端点,枚举右端点,判断是否存在以右端点为结尾的子字符串出现在forbidden中,如果存在,那么右移左指针至不存在的位置。
      • 判断过程:枚举以右端点为结尾的子字符串,长度从小到大枚举至10【forbidden中长度最大为10,长度为10以上的字符串肯定不在forbidden中,不需要判断】,如果 w o r d [ i , l e f t ] word[i,left] word[i,left]在forbidden中,那么合法字符串的左边界只能从 i + 1 i+1 i+1开始
  • 实现

    class Solution {
        public int longestValidSubstring(String word, List<String> forbidden) {
            var fb = new HashSet<String>();
            fb.addAll(forbidden);
            int ans = 0, left = 0, n = word.length();
            for (int right = 0; right < n; right++) {
                for (int i = right; i >= left && i > right - 10; i--) {
                    if (fb.contains(word.substring(i, right + 1))) {
                        left = i + 1; // 当子串右端点 >= right 时,合法子串一定不能包含 word[i]
                        break;
                    }
                }
                ans = Math.max(ans, right - left + 1);
            }
            return ans;
        }
    }
    
    作者:灵茶山艾府
    链接:https://leetcode.cn/problems/length-of-the-longest-valid-substring/solutions/2345796/ha-xi-biao-shuang-zhi-zhen-pythonjavacgo-bcez/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    • 复杂度
      • 时间复杂度: O ( n ) \mathcal{O}(n) O(n)
      • 空间复杂度: O ( m ) \mathcal{O}(m) O(m),forbidden的大小
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值