2272.最大波动的子字符串

目标

字符串的 波动 定义为子字符串中出现次数 最多 的字符次数与出现次数 最少 的字符次数之差。

给你一个字符串 s ,它只包含小写英文字母。请你返回 s 里所有 子字符串的 最大波动 值。

子字符串 是一个字符串的一段连续字符序列。

示例 1:

输入:s = "aababbb"
输出:3
解释:
所有可能的波动值和它们对应的子字符串如以下所示:
- 波动值为 0 的子字符串:"a" ,"aa" ,"ab" ,"abab" ,"aababb" ,"ba" ,"b" ,"bb" 和 "bbb" 。
- 波动值为 1 的子字符串:"aab" ,"aba" ,"abb" ,"aabab" ,"ababb" ,"aababbb" 和 "bab" 。
- 波动值为 2 的子字符串:"aaba" ,"ababbb" ,"abbb" 和 "babb" 。
- 波动值为 3 的子字符串 "babbb" 。
所以,最大可能波动值为 3 。

示例 2:

输入:s = "abcde"
输出:0
解释:
s 中没有字母出现超过 1 次,所以 s 中每个子字符串的波动值都是 0 。

说明:

  • 1 <= s.length <= 10^4
  • s 只包含小写英文字母。

思路

//todo

代码

性能

3110.字符串的分数

目标

给你一个字符串 s 。一个字符串的 分数 定义为相邻字符 ASCII 码差值绝对值的和。

请你返回 s 的 分数 。

示例 1:

输入:s = "hello"
输出:13
解释:
s 中字符的 ASCII 码分别为:'h' = 104 ,'e' = 101 ,'l' = 108 ,'o' = 111 。所以 s 的分数为 |104 - 101| + |101 - 108| + |108 - 108| + |108 - 111| = 3 + 7 + 0 + 3 = 13 。

示例 2:

输入:s = "zaz"
输出:50
解释:
s 中字符的 ASCII 码分别为:'z' = 122 ,'a' = 97 。所以 s 的分数为 |122 - 97| + |97 - 122| = 25 + 25 = 50 。

说明:

  • 2 <= s.length <= 100
  • s 只包含小写英文字母。

思路

计算字符串 s 相邻字符的 ASCII 码的差的绝对值之和。

代码


/**
 * @date 2025-03-15 20:02
 */
public class ScoreOfString3110 {

    public int scoreOfString(String s) {
        int res = 0;
        int n = s.length();
        for (int i = 1; i < n; i++) {
            res += Math.abs(s.charAt(i) - s.charAt(i - 1));
        }
        return res;
    }
}

性能

3340.检查平衡字符串

目标

给你一个仅由数字 0 - 9 组成的字符串 num。如果偶数下标处的数字之和等于奇数下标处的数字之和,则认为该数字字符串是一个 平衡字符串。

如果 num 是一个 平衡字符串,则返回 true;否则,返回 false。

示例 1:

输入:num = "1234"
输出:false
解释:
偶数下标处的数字之和为 1 + 3 = 4,奇数下标处的数字之和为 2 + 4 = 6。
由于 4 不等于 6,num 不是平衡字符串。

示例 2:

输入:num = "24123"
输出:true
解释:
偶数下标处的数字之和为 2 + 1 + 3 = 6,奇数下标处的数字之和为 4 + 2 = 6。
由于两者相等,num 是平衡字符串。

说明:

  • 2 <= num.length <= 100
  • num 仅由数字 0 - 9 组成。

思路

判断数字字符串奇数位上的数字之和是否等于偶数位的数字之和。

代码


/**
 * @date 2025-03-14 0:36
 */
public class IsBalanced3340 {

    public boolean isBalanced(String num) {
        int n = num.length();
        int sum = 0;
        for (int i = 0; i < n; i += 2) {
            sum += num.charAt(i) - '0';
        }
        for (int i = 1; i < n; i += 2) {
            sum -= num.charAt(i) - '0';
        }
        return sum == 0;
    }

}

性能

3306.元音辅音字符串计数II

目标

给你一个字符串 word 和一个 非负 整数 k。

返回 word 的 子字符串 中,每个元音字母('a'、'e'、'i'、'o'、'u')至少 出现一次,并且 恰好 包含 k 个辅音字母的子字符串的总数。

示例 1:

输入:word = "aeioqq", k = 1
输出:0
解释:
不存在包含所有元音字母的子字符串。

示例 2:

输入:word = "aeiou", k = 0
输出:1
解释:
唯一一个包含所有元音字母且不含辅音字母的子字符串是 word[0..4],即 "aeiou"。

示例 3:

输入:word = "ieaouqqieaouqq", k = 1
输出:3
解释:
包含所有元音字母并且恰好含有一个辅音字母的子字符串有:
word[0..5],即 "ieaouq"。
word[6..11],即 "qieaou"。
word[7..12],即 "ieaouq"。

说明:

  • 5 <= word.length <= 2 * 10^5
  • word 仅由小写英文字母组成。
  • 0 <= k <= word.length - 5

思路

求给定字符串 word 的子字符串中至少包含 5 个元音且恰好包含 k 个辅音的子字符串个数。

3305.元音辅音字符串计数I 相比,字符串长度范围变成了 2 * 10^5

恰好型滑动窗口,区间 [left, right] 满足条件不代表 [left, right + 1] 满足条件,可能辅音数量超过了 k,这样就不能累加前面的起点数量,需要重新遍历起点排除一个辅音,退化成暴力解法。

题解提到 恰好 k 个辅音字母 = 至少 k 个辅音字母 - 至少 k + 1 个辅音字母,这样就可以使用两个滑动窗口来求解。

代码


/**
 * @date 2025-03-13 17:03
 */
public class CountOfSubstrings3306 {

    public long countOfSubstrings(String word, int k) {
        return slidingWindow(word, k) - slidingWindow(word, k + 1);
    }

    public long slidingWindow(String word, int k) {
        Set<Character> vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u'));
        Map<Character, Integer> map = new HashMap<>();
        int n = word.length();
        int consonantCnt = 0;
        long res = 0;
        int left = 0;
        for (int right = 0; right < n; right++) {
            char c = word.charAt(right);
            if (vowels.contains(c)) {
                Integer cnt = map.getOrDefault(c, 0);
                map.put(c, cnt + 1);
            } else {
                consonantCnt++;
            }
            while (left <= right && consonantCnt >= k && map.size() == 5) {
                c = word.charAt(left++);
                if (vowels.contains(c)) {
                    map.merge(c, -1, Integer::sum);
                    if (map.get(c) == 0) {
                        map.remove(c);
                    }
                } else {
                    consonantCnt--;
                }

            }
            res += left;
        }
        return res;
    }
}

性能

3305.元音辅音字符串计数I

目标

给你一个字符串 word 和一个 非负 整数 k。

返回 word 的 子字符串 中,每个元音字母('a'、'e'、'i'、'o'、'u')至少 出现一次,并且 恰好 包含 k 个辅音字母的子字符串的总数。

示例 1:

输入:word = "aeioqq", k = 1
输出:0
解释:
不存在包含所有元音字母的子字符串。

示例 2:

输入:word = "aeiou", k = 0
输出:1
解释:
唯一一个包含所有元音字母且不含辅音字母的子字符串是 word[0..4],即 "aeiou"。

示例 3:

输入:word = "ieaouqqieaouqq", k = 1
输出:3
解释:
包含所有元音字母并且恰好含有一个辅音字母的子字符串有:
word[0..5],即 "ieaouq"。
word[6..11],即 "qieaou"。
word[7..12],即 "ieaouq"。

说明:

  • 5 <= word.length <= 250
  • word 仅由小写英文字母组成。
  • 0 <= k <= word.length - 5

思路

求给定字符串 word 的子字符串中至少包含 5 个元音且恰好包含 k 个辅音的子字符串个数。

暴力枚举子数组。

代码


/**
 * @date 2025-03-12 8:42
 */
public class CountOfSubstrings3305 {

    public int countOfSubstrings(String word, int k) {
        int n = word.length();
        Set<Character> vowel = new HashSet<>();
        Collections.addAll(vowel, 'a', 'e', 'i', 'o', 'u');
        Map<Character, Integer> map = new HashMap<>();
        int consonantCnt = 0;
        int res = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                char c = word.charAt(j);
                if (vowel.contains(c)) {
                    Integer cnt = map.getOrDefault(c, 0);
                    map.put(c, cnt + 1);
                } else {
                    consonantCnt++;
                }
                if (consonantCnt == k && map.size() == 5) {
                    res++;
                } else if (consonantCnt > k) {
                    break;
                }
            }
            map.clear();
            consonantCnt = 0;
        }
        return res;
    }

}

性能

2012.数组美丽值求和

目标

给你一个下标从 0 开始的整数数组 nums 。对于每个下标 i(1 <= i <= nums.length - 2),nums[i] 的 美丽值 等于:

  • 2,对于所有 0 <= j < i 且 i < k <= nums.length - 1 ,满足 nums[j] < nums[i] < nums[k]
  • 1,如果满足 nums[i - 1] < nums[i] < nums[i + 1] ,且不满足前面的条件
  • 0,如果上述条件全部不满足

返回符合 1 <= i <= nums.length - 2 的所有 nums[i] 的 美丽值的总和 。

示例 1:

输入:nums = [1,2,3]
输出:2
解释:对于每个符合范围 1 <= i <= 1 的下标 i :
- nums[1] 的美丽值等于 2

示例 2:

输入:nums = [2,4,6,4]
输出:1
解释:对于每个符合范围 1 <= i <= 2 的下标 i :
- nums[1] 的美丽值等于 1
- nums[2] 的美丽值等于 0

示例 3:

输入:nums = [3,2,1]
输出:0
解释:对于每个符合范围 1 <= i <= 1 的下标 i :
- nums[1] 的美丽值等于 0

说明:

  • 3 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

思路

有一个数组 nums,元素的美丽值定义如下:

  • 如果 nums[i] > nums[j] && 0 =< j < i && nums[i] < nums[k] && i < k <= n - 1,美丽值为 2
  • 如果 nums[i - 1] < nums[i] < nums[i + 1],美丽值为 1
  • 否则美丽值为 0

简单来说就是如果元素大于它前面的所有元素值并且小于它后面的所有元素值,美丽值为 2。如果仅仅大于它前面一个元素且小于它后面一个元素,美丽值为 1

首先想到的是使用单调栈维护当前元素后面第一个 小于等于 它的元素下标,保存到 floor[i],如果没有记录为 n;维护当前元素前面第一个 大于等于 它的元素下标,保存到 ceiling[i],如果没有记录为 -1。当 floor[i] == n && ceiling[i] == -1 时,美丽值为 2floor[i] > i + 1 && ceiling[i] < i - 1 时,美丽值为 1

网友题解使用的是维护前缀最大值和后缀最小值,如果当前元素大于前面的最大值且小于后面的最小值,美丽值为 2,否则直接比较前后两个元素,如果满足条件,美丽值为 1

代码


/**
 * @date 2025-03-11 8:48
 */
public class SumOfBeauties2012 {

    /**
     * 前缀最大值 后缀最小值
     */
    public int sumOfBeauties_v1(int[] nums) {
        int n = nums.length;
        int[] prefix = new int[n];
        int[] suffix = new int[n];
        suffix[n - 1] = Integer.MAX_VALUE;
        for (int i = 1; i < n; i++) {
            prefix[i] = Math.max(prefix[i - 1], nums[i - 1]);
            suffix[n - 1 - i] = Math.min(suffix[n - i], nums[n - i]);
        }
        int res = 0;
        for (int i = 1; i < n - 1; i++) {
            if (nums[i] > prefix[i] && nums[i] < suffix[i]) {
                res += 2;
            } else if (nums[i] > nums[i - 1] && nums[i] < nums[i + 1]) {
                res++;
            }
        }
        return res;
    }

    /**
     * 单调栈
     */
    public int sumOfBeauties(int[] nums) {
        int n = nums.length;
        int[] floor = new int[n];
        int[] ceiling = new int[n];
        Deque<Integer> stack = new ArrayDeque<>();
        int res = 0;
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && nums[stack.peek()] >= nums[i]) {
                floor[stack.pop()] = i;
            }
            stack.push(i);
        }
        while (!stack.isEmpty()) {
            floor[stack.pop()] = n;
        }
        for (int i = n - 1; i >= 0; i--) {
            while (!stack.isEmpty() && nums[stack.peek()] <= nums[i]) {
                ceiling[stack.pop()] = i;
            }
            stack.push(i);
        }
        while (!stack.isEmpty()) {
            ceiling[stack.pop()] = -1;
        }
        for (int i = 1; i < n - 1; i++) {
            if (floor[i] == n && ceiling[i] == -1) {
                res += 2;
            } else if (floor[i] > i + 1 && ceiling[i] < i - 1) {
                res++;
            }
        }
        return res;
    }

}

性能

2269.找到一个数字的K美丽值

目标

一个整数 num 的 k 美丽值定义为 num 中符合以下条件的 子字符串 数目:

  • 子字符串长度为 k 。
  • 子字符串能整除 num 。

给你整数 num 和 k ,请你返回 num 的 k 美丽值。

注意:

  • 允许有 前缀 0 。
  • 0 不能整除任何值。

一个 子字符串 是一个字符串里的连续一段字符序列。

示例 1:

输入:num = 240, k = 2
输出:2
解释:以下是 num 里长度为 k 的子字符串:
- "240" 中的 "24" :24 能整除 240 。
- "240" 中的 "40" :40 能整除 240 。
所以,k 美丽值为 2 。

示例 2:

输入:num = 430043, k = 2
输出:2
解释:以下是 num 里长度为 k 的子字符串:
- "430043" 中的 "43" :43 能整除 430043 。
- "430043" 中的 "30" :30 不能整除 430043 。
- "430043" 中的 "00" :0 不能整除 430043 。
- "430043" 中的 "04" :4 不能整除 430043 。
- "430043" 中的 "43" :43 能整除 430043 。
所以,k 美丽值为 2 。

说明:

  • 1 <= num <= 10^9
  • 1 <= k <= num.length (将 num 视为字符串)

思路

有一个数字 num,计算它有多少个长度为 k 的子串能够整除 num

由于子串长度固定,只需枚举起点,将其解析为数字 subNum,判断能否整除 num,即 subNum !=0 && num % subNum == 0

代码


/**
 * @date 2025-03-10 8:45
 */
public class DivisorSubstrings2269 {

    public int divisorSubstrings(int num, int k) {
        String s = String.valueOf(num);
        int n = s.length();
        int res = 0;
        for (int i = 0; i + k <= n; i++) {
            int subNum = Integer.parseInt(s.substring(i, i + k));
            if (subNum != 0 && num % subNum == 0) {
                res++;
            }
        }
        return res;
    }

}

性能

2070.每一个查询的最大美丽值

目标

给你一个二维整数数组 items ,其中 items[i] = [pricei, beautyi] 分别表示每一个物品的 价格 和 美丽值 。

同时给你一个下标从 0 开始的整数数组 queries 。对于每个查询 queries[j] ,你想求出价格小于等于 queries[j] 的物品中,最大的美丽值 是多少。如果不存在符合条件的物品,那么查询的结果为 0 。

请你返回一个长度与 queries 相同的数组 answer,其中 answer[j]是第 j 个查询的答案。

示例 1:

输入:items = [[1,2],[3,2],[2,4],[5,6],[3,5]], queries = [1,2,3,4,5,6]
输出:[2,4,5,5,6,6]
解释:
- queries[0]=1 ,[1,2] 是唯一价格 <= 1 的物品。所以这个查询的答案为 2 。
- queries[1]=2 ,符合条件的物品有 [1,2] 和 [2,4] 。
  它们中的最大美丽值为 4 。
- queries[2]=3 和 queries[3]=4 ,符合条件的物品都为 [1,2] ,[3,2] ,[2,4] 和 [3,5] 。
  它们中的最大美丽值为 5 。
- queries[4]=5 和 queries[5]=6 ,所有物品都符合条件。
  所以,答案为所有物品中的最大美丽值,为 6 。

示例 2:

输入:items = [[1,2],[1,2],[1,3],[1,4]], queries = [1]
输出:[4]
解释:
每个物品的价格均为 1 ,所以我们选择最大美丽值 4 。
注意,多个物品可能有相同的价格和美丽值。

示例 3:

输入:items = [[10,1000]], queries = [5]
输出:[0]
解释:
没有物品的价格小于等于 5 ,所以没有物品可以选择。
因此,查询的结果为 0 。

说明:

  • 1 <= items.length, queries.length <= 10^5
  • items[i].length == 2
  • 1 <= pricei, beautyi, queries[j] <= 10^9

思路

有一个二维数组 items,其元素 items[i] = [pricei, beautyi] 表示 item 的价格与美丽值。有一个查询数组,每一次查询的目标是找出价格小于等于 queries[i] 的物品中的最大美丽值。

为了求得答案,需要知道小于 queries[i] 都有哪些物品,然后从中找出最大美丽值。

先将 items 按照价格从小到大排序,二分查找 queries[i] 的上界下标 end。剩下的问题是找出 [0, end] 范围内的最大美丽值,这些值是固定的,可以预处理。

代码


/**
 * @date 2025-03-09 22:44
 */
public class MaximumBeauty2070 {

    public int[] maximumBeauty(int[][] items, int[] queries) {
        int n = items.length;
        Arrays.sort(items, (a, b) -> a[0] - b[0]);
        int[] maxBeauty = new int[n];
        maxBeauty[0] = items[0][1];
        for (int i = 1; i < n; i++) {
            maxBeauty[i] = Math.max(maxBeauty[i - 1], items[i][1]);
        }
        int[] res = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int upperbound = bs(queries[i], items);
            if (upperbound >= 0 && upperbound < n) {
                res[i] = maxBeauty[upperbound];
            }
        }
        return res;
    }

    public int bs(int target, int[][] items) {
        int n = items.length;
        int left = 0, right = n - 1;
        int mid = left + (right - left) / 2;
        while (left <= right) {
            if (items[mid][0] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
            mid = left + (right - left) / 2;
        }
        return right;
    }

}

性能

2234.花园的最大总美丽值

目标

Alice 是 n 个花园的园丁,她想通过种花,最大化她所有花园的总美丽值。

给你一个下标从 0 开始大小为 n 的整数数组 flowers ,其中 flowers[i] 是第 i 个花园里已经种的花的数目。已经种了的花 不能 移走。同时给你 newFlowers ,表示 Alice 额外可以种花的 最大数目 。同时给你的还有整数 target ,full 和 partial 。

如果一个花园有 至少 target 朵花,那么这个花园称为 完善的 ,花园的 总美丽值 为以下分数之 和 :

  • 完善 花园数目乘以 full.
  • 剩余 不完善 花园里,花的 最少数目 乘以 partial 。如果没有不完善花园,那么这一部分的值为 0 。

请你返回 Alice 种最多 newFlowers 朵花以后,能得到的 最大 总美丽值。

示例 1:

输入:flowers = [1,3,1,1], newFlowers = 7, target = 6, full = 12, partial = 1
输出:14
解释:Alice 可以按以下方案种花
- 在第 0 个花园种 2 朵花
- 在第 1 个花园种 3 朵花
- 在第 2 个花园种 1 朵花
- 在第 3 个花园种 1 朵花
花园里花的数目为 [3,6,2,2] 。总共种了 2 + 3 + 1 + 1 = 7 朵花。
只有 1 个花园是完善的。
不完善花园里花的最少数目是 2 。
所以总美丽值为 1 * 12 + 2 * 1 = 12 + 2 = 14 。
没有其他方案可以让花园总美丽值超过 14 。

示例 2:

输入:flowers = [2,4,5,3], newFlowers = 10, target = 5, full = 2, partial = 6
输出:30
解释:Alice 可以按以下方案种花
- 在第 0 个花园种 3 朵花
- 在第 1 个花园种 0 朵花
- 在第 2 个花园种 0 朵花
- 在第 3 个花园种 2 朵花
花园里花的数目为 [5,4,5,5] 。总共种了 3 + 0 + 0 + 2 = 5 朵花。
有 3 个花园是完善的。
不完善花园里花的最少数目为 4 。
所以总美丽值为 3 * 2 + 4 * 6 = 6 + 24 = 30 。
没有其他方案可以让花园总美丽值超过 30 。
注意,Alice可以让所有花园都变成完善的,但这样她的总美丽值反而更小。

说明:

  • 1 <= flowers.length <= 10^5
  • 1 <= flowers[i], target <= 10^5
  • 1 <= newFlowers <= 10^10
  • 1 <= full, partial <= 10^5

思路

有一个整数数组 flowersflowers[i] 表示下标为 i 的花园种植的花的数目。花园的美丽值通过以下方式计算:

  • 花园中花的数目大于等于 target,称该花园是完善的,美丽值 = 所有完善花园的个数 * full
  • 花园中花的数目小于 target,美丽值 = 所有花园中花的最小数目 * partial

Alice 可以向任意花园中种花,新种的花的数目不超过 newFlowers,求花园的最大美丽值。

// todo

代码

性能

2597.美丽子集的数目

目标

给你一个由正整数组成的数组 nums 和一个 正 整数 k 。

如果 nums 的子集中,任意两个整数的绝对差均不等于 k ,则认为该子数组是一个 美丽 子集。

返回数组 nums 中 非空 且 美丽 的子集数目。

nums 的子集定义为:可以经由 nums 删除某些元素(也可能不删除)得到的一个数组。只有在删除元素时选择的索引不同的情况下,两个子集才会被视作是不同的子集。

示例 1:

输入:nums = [2,4,6], k = 2
输出:4
解释:数组 nums 中的美丽子集有:[2], [4], [6], [2, 6] 。
可以证明数组 [2,4,6] 中只存在 4 个美丽子集。

示例 2:

输入:nums = [1], k = 1
输出:1
解释:数组 nums 中的美丽数组有:[1] 。
可以证明数组 [1] 中只存在 1 个美丽子集。

说明:

  • 1 <= nums.length <= 18
  • 1 <= nums[i], k <= 1000

思路

返回数组的美丽子序列个数,美丽指子序列中任意两个元素的差的绝对值不等于 k

由于数组长度不大,可以回溯枚举子序列,并针对每一子序列,两两比较,判断差的绝对值是否等于 k

更好的处理方法是使用哈希表记录元素的出现次数,回溯枚举子序列时直接判断当前元素加减 k 的绝对值是否存在于哈希表中。

代码


/**
 * @date 2025-03-07 0:09
 */
public class BeautifulSubsets2597 {

    public int beautifulSubsets(int[] nums, int k) {
        return dfs(0, new ArrayList<>(), nums, k);
    }

    public int dfs(int index, List<Integer> path, int[] nums, int k) {
        if (index == nums.length) {
            return path.size() > 0 ? 1 : 0;
        }
        int res = 0;
        res += dfs(index + 1, path, nums, k);
        for (Integer num : path) {
            if (Math.abs(num - nums[index]) == k) {
                return res;
            }
        }
        path.add(nums[index]);
        res += dfs(index + 1, path, nums, k);
        path.remove(path.size() - 1);
        return res;
    }

}

性能