算法笔记:从极端情况到目标情况的优化求解

最近心情是非常沉郁啊,南大面试真是搞我心态。哎,也没啥好抱怨的,写篇博客舒缓一下心情,总结总结经验之后继续面吧。

这篇博客的灵感是来自于在论文中实际应用的一个算法,最开始面对我要求解的问题的时候,算法的时间复杂度令我非常的绝望,是一个指数的算法复杂度。后来在做启发式的算法的时候,偶然发现,这个问题竟然可以在线性时间内求解,感觉非常的惊喜,算法确实是一个很奇妙的东西。

在这篇博客中,我首先会通过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把我给问死了。老师语重心长的对我说,写博客就像教学一样,要严谨准确,保证传达给别人的东西是正确的。我后来想了想这个问题,我觉得吧,博客这玩意,要真写严谨了,就成教科书了,我可没有精力去写教科书。我觉得,博客还是去分享一些我觉得有价值的点,留作记录,最好也能帮助后来可能与我有相同困惑的人,想写的全面正确可太难了。

好了,今天就说这么多,如果大家觉得这个算法有问题或者可以改进,欢迎与我联系,毕竟写论文这事还是要严谨的,哈哈哈~

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值