问题描述:
第一题:现在想通过交换相邻元素的操作把一个给定序列交换成有序,最少需要交换的次数是多少?比如3 1 2 4 5需要最少交换2次。
答案:需要交换的最少次数为该序列的逆序数。
证明:可以先将最大数交换到最后,由于是相邻两个数交换,需要交换的次数为最大数后面的数的个数(可以看做是最大数的逆序数),然后,交换过后,去除最大数,再考虑当前最大数也需要其逆序数次交换。则每个数都需要交换其逆序数次操作,则总最少交换次数为序列总体的逆序数。
class Solution {
public:
int minSwap(vector&nums)
{
if(nums.empty()||nums.size()==1)return 0;
int res=0;
for(int i=0;inums[j])
{
res+=1;
}
}
}
retrun res;
}
};
第二题:现在想通过交换任意两个元素的操作把一个给定序列交换成有序,最少需要交换的次数是多少?
答案:我认为是数字的总个数减去循环节的个数。
循环节的求法是,先将数组排序,然后根据之前的坐标和排序之后的坐标,构建成一个有向图,然后在这个图上找到环
/*
* 交换任意两数的本质是改变了元素位置,
* 故建立元素与其目标状态应放置位置的映射关系
*/
int getMinSwaps(vector &A)
{
// 排序
vector B(A);
sort(B.begin(), B.end());
map m;
int len = (int)A.size();
for (int i = 0; i < len; i++)
{
m[B[i]] = i; // 建立每个元素与其应放位置的映射关系
}
int loops = 0; // 循环节个数
vector flag(len, false);
// 找出循环节的个数
for (int i = 0; i < len; i++)
{
if (!flag[i])
{
int j = i;
while (!flag[j])
{
flag[j] = true;
j = m[A[j]]; // 原序列中j位置的元素在有序序列中的位置
}
loops++;
}
}
return len - loops;
}
第三题:给定数组 A 和 B ,请返回使得两个数组均保持严格递增状态的最小交换次数。假设给定的输入总是有效的。
示例:
输入: A = [1,3,5,4], B = [1,2,3,7]
输出: 1
解释:
交换 A[3] 和 B[3] 后,两个数组如下:
A = [1, 3, 5, 7] , B = [1, 2, 3, 4]
两个数组均为严格递增的。
注意:
A, B 两个数组的长度总是相等的,且长度的范围为 [1, 1000]。
A[i], B[i] 均为 [0, 2000]区间内的整数。
依照定义,确实可以通过dfs来逐个交换并且判断。但是时间复杂度是几何上升的。
一般经验是这种可以解决的问题都少不了dp。
但是dp需要一个定义,我们又怎样来定义呢?
所以这里实际上需要两个数组或者说是一组二元变量。
用swap数组中的swap[i]表示第i个元素如果要交换来使得AB成为递增序列那么需要多少次的代价。
用keep数组的keep[i]表示第i个元素不交换使得两个数组依然有序需要多少代价。
举个例子,我们再推导转移方程。
针对A=[1,3,5,7] B=[1,2,3,4]
swap[0]=1,keep[0]=0表示第0个元素为了使其保持有序,交换确实可以,但付出了1次的代价,不交换也可以,0次代价。
这只是初始化,真正的大头在后面。
swap[1]=2,keep[1]=0,这个也很显然,因为只看前两个元素的话,已经是满足条件了。那么如果我非要交换来使得顺序成立,那就需要2次,但是不交换依然可以,0次。
swap[2]=3,keep[2]=0,道理同上一段的推导。
但是swap[3]=1,keep[3]=3,这又是什么道理呢?观察AB的末尾,容易看出其实只要交换了7和4就满足了条件了,那么swap就是1。但是如果我不想交换这个元素,那么就需要把前三个都交换一次,就是3.
经过上述的推导,我们发现keep[i],swap[i]总是与keep[i-1],swap[i-1]存在着某种联系。
首先有两种情况:
一、第i个元素是满足增序的,也就是A[i]>A[i-1],B[i]>B[i-1]因为只要保持就可以了,所以keep[i]=keep[i-1],但是如果要交换,那就比swap[i-1]还多了个第i个元素的代价,也就是swap[i]=swap[i-1]+1.
二、A[i]>B[i-1] 而且B[i]>A[i-1]这种情况满足了“是不是我可以通过交换使得满足条件”,值得一提的是这两个条件应该同时判断,因为谁也不知道那种情况下会有更小的代价。但是如果交换就会是keep[i-1]+1的代价(因为实际上就是翻转了第i个再加上让前i-1个保持的代价),同理keep[i]此时等于swap[i-1]因为不翻转这个,但要翻转前i个。
代码:
//dp1[i]表示在不交换A[i]和B[i]的情况下,使得A的前i + 1个元素和B的前i + 1个元素严格递增的最小交换次数;
//dp2[i]表示在交换A[i]和B[i]的情况下,使得A的前i + 1个元素和B的前i + 1个元素严格递增的最小交换次数。
class Solution {
public:
int minSwap(vector& A, vector& B) {
int length = A.size();
vector dp1(length, INT_MAX);
vector dp2(length, INT_MAX);
dp1[0] = 0;
dp2[0] = 1;
for(int i=1; iA[i-1]&&B[i]>B[i-1]){
dp1[i] = dp1[i-1];
dp2[i] = dp2[i-1]+1; //需要交换 因此+1
}
if(B[i]>A[i-1]&&A[i]>B[i-1]){
dp1[i] = min(dp1[i], dp2[i-1]);
dp2[i] = min(dp2[i], dp1[i-1]+1); //如果dp1[i - 1] + 1 < dp2[i],那么说明在维持第i - 1对元素的次序的情况下,交换第i对元素可以达到更优解
}
}
return min(dp1.back(), dp2.back());
}
};