最近心情是非常沉郁啊,南大面试真是搞我心态。哎,也没啥好抱怨的,写篇博客舒缓一下心情,总结总结经验之后继续面吧。
这篇博客的灵感是来自于在论文中实际应用的一个算法,最开始面对我要求解的问题的时候,算法的时间复杂度令我非常的绝望,是一个指数的算法复杂度。后来在做启发式的算法的时候,偶然发现,这个问题竟然可以在线性时间内求解,感觉非常的惊喜,算法确实是一个很奇妙的东西。
在这篇博客中,我首先会通过leetcode中的一个题目举例,说明算法的大致思想,之后我会给出论文中要求解问题的一个抽象表达,并求解这个问题。
首先,我们先看leetcode的1029题——两地调度,我把题目的内容直接复制在下面。
公司计划面试 2N 人。第 i 人飞往 A 市的费用为 costs[i][0],飞往 B 市的费用为 costs[i][1]。
返回将每个人都飞到某座城市的最低费用,要求每个城市都有 N 人抵达。
示例:
输入:[[10,20],[30,200],[400,50],[30,20]]
输出:110
解释:
第一个人去 A 市,费用为 10。
第二个人去 A 市,费用为 30。
第三个人去 B 市,费用为 50。
第四个人去 B 市,费用为 20。
最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。
这个题目比较简单,我看了一下leetcode的解答,方法也比较多,但是基本思路我把它归为一点——首先考虑极端情况,再从极端情况向目标情况过渡。
首先,正常看到这个问题,一定想在一次扫描的过程中就把人员安排好,但这实际上是不可能的,因为在第一次扫描的过程中,我们并不知道后面会发生什么情况。比如,如果我们采用贪心算法,每次就选择费用最小的城市,可能我们会提前把去A市的名额用完,所以到后面即使出现了更好的去A的人选,我们已经无法把他派去A了。
那么我们应该采用什么方法呢?我在这里提供一种思路,我首先不管去A的名额去B的名额满没满,每次我就选费用最小的,但是我会记录下来每次选费用最小的,到底少花了多少钱,并记录去A和去B到底去了多少人。扫描一遍之后,如果去A的人少了,那就从去B的人中,选出“省钱省的最少的”几个人去A,如果去A的人多了,就从去A的人里面挑。
这个题目虽然很简单,但是我觉得思路是非常好的。通过考虑极端情况,记录极端情况的发生条件,来降低我们思维的难度,帮助我们向目标情况平稳优化过渡。
下面附上代码:
class Solution {
public:
int twoCitySchedCost(vector<vector<int>>& costs) {
int N = costs.size()/2;
int count = 0;
vector<int> values1;
vector<int> values2;
int minSum = 0;
for(int i=0;i<2*N;i++)
{
int value1 = costs[i][0];
int value2 = costs[i][1];
if(value1<value2)
{
minSum+=value1;
count++;
values1.push_back(value2-value1);
}
else
{
minSum+=value2;
values2.push_back(value1-value2);
}
}
if(count==N)
return minSum;
if(count<N)
{
sort(values2.begin(),values2.end());
for(int i=0;i<abs(N-count);i++)
{
minSum+=values2[i];
}
return minSum;
}
sort(values1.begin(),values1.end());
for(int i=0;i<abs(N-count);i++)
{
minSum+=values1[i];
}
return minSum;
}
};
好,下面进入正题,我们来看这个问题的求解。
已知两个序列,A与B,已知A,B两个序列的长度相同,假设长度为n,并且只含有0,1,2三种取值。我们定义序列与序列“异或”操作用来产生新的序列。举例如下:
A: 0 1 2
B: 2 1 0
我们可以看到,两个序列的第一位和第三位是不一样的。那么我们产生的新序列,在这两位可以从A,B两个序列中对应的位置任意取值。因为有两位不一样,所以我们最后可以产生2^2=4个序列,包括:012,210,010,212. 第二位因为A,B都相同,所以维持不变。
问题:满足上述描述的A,B两个序列,在所有由A,B“异或”操作产生的序列中,序列的均值最接近于1的序列的均值为多少?
这个问题,同样刚开始看的时候比较困难,但是如果先从极端值考虑问题,就会容易很多。我们首先考虑,这样子产生的序列,最大均值和最小均值的情况分别是怎样的?
这两种情况都是比较好分析的,在做所谓“异或”操作的时候,我们每一位只要取大的,或者小的就可以了。对于得到的这两个序列,我们其实不关心它的具体排列顺序,我们记录这么几个值:1.均值最大的序列2的个数减去0的个数,记为X. 2.均值最小的序列2的个数减去0的个数,记为Y。3. 是否出现0与1,1与2之间的异或操作,记为Z。之后,我们继续分析什么时候均值最接近于1.
如果X,Y都大于0,那么说明即使是均值最小的序列,其均值都大于1.那么,均值最小的序列自然就是最接近1的序列。求其均值即可。
如果X,Y都小于0,那么说明即使是均值最大的序列,其均值都小于1.那么,均值最大的序列自然就是最接近1的序列。求其均值即可。
如果X>0而Y<0,那么说明,这个序列可以产生的均值可以在1的上下浮动。如果序列的取值是任意的,那么这是个完全背包问题,解不了,但是这里只有0,1,2三种取值,问题就简单多了。首先,因为跨度最大为2(0变2,2变0),所以最糟糕的情况,我们也能得到最接近于1的均值(n-1)/n或者是(n+1)/n。如果在异或操作中,出现了0与1,1与2之间的异或操作,那么我们就可以得到完美的n/n=1.即便是全部都是0与2之间的替换,如果X或Y为偶数(这两个条件是一个意思),也可以理解成发生0,2替换的数量为偶数,那么我们就能得到均值为1,否则均值为(n-1)/n或者是(n+1)/n。
对于X>0而Y<0,如果Z为true或X为偶数,则均值为1.否则,均值为(n-1)/n或者是(n+1)/n。
代码暂时就不放上去了,最近没大有时间写,论文的代码又暂时不能放。
在南大面试的时候,谈到了写博客这个事情,然后老师问quick sort把我给问死了。老师语重心长的对我说,写博客就像教学一样,要严谨准确,保证传达给别人的东西是正确的。我后来想了想这个问题,我觉得吧,博客这玩意,要真写严谨了,就成教科书了,我可没有精力去写教科书。我觉得,博客还是去分享一些我觉得有价值的点,留作记录,最好也能帮助后来可能与我有相同困惑的人,想写的全面正确可太难了。
好了,今天就说这么多,如果大家觉得这个算法有问题或者可以改进,欢迎与我联系,毕竟写论文这事还是要严谨的,哈哈哈~