本文涉及知识点
LeetCode 3149. 找出分数最低的排列
给你一个数组 nums ,它是 [0, 1, 2, …, n - 1] 的一个排列
。对于任意一个 [0, 1, 2, …, n - 1] 的排列 perm ,其 分数 定义为:
score(perm) = |perm[0] - nums[perm[1]]| + |perm[1] - nums[perm[2]]| + … + |perm[n - 1] - nums[perm[0]]|
返回具有 最低 分数的排列 perm 。如果存在多个满足题意且分数相等的排列,则返回其中字典序最小的一个。
示例 1:
输入:nums = [1,0,2]
输出:[0,1,2]
解释:
字典序最小且分数最低的排列是 [0,1,2]。这个排列的分数是 |0 - 0| + |1 - 2| + |2 - 1| = 2 。
示例 2:
输入:nums = [0,2,1]
输出:[0,2,1]
解释:
字典序最小且分数最低的排列是 [0,2,1]。这个排列的分数是 |0 - 1| + |2 - 2| + |1 - 0| = 2 。
提示:
2 <= n == nums.length <= 14
nums 是 [0, 1, 2, …, n - 1] 的一个排列。
动态规划
动态规划的状态表示
dp[mask][sel] 记录 最低分数。 (1 << i )&mask 表示 i是已经选择,最后选择的是sel。
分数不包括sel。
perm[0]会影响分数,所以分别枚举。这样时间复杂度:
O(14
×
\times
× 214 1414)
≈
\approx
≈ 108 超时。
看了高手的题解,发现perm[0]一定是0,因为旋转pem 分数不变。
空间复杂度: O(2nn)
vPre[mask][sel] 记录当前状态的前置状态。
动态规划的转移方程
三层循环:前置状态的mask,前置状态最后的选择,当前的选择。转移的时候如果分数相同,要取字典序小的。
细节很多。
时间复杂度: O(2nnn)
动态规划的填表顺序
mask和pre都从小到大。
动态规划的初值值
dp[1<<0][0]=0,其它非法。
动态规划的返回值
x = 0 : n-1 dp.back()[x] 最小对应的排列。
动态规划代码
核心代码
template<class ELE,class ELE2>
void MinSelf(ELE* seft, const ELE2& other)
{
*seft = min(*seft,(ELE) other);
}
template<class ELE>
void MaxSelf(ELE* seft, const ELE& other)
{
*seft = max(*seft, other);
}
class Solution {
public:
vector<int> findPermutation(vector<int>& nums) {
m_c = nums.size();
const int iMaskCount = 1 << m_c;
pair<int, vector<int>> ret;
ret.first = m_iNotMay;
vector<int> tmp1, tmp2;
auto Do = [&](int first) {
vector<vector<int>> dp(iMaskCount, vector<int>(m_c, m_iNotMay));
vector<vector<pair<int, int>>> vPre(iMaskCount, vector<pair<int, int>>(m_c, make_pair( -1,-1) ));
auto PairToV = [&](vector<int>& tmp,pair<int, int> p) {
tmp.clear();
while (-1 != p.second) {
tmp.emplace_back(p.second);
p = vPre[p.first][p.second];
}
};
auto Less = [&]( pair<int, int> p1, pair<int, int> p2) {
PairToV(tmp1, p1);
PairToV(tmp2, p2);
for (int i = tmp1.size() - 1; i >= 0; i--) {
if (tmp1[i] < tmp2[i]) { return true; }
if (tmp1[i] > tmp2[i]) { return false; }
}
return false;
};
dp[1 << first][first] = 0;
for (int mask = 0; mask < iMaskCount; mask++) {
for (int pre = 0; pre < m_c; pre++) {
if (dp[mask][pre] >= m_iNotMay) { continue; }
for (int sel = 0; sel < m_c; sel++) {
if (mask & (1 << sel)) { continue; }
const int iNewScore = dp[mask][pre] + abs(pre - nums[sel]);
auto& dpNew = dp[mask + (1 << sel)][sel];
auto& preNew = vPre[mask + (1 << sel)][sel];
if (iNewScore < dpNew) {
dpNew = iNewScore;
preNew.first = mask;
preNew.second = pre;
}
else if (iNewScore == dpNew) {
auto tmp = make_pair( mask,pre );
if (Less(tmp , preNew)) {
preNew.swap(tmp);
}
}
}
}
}
for (int pre = 0; pre < m_c; pre++) {
dp.back()[pre] += abs(pre - nums[first]);
vector<int> v;
PairToV(v,vPre.back()[pre]);
std::reverse(v.begin(), v.end());
v.emplace_back(pre);
pair<int, vector<int>> cur = make_pair( dp.back()[pre] ,v );
MinSelf(&ret, cur);
}
};
Do(0);
return ret.second;
}
int m_c;
const int m_iNotMay = 1'000'000;
};
测试用例
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i] == v2[i]);
}
}
template<class T>
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
int main()
{
vector<int> nums;
{
Solution slu;
nums = { 1,0,2 };
auto res = slu.findPermutation(nums);
Assert({ 0,1,2 }, res);
}
{
Solution slu;
nums = { 0,2,1 };
auto res = slu.findPermutation(nums);
Assert({ 0,2,1 }, res);
}
{
Solution slu;
nums = { 9,5,4,6,3,11,10,12,7,2,0,1,8 };
auto res = slu.findPermutation(nums);
Assert({ 0,10,5,1,11,6,3,4,2,9,12,7,8 }, res);
}
{
Solution slu;
nums = { 1,5,3,8,10,6,11,13,0,4,12,7,9,2 };
auto res = slu.findPermutation(nums);
Assert({ 0,8,3,2,13,7,11,4,9,12,10,6,5,1 }, res);
}
}
直接记录个状态的mask 用时的大约是3倍
能过。
template<class ELE,class ELE2>
void MinSelf(ELE* seft, const ELE2& other)
{
*seft = min(*seft,(ELE) other);
}
template<class ELE>
void MaxSelf(ELE* seft, const ELE& other)
{
*seft = max(*seft, other);
}
class Solution {
public:
vector<int> findPermutation(vector<int>& nums) {
m_c = nums.size();
const int iMaskCount = 1 << m_c;
pair<int, vector<int>> ret;
ret.first = m_iNotMay;
auto Do = [&](int first) {
vector<vector<int>> dp(iMaskCount, vector<int>(m_c, m_iNotMay));
vector<vector<vector<int>>> vPre(iMaskCount, vector < vector<int>>(m_c));
dp[1 << first][first] = 0;
vPre[1 << first][first].emplace_back(first);
for (int mask = 0; mask < iMaskCount; mask++) {
for (int pre = 0; pre < m_c; pre++) {
if (dp[mask][pre] >= m_iNotMay) { continue; }
for (int sel = 0; sel < m_c; sel++) {
if (mask & (1 << sel)) { continue; }
const int iNewScore = dp[mask][pre] + abs(pre - nums[sel]);
auto& dpNew = dp[mask + (1 << sel)][sel];
auto& preNew = vPre[mask + (1 << sel)][sel];
if (iNewScore < dpNew) {
dpNew = iNewScore;
preNew = vPre[mask][pre];
preNew.emplace_back(sel);
}
else if (iNewScore == dpNew) {
auto tmp = vPre[mask][pre];
tmp.emplace_back(sel);
if (tmp < preNew) {
preNew.swap(tmp);
}
}
}
}
}
for (int pre = 0; pre < m_c; pre++) {
dp.back()[pre] += abs(pre - nums[first]);
pair<int, vector<int>> cur = { dp.back()[pre] ,vPre.back()[pre] };
MinSelf(&ret, cur);
}
};
Do(0);
return ret.second;
}
int m_c;
const int m_iNotMay = 1'000'000;
};
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。