【问题标题】:Minimum Sum of All Travel Times所有旅行时间的最小总和
【发布时间】:2011-08-24 05:52:09
【问题描述】:

我在interviewStreet网上找到了一个谜题,尝试解决如下:

有一个无限整数网格,N 个人的房子在上面。他们决定 在一个共同的聚会地点团结起来,那是某人的房子。从任何给定的单元格,所有 8 相邻小区可在 1 个单位时间内到达。例如:(x,y) 可以从 (x-1,y+1) 到达 在一个单位时间内。找到一个公共的聚会地点,使总和最小化 所有人的旅行时间。

我首先想到的是及时写一个 n² 复杂度的解决方案,但限制是

1

所以,我改变了我的第一种方法,不再关注距离和旅行时间的问题,而是将不同的房屋视为具有不同重量的不同物体。我不是计算所有距离,而是寻找这组物体的重心。

这是我的“解决”函数的代码,vectorToTreat 是一个 lengthX2 表,存储有关网格上点的所有数据,resul 是要打印到标准输出的数字:

long long solve(long long** vectorToTreat, int length){
    long long resul = 0;
    int i;
    long long x=0;
    long long y=0;
    int tmpCur=-1;
    long long tmp=-1;
    for(i=0;i<length;i++){
        x+=vectorToTreat[i][0];
        y+=vectorToTreat[i][1];
    }
    x=x/length;
    y=y/length;
    tmp = max(absol(vectorToTreat[0][0]-x),absol(vectorToTreat[0][1]-y));
    tmpCur = 0;
    for(i=1;i<length;i++){
        if(max(absol(vectorToTreat[i][0]-x),absol(vectorToTreat[i][1]-y))<tmp){
            tmp = max(absol(vectorToTreat[i][0]-x),absol(vectorToTreat[i][1]-y));
            tmpCur = i;
        }
    }
    for(i=0;i<length;i++){
        if(i!=tmpCur)
            resul += max(absol(vectorToTreat[i][0]-vectorToTreat[tmpCur][0]),absol(vectorToTreat[i][1]-vectorToTreat[tmpCur][1]));
    }

    return resul;
}

现在的问题是我通过了 12 个官方测试用例超过 13 个,我看不出我做错了什么,有什么想法吗? 提前致谢。 自动曝光

【问题讨论】:

  • 问题是重心和最小化总距离的点是不同的点。如果您试图找到后者,请不要编写找到前者的算法。
  • 我不知道解决方案,但我建议尝试通过解决问题的更简单版本来获得一些见解。如果问题仅限于一维,则存在线性解决方案;找出解决方案,然后看看这是否有助于解决二维问题。
  • 我猜你的解决方案解决了这个问题,当使用毕达哥拉斯计算到对角线单元的距离时。
  • 每个单元格可以有多个房子吗?肉食点是被占用的牢房之一(房子)还是没有房子的空闲牢房,或者它没有发挥作用?您是否尝试找到平均 x 和 y 位置?你有这些的样本数据和解决方案吗?
  • 大家好,感谢大家对这篇文章的评论。 @Eric Lippert,重心与最小化总距离的点完全不同,但在我的代码中,找到重心后,我会寻找最近的点并说:这是解决方案点。

标签: c algorithm


【解决方案1】:

这个问题的关键是centroid of a set of points 的概念。对于代表所有房屋的点集,会面地点是离质心最近的房屋。使用这种方法,您可以在线性时间内解决问题,即 O(N)。我是用 Python 完成的,提交了我的解决方案并通过了所有测试。

但是,很容易构建质心方法不起作用的数据集。这是一个例子:

[(0, 0), (0, 1), (0, 2), (0, 3), 
 (1, 0), (1, 1), (1, 2), (1, 3), 
 (2, 0), (2, 1), (2, 2), (2, 3), 
 (3, 0), (3, 1), (3, 2), (3, 3), 
 (101, 101)]

最好的解决方案是在 (2, 2) 的房子里见面,费用是 121(你可以通过详尽的搜索找到这个 - O(N^2))。但是,质心方法给出了不同的结果:

  • 质心为 (7, 7)
  • 离质心最近的房子是 (3, 3)
  • 在 (3, 3) 开会的费用是 132

网站上的测试用例显然是按照质心解决方案确定的,或者他们只是想弄清楚您是否了解质心的概念。

【讨论】:

  • 只是为任何寻求解决方案的人澄清 - “会议地点是代表所有房屋的点集的质心最近的房屋” - “最近”在这里表示欧几里德距离而不是切比雪夫距离(如问题中所用)。顺便说一句,这种方法确实有效:)
  • 不适用于:(12 -14) (-3 3) (-14 7) (-14 -3) (2 -12) (-1 -6)
  • @Manan 我在自己的回答中写道,很容易找到质心方法不起作用的示例。
【解决方案2】:

我没有阅读您的代码,但请考虑以下示例:

  • 2 个人住在 (0, 0)
  • 1 个人住在 (2, 0)
  • 4 个人住在 (3, 0)

重心在 (2, 0),总行程时间最短为 8,但最优解在 (3, 0),总行程时间最短为 7。

【讨论】:

  • 在这个例子中,在 (0,0) 也有一个最优值。
  • @John,不,没有。 (0,0) 的总行程时间为 6。还是我错过了问题陈述中的某些内容?
  • 糟糕,我确实错过了一些东西,但这并不能使 (0,0) 成为最佳解决方案。
  • 嗯,这感觉有点不对劲,因为它假设我们谈论的是欧几里得距离,但我们谈论的是八连通网格(所以是 d_\infty 度量而不是 d_2)。例如,(0,0) 与 (5,0) 的距离与 (5,5) 的距离一样远,因此创建反例应该不难。我认为这个想法是合理的,但您可能应该为 d_\infty 度量而不是 d_2 度量设计一个“重心”。
  • 对不起,我看错了你的例子,你是对的 (0,0) 绝对不是这里的最佳选择。
【解决方案3】:

您好,感谢您的回答和 cmets,他们非常有帮助。 我终于放弃了使用重心的算法,当我在上面运行一些样本时,我注意到当房屋聚集在不同的村庄,它们之间的距离不同时,算法不起作用。 如果我们考虑上面@Rostor 所说的例子:

(0,0), (1,0), (2000,0), (3000,0), (3001,0), (3002, 0), (3003, 0)

使用重心的算法回答第 3 宫是解决方案,但正确答案是第 4 宫。 在这类问题中使用的正确概念是中位数,并使其适应所需的维度。 这是一篇关于The Geometric median 的精彩文章,希望对您有所帮助。

【讨论】:

    【解决方案4】:

    解决方案:

    1. 如果所有点都在一条线上,并且人们只能在两个方向(左右)移动

      对点进行排序并计算两个数组,一个如果它们只向左移动,另一个如果它们只向右移动。 添加两个向量并找到最小值以找到解决方案

    2. 如果人们只能移动 4 个方向(左、下、上、右),你可以应用相同的规则,你需要支持的是当你在一个轴上排序时,你必须能够返回,所以当排序你还必须保存排序排列

    3. 如果人们可以在 8 个方向上移动(如问题),您可以使用与在 4 个方向上使用时相同的算法(2. algorhitm),因为如果您正确观察运动,您可以看到可以做出相同数量的如果每个人都只沿对角线移动并且他们不需要左右上下移动,则移动,但如果每个点 (x,y) 都满足(x+y) % 2 == 0 - 想象网格是棋盘,房子只在黑色方块上

      在应用 2.algorhitm 之前,你必须进行点转换,所以

      (x,y) 变为 (x+y,x-y) - 这是将点旋转 45 度。然后你应用 2. 算法并将结果除以 2。

    【讨论】:

      【解决方案5】:

      “...这是某人的房子”意味着您选择一个有人居住的房子,而不是任意位置。

      编辑:糟糕,max(abs(a-A),abs(b-B)) 替换了 (abs(a-A)+abs(b-B))。当 p->infinty 时,请参阅L_p space 了解更多详情。

      (a,b) 到 (A,B) 的距离为 max(abs(a-A),abs(b-B))。一个蛮力的方法是计算在每个被占用的房子会面的总旅行时间,跟踪迄今为止最好的会面地点。

      这可能需要一段时间。质心排序可以让您优先考虑搜索顺序。我看到您正在为这个度量使用良好的质心计算:取第一个坐标的简单平均值和第二个分量的简单平均值。

      【讨论】:

      • 嗯。我不知道你的方程式。也许它应该是从 (a,b) 到 (A,B) = max(abs(a-A), abs(b-B)) 的距离,因为对角线与相邻对角线相同。从 (0,0) 到 (2,3) 的距离是 3,例如,不是 5。
      • 我认为您的距离公式不正确,因为沿对角线行驶仅需 1 个单位。只需从描述中获取示例:(x,y) can be reached from (x-1,y+1) in a single unit of time,但您的公式将返回 2 而不是 1。它应该是 max(abs(a-A), abs(b-B))
      【解决方案6】:

      如果您考虑一下距离函数,即 (x1,y1) 和 (x2,y2) 之间的旅行时间

      def dist( (x1,y1), (x2,y2)):
          dx = abs(x2-x1)
          dy = abs(y2-y1)
          return max(dx, dy)
      

      如果你在一张带网格的纸上画草图,你会看到这一点。

      所以你只需要遍历每个房子,总结其他房子的旅行时间,然后用最小和取房子。

      完整的解决方案是

      houses = [ (7,4), (1,1), (3,2), (-3, 2), (2,7), (8, 3), (10, 9) ]
      
      def dist( (x1, y1), (x2, y2)):
          dx = abs(x1-x2)
          dy = abs(y1-y2)
          return max(dx, dy)
      
      def summed_time_to(p0, houses):
          return sum(dist(p0, p1) for p1 in houses)
      
      distances = [ (summed_time_to(p, houses), i) for i, p in enumerate(houses) ]
      distances.sort()
      
      min_dist = distances[0][0]
      
      print "best houses are:"
      for d, i in distances:
          if d==min_dist:
              print i, "at", houses[i]
      

      【讨论】:

      • 原始发帖人表示他正在尝试改进的 n 平方解决方案。你能把它改进到比 n-squared 更好吗?
      • @rocksportrocker,感谢您的努力,但正如 Eric 所说,我之前确实测试过这个解决方案,但它不适用于大量输入。
      【解决方案7】:

      我在 scala 中编写了一个快速而肮脏的网格距离测试器,它将平均值与穷举搜索的最小值进行比较:

      class Coord (val x:Int, val y: Int) {
        def delta (other: Coord) = {
          val dx = math.abs (x - other.x)
          val dy = math.abs (y - other.y)
          List (dx, dy).max
        }
        override def toString = " (" + x + ":" + y + ") "
      }
      
      def run (M: Int) {
        val r = util.Random 
        // reproducable set:
        // r.setSeed (17)
      
        val ucells = (1 to 2 * M).map (dummy => new Coord (r.nextInt (M), r.nextInt (M))).toSet take (M) toSeq
        val cells = ucells.sortWith ((a,b) => (a.x < b.x || a.x == b.x && a.y <= b.y))
      
        def distanceSum (lc: Seq[Coord], cell: Coord) = lc.map (c=> cell.delta (c)).sum
      
        val exhaustiveSearch = for (x <- 0 to M-1;
          y <- 0 to M-1)
            yield (distanceSum (cells, new Coord (x, y)))
      
        def sum (lc: Seq[Coord]) = ((0,0) /: lc) ((a, b) => (a._1 + b.x, a._2 + b.y))
        def avg (lc: Seq[Coord]) = {
          val s = sum (lc) 
          val l = lc.size 
          new Coord ((s._1 + l/2) / l, (s._2 + l/2) / l)
        }
        val av = avg (ucells)
        val avgMethod = distanceSum (cells, av)
      
        def show (cells : Seq[Coord]) {
           val sc = cells.sortWith ((a,b) => (a.x < b.x || a.x == b.x && a.y <= b.y))
           var idx = 0
           print ("\t")
           (0 to M).foreach (i => print (" " + (i % 10))) 
           println ()
           for (x <- 0 to M-1) {
             print (x + "\t")
             for (y <- 0 to M -1) {
               if (idx < M && sc (idx).x == x && sc (idx).y == y) {
                 print (" x") 
                 idx += 1 }
                 else if (x == av.x && y == av.y) print (" A")
                 else print (" -")
             }
             println ()
           }
        }
      
        show (cells)
        println ("exhaustive Search: " + exhaustiveSearch.min)
        println ("avgMethod: " + avgMethod)
        exhaustiveSearch.sliding (M, M).toList.map (println)
      }
      

      这是一些示例输出:

      run (10)
           0 1 2 3 4 5 6 7 8 9 0
      0    - x - - - - - - - -
      1    - - - - - - - - - -
      2    - - - - - - - - - -
      3    x - - - - - - - - -
      4    - x - - - - - - - -
      5    - - - - - - x - - -
      6    - - - - A - - x - -
      7    - x x - - - - - - -
      8    - - - - - - - - - x
      9    x - - - - - - - - x
      exhaustive Search: 36
      avgMethod: 37
      Vector(62, 58, 59, 60, 62, 64, 67, 70, 73, 77)
      Vector(57, 53, 50, 52, 54, 57, 60, 63, 67, 73)
      Vector(53, 49, 46, 44, 47, 50, 53, 57, 63, 69)
      Vector(49, 46, 43, 41, 40, 43, 47, 53, 59, 66)
      Vector(48, 43, 41, 39, 37, 37, 43, 49, 56, 63)
      Vector(47, 43, 39, 37, 36, 37, 39, 46, 53, 61)
      Vector(48, 43, 39, 36, 37, 38, 40, 43, 51, 59)
      Vector(50, 44, 40, 39, 38, 40, 42, 45, 49, 57)
      Vector(52, 47, 44, 42, 42, 42, 45, 48, 51, 55)
      Vector(55, 52, 49, 47, 46, 47, 48, 51, 54, 58)
      

      平均值并不总是完美的位置(如本例所示),但您可以跟踪具有相同或更高值的邻居,以找到最佳位置。这是一个很好的起点,我也没有找到一个局部最优的样本,你会卡在那里。这对于庞大的数据集可能是必不可少的。

      但我没有证明是否总是如此,以及如何直接找到完美的位置。

      【讨论】:

      • 您的详尽搜索检查了所有地方,您只需要考虑房屋位置。
      • 如果您将问题限制为一维问题(例如所有 y 固定),则最佳解决方案是所有 x 位置的中值(而不是平均值)。所以你应该通过计算中位数来代替你的平均值,也许这会改进你的方法。
      • @rocksportrocker:是的,我不知何故错过了,它是其中一所房子。但是对于像 8x8 甚至 40x40 这样的小网格,计算所有位置对于我的高性能 2005 年笔记本电脑来说不是问题。它允许查看水平曲线,这可能有助于理解问题。
      • 如何构建 2-dim 网格的中位数?我可以轻松计算平均值,并搜索距离为 0、1、2、...的房子。
      • 正如我所说,中位数是 1dim 问题的解决方案。所以你必须为 x 和 y 坐标分别建立中位数。
      【解决方案8】:

      这就是我所做的

      def abs(a)
        (a < 0) ? -a : a
      end
      
      # Calculate distance between cell(i) to cell(j)
      #
      # a and b are point structures each having x, y co-ordinate
      def dist(a, b)
      
        if ((a[0] == b[0]) && (a[1] == b[1]))
          return 0
        end
      
        del_row = abs(a[0] - b[0])
        del_col = abs(a[1] - b[1])
      
        if (del_row == del_col)
          return del_row
        else
          return del_row > del_col ? del_row : del_col
        end
      
      end
      
      # Find the median cell from an array of cells
      def find_median(array)
      
        # If array is of even length, the median element is not in the array. We've to consider
        # two adjacent elements of the median. For odd case we've just one median
      
        n = array.length
      
        # Median finding can be done at O(n)...
        #
        # Sort cell array - O(nlogn)
        array = array.sort do |cell1, cell2|
      
                # Try first by comparing x
                if (cell1[0] != cell2[0])
                  cell1[0] < cell2[0] ? -1 : 1
                else
                  # Resolve tie by comparing y
                  cell1[1] <=> cell2[1]
                end
      
        end
      
        # Find median
        k = n / 2
        median_element = []
        if ((n % 2) == 0)
          median_element << array[k]
          median_element << array[k+1]
        else
          median_element << array[k]
        end
      
        median_element
      end
      
      # Calculate travel time given an array of cells and a cell indicating the meeting point
      def calculate_travel_time(array, meeting_cell)
      
        travel_time = 0
        array.each do |cell|
      
          # Skip the meeting cell itself
          if (!((cell[0] == meeting_cell[0]) && (cell[1] == meeting_cell[1])))
            travel_time = travel_time + dist(cell, meeting_cell)
          end
      
        end
      
        travel_time
      
      end
      
      def figure_out_the_meeting_point(array)
      
        if (array.nil?)
          return 0
        end
      
        n = array.length
        if (n == 0)
          return 0
        end
      
        if (n == 1)
          # Lonely person
          return 0
        end
      
        if (n == 2)
          # Just two neighbors
          return dist(array[0], array[1])
        end
      
        # Find median
        median = find_median(array)
        median_length = median.length
        min_travel_time = 0
        meeting_point = nil
        if (median_length == 1)
      
          min_travel_time = calculate_travel_time(array, median[0])
          meeting_point = median[0]
      
        else
      
          # We've two candidates. Need to check minimum of them
          t_first_median = calculate_travel_time(array, median[0])
          t_second_median = calculate_travel_time(array, median[1])
          if (t_first_median < t_second_median)
            min_travel_time = t_first_median
            meeting_point = median[0]
          else
            min_travel_time = t_second_median
            meeting_point = median[1]
          end
      
        end
      
        return min_travel_time, meeting_point
      
      end
      
      # Handle STDIN and STDOUT for I/O
      def handle_io()
      
        STDOUT.flush
        n = gets.to_i
        array = []
        (n).times do
          STDOUT.flush
          s = gets
          array << s.split(' ').map(&:to_i)
        end
      
        array
      end
      
      tt, mp = figure_out_the_meeting_point(handle_io)
      puts tt
      puts mp
      

      网格是无限的。因此,解决方案应该是整数溢出安全的。我检查了巨大的整数,Ruby 正按预期正确地将它们转换为 BigNum。

      知道为什么我没有通过所有测试用例。

      【讨论】:

        【解决方案9】:

        我尝试使用几何中位数的方法来解决这个问题。但 13 个测试用例中只有 11 个通过。这是我的策略。

        1. finding centroid of a set of points.
        2. then found the point closest to that centroid.
        

        【讨论】:

          【解决方案10】:

          我什至尝试过,但只通过了 13 个测试用例中的 4 个。 他们说的分段错误。

          但我已经制作了两个数组,每个数组 100001 和一些变量 m 使用。

          我的算法。

          找到给定点的质心。

          找到离质心最近的点。 使用maximum(abs(a-A),abs(b-B))得到所有距离的总和。

          【讨论】:

            猜你喜欢
            • 2022-06-16
            • 2022-12-23
            • 2022-09-30
            • 1970-01-01
            • 1970-01-01
            • 2012-03-13
            • 2015-12-30
            • 1970-01-01
            • 2023-02-20
            相关资源
            最近更新 更多