暴力枚举下标三元组 (i, j, k),更新答案 sum = min(sum, nums[i] + nums[j] + nums[k])。
/*
* @lc app=leetcode.cn id=2908 lang=cpp
*
* [2908] 元素和最小的山形三元组 I
*/
// @lc code=start
class Solution
{
public:
int minimumSum(vector<int> &nums)
{
int n = nums.size();
int sum = INT_MAX;
for (int i = 0; i < n - 2; i++)
for (int j = i + 1; j < n - 1; j++)
for (int k = j + 1; k < n; k++)
if (nums[i] < nums[j] && nums[j] > nums[k])
sum = min(sum, nums[i] + nums[j] + nums[k]);
return sum == INT_MAX ? -1 : sum;
}
};
// @lc code=end
时间复杂度:O(n3),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
枚举 nums[j] + 前后缀分解。
定义 preMin[i] 为前缀最小值,初始化 preMin[0] = nums[0]
,递推公式:preMin[i] = min(preMin[i - 1], nums[i])
。
定义 sufMin[i] 为后缀最小值,初始化 sufMin[n - 1] = nums[n - 1]
,递推公式:sufMin[i] = min(sufMin[i + 1], nums[i])
。
枚举 nums[j],答案为 preMin[j - 1] + nums[j] + sufMin[j + 1]
的最小值。
/*
* @lc app=leetcode.cn id=2909 lang=cpp
*
* [2909] 元素和最小的山形三元组 II
*/
// @lc code=start
class Solution
{
public:
int minimumSum(vector<int> &nums)
{
int n = nums.size();
vector<int> preMin(n);
preMin[0] = nums[0];
for (int i = 1; i < n; i++)
preMin[i] = min(preMin[i - 1], nums[i]);
vector<int> sufMin(n);
sufMin[n - 1] = nums[n - 1];
for (int i = n - 2; i >= 0; i--)
sufMin[i] = min(sufMin[i + 1], nums[i]);
int minimumSum = INT_MAX;
for (int j = 1; j < n - 1; j++)
if (preMin[j - 1] < nums[j] && nums[j] > sufMin[j + 1])
minimumSum = min(minimumSum, preMin[j - 1] + nums[j] + sufMin[j + 1]);
return minimumSum == INT_MAX ? -1 : minimumSum;
}
};
// @lc code=end
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(n),其中 n 是数组 nums 的长度。
贪心。
统计每个数字的出现次数,记在哈希表 hash 中。
假设可以分成大小为 k 和 k+1 的组,现在需要算出对于每个数 num,每个 hash[num] 最少可以分成多少组。
设 q = freq / k,r = freq % k。
如果 q < r 则无法分成 k 和 k+1 组,否则一定可以分组。
在可以分组的前提下,分出的 k+1 越多,组数就越少,所以最少可以分出 ceil(freq / (k + 1)) 组。
累加组数即为分组个数。
从 min(hash[num]) 开始倒着枚举 k,只要可以分,就立刻返回答案。
/*
* @lc app=leetcode.cn id=2910 lang=cpp
*
* [2910] 合法分组的最少组数
*/
// @lc code=start
class Solution
{
public:
int minGroupsForValidAssignment(vector<int> &nums)
{
unordered_map<int, int> hash; //
for (const int &num : nums)
hash[num]++;
auto cmp = [](const auto &a, const auto &b) -> bool
{
return a.second < b.second;
};
int k = min_element(hash.begin(), hash.end(), cmp)->second;
int ans = 0;
for (;; k--)
{
for (auto &[num, freq] : hash)
{
int q = freq / k, r = freq % k;
if (q < r)
{
ans = 0;
break;
}
ans += ceil((double)freq / (k + 1));
}
if (ans)
break;
}
return ans;
}
};
// @lc code=end
时间复杂度:O(n),其中 n 是数组 nums 的长度。设哈希表的大小为 m,哈希表中最小的 value 为 k,由于所有 value 之和为 n,所以 k * m ≤ n 。而循环次数又至多为 k * m,所以时间复杂度为 O(n)。
空间复杂度:O(n),其中 n 是数组 nums 的长度。
题解:预处理+记忆化搜索/递推(附题单!)Python/Java/C++/Go
/*
* @lc app=leetcode.cn id=2911 lang=cpp
*
* [2911] 得到 K 个半回文串的最少修改次数
*/
// @lc code=start
// 预处理每个数的真因子,时间复杂度 O(MX * log(MX))
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>> modify(n - 1, vector<int>(n));
for (int left = 0; left < n - 1; left++)
for (int right = left + 1; right < n; right++)
{
string subStr = s.substr(left, right - left + 1);
modify[left][right] = get_modify(subStr);
}
vector<vector<int>> memo(k, vector<int>(n, n + 1)); // n+1 表示没有计算过
function<int(int, int)> dfs = [&](int i, int j) -> int
{
if (i == 0)
return modify[0][j];
int &res = memo[i][j]; // 注意这里是引用
if (res <= n)
{ // 之前计算过
return res;
}
for (int L = i * 2; L < j; L++)
res = min(res, dfs(i - 1, L - 1) + modify[L][j]);
return res;
};
return dfs(k - 1, n - 1);
}
// 辅函数 - 计算字符串 s 变成半回文串需要修改的字符数目
int get_modify(string s)
{
int n = s.size(), res = n;
for (int d : divisors[n])
{
int cnt = 0;
for (int i0 = 0; i0 < d; i0++)
for (int i = i0, j = n - d + i0; i < j; i += d, j -= d)
cnt += s[i] != s[j];
res = min(res, cnt);
}
return res;
}
};
// @lc code=end
时间复杂度:O(n3logn),其中 n 是字符串 s 的长度。时间主要在预处理上,有 O(n2) 个子串,平均每个子串有 O(logn) 个因子,每个因子需要 O(n) 的时间计算修改次数。
空间复杂度:O(n2),其中 n 是字符串 s 的长度。