Q1、奇偶频次间的最大差值 Ⅰ
1、题目描述
给你一个由小写英文字母组成的字符串 s
。请你找出字符串中两个字符的出现频次之间的 最大 差值,这两个字符需要满足:
- 一个字符在字符串中出现 偶数次 。
- 另一个字符在字符串中出现 奇数次 。
返回 最大 差值,计算方法是出现 奇数次 字符的次数 减去 出现 偶数次 字符的次数。
2、解题思路
-
统计字符出现次数
- 由于字符串只包含小写英文字母,我们可以使用 哈希表(unordered_map) 记录每个字符的出现次数。
-
遍历哈希表,分类统计
-
找到 出现次数是奇数的最大值。
-
找到 出现次数是偶数的最小值。
-
-
计算并返回结果
-
如果
mineven
仍然是INT_MAX
,说明没有出现次数为偶数的字符,应返回 0。 -
计算
maxodd - mineven
并返回。
-
3、代码实现
class Solution {
public:int maxDifference(string s) {unordered_map<char, int> hash; // 统计每个字符出现的次数// 统计字符出现次数for (const auto& ch : s) {hash[ch]++;}int maxodd = 0; // 记录出现奇数次的最大值int mineven = INT_MAX; // 记录出现偶数次的最小值// 遍历哈希表, 找到最大奇数次和最小偶数次for (const auto& kv : hash) {if (kv.second % 2) { // 如果次数是奇数maxodd = max(maxodd, kv.second);} else { // 如果次数是偶数mineven = min(mineven, kv.second);}}// 如果不存在出现偶数次的字符, 返回 0if (mineven == INT_MAX) {return 0;}return maxodd - mineven;}
};
4、复杂度分析
-
时间复杂度:
-
统计字符频率:
O(n)
-
遍历哈希表:
O(26)
(最多 26 个字母) -
总时间复杂度:
O(n)
-
-
空间复杂度:
-
需要
O(26) = O(1)
的空间存储哈希表(最多 26 个字符)。 -
总空间复杂度:
O(1)
-
Q2、K 次修改后的最大曼哈顿距离
1、题目描述
给你一个由字符 'N'
、'S'
、'E'
和 'W'
组成的字符串 s
,其中 s[i]
表示在无限网格中的移动操作:
'N'
:向北移动 1 个单位。'S'
:向南移动 1 个单位。'E'
:向东移动 1 个单位。'W'
:向西移动 1 个单位。
初始时,你位于原点 (0, 0)
。你 最多 可以修改 k
个字符为任意四个方向之一。
请找出在 按顺序 执行所有移动操作过程中的 任意时刻 ,所能达到的离原点的 最大曼哈顿距离 。
曼哈顿距离 定义为两个坐标点 (xi, yi)
和 (xj, yj)
的横向距离绝对值与纵向距离绝对值之和,即 |xi - xj| + |yi - yj|
。
2、解题思路
为了求解最大曼哈顿距离,我们需要动态地计算经过每一步移动后的曼哈顿距离,并考虑在每一步是否修改某些操作来增加这个距离。
- 分为北南和东西方向:
- 在任意时刻,当前位置的
x
和y
坐标分别受东西方向(‘E’、‘W’)和南北方向(‘N’、‘S’)的影响。 - 对于北南方向,我们关心的是
'N'
和'S'
的出现次数,计算它们之间的差值来表示当前在y
轴上的位置。 - 对于东西方向,我们关心的是
'E'
和'W'
的出现次数,计算它们之间的差值来表示当前在x
轴上的位置。
- 在任意时刻,当前位置的
- 修改操作:
- 我们可以修改最多
k
个字符,从而最大化曼哈顿距离。 - 修改字符时,我们的目标是让北南方向和东西方向的距离尽量增大,因此在每次修改时,我们应选择最有利于增加距离的方向。
- 我们可以修改最多
- 动态更新曼哈顿距离:
- 对于每一移动操作,我们都会根据当前的方向更新当前的
x
和y
坐标。 - 然后计算当前的曼哈顿距离,并查看是否需要修改操作来进一步增加距离。
- 对于每一移动操作,我们都会根据当前的方向更新当前的
3、代码实现
class Solution {
private:// 计算给定方向的曼哈顿距离int calculateManhattanDistance(int a, int b, int& modify) {// 计算 a 和 b 之间的距离, 以及我们最多可以修改的步数 modifyint d = min({a, b, modify}); // 可以修改的最小步数modify -= d; // 修改步数减少// 返回总的曼哈顿距离return abs(a - b) + d * 2; // 绝对差 + 修改带来的额外距离}public:int maxDistance(string s, int k) {int ret = 0; // 用于记录最大曼哈顿距离// 统计四个方向的出现次数int cnt['X'] = {}; // 用一个数组来记录 'N', 'S', 'E', 'W' 出现的次数// 遍历字符串s, 计算每步的曼哈顿距离for (char ch : s) {cnt[ch]++; // 更新对应方向的次数int modify = k; // 重置剩余可以修改的步数// 计算北南方向的曼哈顿距离int northSouthDist =calculateManhattanDistance(cnt['N'], cnt['S'], modify);// 计算东西方向的曼哈顿距离int eastWestDist =calculateManhattanDistance(cnt['E'], cnt['W'], modify);// 更新最远距离ret = max(ret, northSouthDist + eastWestDist);}return ret; // 返回最大曼哈顿距离}
};
4、复杂度分析
时间复杂度:
- 遍历字符串
s
计算每步的曼哈顿距离,时间复杂度为O(n)
,其中n
是字符串s
的长度。 - 计算每次曼哈顿距离需要常数时间
O(1)
。 - 总时间复杂度:
O(n)
。
空间复杂度:
- 使用一个大小为 4 的数组
cnt
来记录各方向的计数,空间复杂度为O(1)
。 - 总空间复杂度:
O(1)
。
Q3、使数组包含目标值倍数的最少增量
1、题目描述
给你两个数组 nums
和 target
。
在一次操作中,你可以将 nums
中的任意一个元素递增 1 。
返回要使 target
中的每个元素在 nums
中 至少 存在一个倍数所需的 最少操作次数 。
2、解题思路
这个问题可以通过 动态规划 和 子集最小公倍数(LCM) 的思想来解决。
- 最小公倍数的概念:
- 假设
a
是nums
中的一个元素,b
是target
中的一个元素。如果a
是b
的倍数,则a % b == 0
。如果我们希望在nums
中的每个元素都至少是target
中某元素的倍数,我们需要根据每个元素的倍数关系来计算递增次数。
- 假设
- 子集 LCM 的计算:
- 对于
target
中的多个元素,我们可以计算这些元素的最小公倍数(LCM)。假设我们正在考虑target
中某个元素的子集,我们需要计算该子集的最小公倍数,这样才能判断nums
中哪些元素可以满足这些条件。 - 为了计算每个子集的 LCM,可以使用动态规划的思想,遍历所有子集并更新 LCM。
- 对于
- 递归与记忆化搜索:
- 对于
nums
中的每个元素,我们尝试递增它使其成为target
中某个元素的倍数。 - 使用一个
memo
数组记录已计算过的状态,避免重复计算。
- 对于
步骤详解
- 计算子集 LCM:
- 对于
target
数组的每个子集,计算其最小公倍数(LCM)。在nums
中,如果一个元素是某个子集 LCM 的倍数,那么我们可以考虑它作为这个子集的代表元素。
- 对于
- 递归动态规划:
- 使用深度优先搜索(DFS)结合记忆化搜索(Memoization)计算最小的操作次数。
dfs(i, j)
表示我们已经处理了nums
中前i
个元素,并且target
中的子集状态为j
(j
是一个位掩码,表示target
中哪些元素已被满足)。
- 使用深度优先搜索(DFS)结合记忆化搜索(Memoization)计算最小的操作次数。
- 返回结果:
- 最终返回的结果是从
nums
中选择最小操作次数的答案。
- 最终返回的结果是从
3、代码实现
class Solution {
public:// 计算两个数的最小公倍数 (LCM)long long lcm(long long a, long long b) { return a * b / __gcd(a, b); }// 预处理 target 数组的所有子集的 LCMvector<long long> calculateLCMSubsets(const vector<int>& target) {int m = target.size();vector<long long> lcms(1 << m, 1); // 初始化 lcms 数组, 大小为 2^m, 每个值都初始化为 1// 对每个 target 的元素进行处理for (int i = 0; i < m; ++i) {int bit = 1 << i; // 当前元素对应的位for (int mask = 0; mask < bit; ++mask) {lcms[bit | mask] = lcm(target[i], lcms[mask]); // 更新子集的 LCM}}return lcms;}// 计算最少的操作次数, 确保 nums 中每个元素是 target 中某元素的倍数int minimumIncrements(vector<int>& nums, vector<int>& target) {int n = nums.size();int m = target.size();// 计算 target 数组的所有子集的 LCMvector<long long> lcms = calculateLCMSubsets(target);// 创建 memo 数组, 缓存已经计算过的状态, 避免重复计算vector<vector<long long>> memo(n, vector<long long>(1 << m, -1));// 定义 dfs 函数, 递归计算最小操作次数auto dfs = [&](auto&& dfs, int i, int j) -> long long {if (j == 0) {return 0; // 如果 j 为 0, 说明已经满足条件, 不需要操作}if (i < 0) {return LLONG_MAX / 2; // 超过范围时返回一个很大的值, 防止溢出}long long& res = memo[i][j];if (res != -1) {return res; // 如果已经计算过, 直接返回缓存的结果}res = dfs(dfs, i - 1, j); // 不修改 nums[i]// 枚举 j 的所有非空子集for (int sub = j; sub; sub = (sub - 1) & j) {long long l = lcms[sub]; // 当前子集的 LCMres = min(res, dfs(dfs, i - 1, j ^ sub) + (l - nums[i] % l) % l); // 更新最小操作数}return res; // 返回结果};// 从数组的最后一个元素开始, 计算整个数组的最小操作次数return dfs(dfs, n - 1, (1 << m) - 1);}
};
Q4、奇偶频次间的最大差值 Ⅱ
1、题目描述
给你一个字符串 s
和一个整数 k
。请你找出 s
的子字符串 subs
中两个字符的出现频次之间的 最大 差值,freq[a] - freq[b]
,其中:
subs
的长度 至少 为k
。- 字符
a
在subs
中出现奇数次。 - 字符
b
在subs
中出现偶数次。
返回 最大 差值。
注意 ,subs
可以包含超过 2 个 互不相同 的字符。.
子字符串 是字符串中的一个连续字符序列。
2、解题思路
-
字母频次计数:
- 为了方便处理,我们可以将字符串
s
中的字符映射为数字0
到4
,即用整数表示字符。例如,假设s
只包含字符0, 1, 2, 3, 4
,我们可以直接用这些数字来进行频率统计。
- 为了方便处理,我们可以将字符串
-
滑动窗口的应用:
- 使用 滑动窗口 来遍历字符串中的每一个可能的子字符串,并检查子字符串的频次是否符合题目条件。窗口的大小需要至少为
k
,即right - left + 1 >= k
。
- 使用 滑动窗口 来遍历字符串中的每一个可能的子字符串,并检查子字符串的频次是否符合题目条件。窗口的大小需要至少为
-
频次条件:
- 我们需要一个子字符串,使得某个字符
a
在子字符串中的出现次数为奇数,另一个字符b
的出现次数为偶数。通过更新频次并调整窗口的左右指针来找到合适的子字符串。
- 我们需要一个子字符串,使得某个字符
-
最大差值的计算:
- 对于每个字符对
(odd_char, even_char)
,我们维护两个频次数组:current_freq
(当前子字符串的频次)和prev_freq
(上一个子字符串的频次)。通过滑动窗口不断更新这两个数组,计算最大差值。
- 对于每个字符对
3、代码实现
class Solution {
public:int maxDifference(string s, int k) {// 常量, 表示一个很大的数, 用于初始化const int INF = INT_MAX / 2;int result = -INF; // 用于存储最终的最大差值// 遍历所有可能的字符对 (x, y), 其中 x 和 y 不相同for (int odd_char = 0; odd_char < 5; ++odd_char) {for (int even_char = 0; even_char < 5; ++even_char) {if (odd_char == even_char) {continue; // 如果两个字符相同, 则跳过}// 初始化频率数组int current_freq[5] = {0}; // 当前子串中每个字符的出现次数int prev_freq[5] = {0}; // 上一个子串中每个字符的出现次数int min_diff[2][2] = {{INF, INF}, {INF, INF}}; // 最小差值数组int left = 0; // 左指针, 表示当前子串的起始位置// 遍历字符串for (int right = 0; right < s.size(); ++right) {// 增加当前字符的频率current_freq[s[right] - '0']++;// 当子串长度大于等于 k 且字符 x 和 y 的频次符合要求时while (right - left + 1 >= k && current_freq[odd_char] > prev_freq[odd_char] && current_freq[even_char] > prev_freq[even_char]) {// 计算当前子串的频次差int parity_x = prev_freq[odd_char] & 1; // x字符的奇偶性int parity_y = prev_freq[even_char] & 1; // y字符的奇偶性// 更新最小差值min_diff[parity_x][parity_y] = min(min_diff[parity_x][parity_y], prev_freq[odd_char] - prev_freq[even_char]);// 移动左指针, 更新 prev_freqprev_freq[s[left] - '0']++;left++;}// 计算当前的最大差值result = max(result, current_freq[odd_char] - current_freq[even_char] - min_diff[current_freq[odd_char] & 1 ^ 1][current_freq[even_char] & 1]);}}}return result;}
};
4、复杂度分析
时间复杂度: O(n)
,其中 n
是字符串 s
的长度。
空间复杂度: O(1)
,使用常数空间。