天池比赛小结1
巧妙利用回溯法解决路径提交所引发的血案
0 引言
前一阵子参加了天池比赛气象数据领航无人飞行器线路优化大赛,凭借着初赛一步步的稳扎稳打以及最后时刻的一波套磁 + 人品爆发,以第三名的成绩进入了决赛。但在决赛阶段,我们所遇到的最大问题并不是此次赛题中的两个重点:气象数据回归或路径规划算法,令我们差点止步前 10 的是提交路径之后,由于不同路径之间的各种组合导致的无法推知到底是哪些路径成功抵达。后来我们用了一些巧妙的解法在某种程度上解决了这个问题(并没有完全解决,下文会细讲),因此,这也是此篇博客诞生的原因,想要记录下解决这一个问题的思路。
1 问题
该比赛要求我们规划 5 天之中的 10 条飞行路线,即总共要规划 5*10 = 50 条,并要求时间最短,安全性最高。那么,这就会引发如下问题:
- 某几条路径都是最短路径,所以其耗时是一样的。譬如:从起始点飞往城市 2 所耗费的最短时间是 200 min,那么如果我们所提交的路径之中5天的飞往城市2的时间都是最短时间,那么我们则无法推知到底是哪几条过了。
- 不同路径之间的组合导致同一个分数有多种方案。譬如:飞往城市 1 用了 150 min, 飞往城市 2 用了250 min , 飞往城市 3 用了 100 min,飞往城市 4 用了 300 min,那么,如果最后的成绩显示是 400 min,那么我们将无法得知是路径1和2到达,还是路径3和4到达。
好,那我们其实在决赛前有想到会出现这种情况,不过我们还是too young,忽略了问题的复杂性,我们一开始的想法是只要对每条路径进行微调(在时间允许范围内+2/+4/+8啥的)从而区分耗时相同的路径即可,然而…这种解决方式会加剧问题 2 (譬如路径A+B = C+D
,微调过后仍会出现 (A+2)+ (B+6)= (C+4) + (C+4)
等情况的出现),即会出现更多的不同路径组合而总消耗时间相同的情况。 所以还要花很多时间在微调数的设置上,并没有从根本上解决问题。
2 伪解决方案
为什么说其是伪解决方案呢,因为该解决方案并没有完全解决我们的问题,但是可以某种程度上解决我们的问题。话不多说,我们最终采用的方案如下:
- Step 1 : 每次提交只提交少量的线路,比如,一次提交只涉及5天中某一天的10条路线
- Step 2:通过基于
dfs
的改进k数和算法
检验这些线路是否存在不同组合而消耗相同时间的问题 - Step 3:若有,则继续微调每条线路的耗时,若没有,则可以提交
3 K数和
其实这个问题究其本质,就是K数和问题。
K数和问题定义:
Given n unique integers, number k (1<=k<=n) and target.
Find all possible k integers where their sum is target.
样例:
给出[1,2,3,4],k=2, target=5,返回 [[1,4],[2,3]]
解题思路
利用递归与回溯来解
代码:
Java
1234567891011121314151617181920212223242526272829public class Solution {/*** @param A: an integer array.* @param k: a positive integer (k <= length(A))* @param target: a integer* @return a list of lists of integer*/public ArrayList<ArrayList<Integer>> kSumII(int A[], int k, int target) {// write your code hereArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();ArrayList<Integer> path = new ArrayList<Integer>();helper(result, path, A, k, target, 0);return result;}public void helper(ArrayList<ArrayList<Integer>> result,ArrayList<Integer> path,int [] A,int k ,int remain,int index){if(path.size() == k){if(remain ==0)result.add(new ArrayList<Integer>(path));elsereturn;}for(int i = index; i< A.length; i++){path.add(A[i]);helper(result, path, A, k, remain - A[i], i+1);path.remove(path.size() - 1);}}}Python
123456789101112131415161718192021222324class Solution:"""@param A: An integer array.@param k: A positive integer (k <= length(A))@param target: Integer@return a list of lists of integer"""def kSumII(self, A, k, target):# write your code herepath = []result = []self.helper(result,path,A,k,target,0)return resultdef helper(self,result , path ,A ,k,target,index):if len(path) == k:if target==0:result.append(path[:])else:for i in range(index,len(A)):path.append(A[i])self.helper(result,path,A,k,target - A[i] ,i + 1)path.pop()
4 K数和&字典
要将K数和与本问题结合,还需要做一点小小的处理。即我们希望最后是直接输出路径的组合,而非路径耗时的组合,因此,最终我们的代码如下:
以上代码未考虑到线上评分机制:
目标函数值 = 2460飞行器坠毁数 + 顺利到达的飞行器总飞行时长(分钟)
考虑以上机制后,我们的代码改进为:
全部思路大致如上。接下来即可实现两个功能:
- 线下模拟评测不同耗时下的路线组合数,从而对路线进行微调
- 根据线上成绩,快速得到成功到达的路径组合