Q1 元素和最小的山形三元组 I
-
解题思路:
- 数据量很小,可以直接三重循环枚举下标,判断是否满足山形三元组条件
-
解题代码:
class Solution {
public:
int minimumSum(vector<int>& nums) {
int n = nums.size();
int ans = INT_MAX;
for(int i = 0; i < n; i++)
{
for(int j = i+1; j < n; j++)
{
for(int k = j+1; k < n; k++)
{
if(nums[j] > nums[i] && nums[j] > nums[k])
ans = min(ans, nums[i] + nums[j] + nums[k]);
}
}
}
if(ans == INT_MAX)
return -1;
return ans;
}
};
Q2 元素和最小的山形三元组 II (前后缀分解)
-
解题思路:
- 用数组left记录每个元素左侧最小元素是多少
- 用数组right记录每个元素右侧最小元素是多少
- 遍历每个元素,判断其与左侧、右侧最小元素是否可以形成山形三元组,如果可以,使用其和更新答案
-
解题代码:
class Solution {
public:
int minimumSum(vector<int>& nums) {
int n = nums.size();
vector<int> left(n), right(n);
left[0] = INT_MAX;
for(int i = 1; i < n; i++)
left[i] = min(left[i-1], nums[i-1]);
right[n-1] = INT_MAX;
for(int i = n-2; i >= 0; i--)
right[i] = min(right[i+1], nums[i+1]);
int ans = INT_MAX;
for(int i = 0; i < n; i++)
{
if(nums[i] > left[i] && nums[i] > right[i])
ans = min(ans, nums[i] + left[i] + right[i]);
}
if(ans == INT_MAX)
return -1;
return ans;
}
};
- 空间优化
- 左侧最小元素可以不用left数组记录,而是直接在遍历元素的过程中维护,这样可以省去一个数组空间
- 代码:
class Solution {
public:
int minimumSum(vector<int>& nums) {
int n = nums.size();
vector<int> right(n);
right[n-1] = INT_MAX;
for(int i = n-2; i >= 0; i--)
right[i] = min(right[i+1], nums[i+1]);
int leftMin = INT_MAX;
int ans = INT_MAX;
for(int i = 0; i < n; i++)
{
if(nums[i] > leftMin && nums[i] > right[i])
ans = min(ans, nums[i] + leftMin + right[i]);
leftMin = min(leftMin, nums[i]);
}
if(ans == INT_MAX)
return -1;
return ans;
}
};
Q3 合法分组的最少组数
- 题目链接
- 解题思路:
- 首先统计每个数出现的频数
- 对于每个频数r,需要将其表示为 r = k1 * d1 + k2 * d2, 其中d2 = d1 + 1,要求的结果就是所有k1、k2加和的最小值
- 可以选取最小的频数,枚举其可以拆成的d1、d2情况
-
对于每一个频数r,应该满足以下两个式子:
- r = k1 * d1 + k2 * d2
- r + k1 = (k1 + k2) * d2
-
由这两个式子可以解出 k1 + k2 的最小值,如果此时k1、k2均满足大于等于0,将k1 + k2累加到答案中
-
遍历完所有频数后,得到在一种拆分成d1、d2的方案下拆出的组数
-
最后的答案应该是所有拆分方案下拆出组数的最小值
-
- 时间复杂度是O(n)的
- 解题代码:
class Solution {
public:
int minGroupsForValidAssignment(vector<int>& nums) {
unordered_map<int, int> um;
for(auto num : nums)
um[num] += 1;
vector<int> r;
for(auto &[_, cnt] : um)
r.push_back(cnt);
sort(r.begin(), r.end());
int ans = INT_MAX;
int len = r.size();
for(int d1 = 1; d1 <= r[0]; d1++)
{
int d2 = d1 + 1;
//r[i] = k1 * d1 + k2 * d2
//r[i] + k1 = (k1 + k2) * d2
int temp = 0;
bool flag = true;
for(int i = 0; i < len; i++)
{
int mod = r[i] % d2;
if(mod == 0)//k1 = 0, k1 + k2 = r[i] / d2
temp += r[i] / d2;
else
{
int k1 = d2 - mod;
int k2 = (r[i] + k1) / d2 - k1;
if(k2 >= 0)
temp += k1 + k2;
else
{
flag = false;
break;
}
}
}
if(flag)
ans = min(ans, temp);
}
return ans;
}
};
-
灵神的解法:
- 在计算每个频数可以拆成多少个组时:
- 令q = r / d1下取整, mod = r % d1
- 当mod <= q时,可以将mod拆成mod个1,分别加到每个组中,这样会得到mod组d2,q-mod组d1,共可拆成 r / d2 上取整组
- 当mod > q时,无法拆
- 倒序枚举d1,遇到第一个可拆分的d1就返回答案
- 在计算每个频数可以拆成多少个组时:
-
解题代码:
class Solution {
public:
int minGroupsForValidAssignment(vector<int>& nums) {
unordered_map<int, int> um;
for(auto num : nums)
um[num] += 1;
vector<int> r;
for(auto &[_, cnt] : um)
r.push_back(cnt);
sort(r.begin(), r.end());
int len = r.size();
for(int d1 = r[0]; d1 >= 1; d1--)
{
int temp = 0;
bool state = true;
int d2 = d1 + 1;
for(int i = 0; i < len; i++)
{
int q = r[i] / d1;
int mod = r[i] % d1;
if(q >= mod)
temp += (r[i] + d2 - 1) / d2;
else
{
state = false;
break;
}
}
if(state)
return temp;
}
return -1;
}
};
Q4 得到K个半回文串的最少修改次数
- 题目链接
- 解题思路:
- 首先可以预处理出所有子字符串处理成半回文串所需要的最少修改次数
- 枚举子字符串
- 枚举正整数d,计算需要的最少修改次数
- 定义dfs(i, remain),其中i表示当前考虑[i, n-1]区间,remain表示还可以分成remain个子字符串
-
状态转移:
- 枚举右端点j,将[i, j]拆分成一个子字符串,则dfs(i, remain) = min(dfs(j+1, remain-1)),其中j满足 i <= j < n
-
边界情况:
- 当 i = n时,如果remain = 0,返回0,否则返回201(一个大于所有可能答案的极大值)
- 当remain = 0时,返回201
-
递归入口: dfs(0, k)
-
需要记忆化优化
-
- 需要注意隐含条件,子字符串的长度大于1
- 时间复杂度O(n^3logn)
- 首先可以预处理出所有子字符串处理成半回文串所需要的最少修改次数
- 解题代码:
class Solution {
public:
int minimumChanges(string s, int k) {
int n = s.size();
vector<vector<int>> table(n, vector<int>(n, INT_MAX));
auto cal = [&](int l, int r, int d) -> int
{
int ans = 0;
for(int i = 0; i < d; i++)
{
int left = l + d - 1 - i;
int right = r - i;
while(left <= right)
{
if(s[left] != s[right])
ans += 1;
left += d;
right -= d;
}
}
return ans;
};
for(int l = 0; l < n; l++)
{
for(int r = l; r < n; r++)
{
int len = r - l + 1;
for(int d = 1; d <= sqrt(len); d++)
{
if(len % d != 0)
continue;
int t1 = cal(l, r, d);
int t2 = d == 1 ? INT_MAX : cal(l, r, len / d);
table[l][r] = min(table[l][r], min(t1, t2));
}
}
}
vector<vector<int>> f(n, vector<int>(k+1, -1));
function<int(int, int)> dfs = [&](int i, int remain) -> int
{
if(i == n)
{
if(remain == 0)
return 0;
return 201;
}
if(remain == 0)
return 201;
if(f[i][remain] != -1)
return f[i][remain];
int &ans = f[i][remain];
ans = 201;
for(int j = i+1; j < n - 2*(remain-1); j++)
ans = min(ans, dfs(j+1, remain-1) + table[i][j]);
return ans;
};
return dfs(0, k);
}
};
- 可以预处理所有的真因子
//预处理所有真因子
const int MX = 201;
vector<vector<int>> divisors(MX);
int init = [] {
for (int i = 1; i < MX; i++) {
for (int j = i * 2; j < MX; j += i) {
divisors[j].push_back(i);
}
}
return 0;
}();
class Solution {
public:
int minimumChanges(string s, int k) {
int n = s.size();
vector<vector<int>> table(n, vector<int>(n, INT_MAX));
auto cal = [&](int l, int r, int d) -> int
{
int ans = 0;
for(int i = 0; i < d; i++)
{
int left = l + d - 1 - i;
int right = r - i;
while(left <= right)
{
if(s[left] != s[right])
ans += 1;
left += d;
right -= d;
}
}
return ans;
};
for(int l = 0; l < n; l++)
{
for(int r = l; r < n; r++)
{
int len = r - l + 1;
for(auto d : divisors[len])
table[l][r] = min(table[l][r], cal(l, r, d));
}
}
vector<vector<int>> f(n, vector<int>(k+1, -1));
function<int(int, int)> dfs = [&](int i, int remain) -> int
{
if(i == n)
{
if(remain == 0)
return 0;
return 201;
}
if(remain == 0)
return 201;
if(f[i][remain] != -1)
return f[i][remain];
int &ans = f[i][remain];
ans = 201;
for(int j = i+1; j < n - 2*(remain-1); j++)
ans = min(ans, dfs(j+1, remain-1) + table[i][j]);
return ans;
};
return dfs(0, k);
}
};