【问题标题】:Time complexity on simulation exercise模拟练习的时间复杂度
【发布时间】:2013-09-24 14:32:06
【问题描述】:

我遇到了算法复杂性的问题,我尝试了下面的解决方案,认为当我忘记考虑时间限制时这很容易。我在下面添加了我的代码,我什至不确定它的复杂性。我很想知道O(N)O(K) 解决方案是什么。如果有人能帮忙解决这个问题,将不胜感激。

Time Limit: 1 second

有 K 个座位可用,每个座位都由一个物理座位表示,位于一个圆圈周围。 K+1 人最初也站在圆圈周围的点上。圆上的点从 1 到 N 顺时针标记,这样点 1 紧跟在 N 点之后。最初不会有两个人站在同一点,也不会有两把椅子在同一点。

每一秒,所有仍然站立的人(同时)执行以下操作:

  • 如果此人与空椅子站在同一位置,则此人将坐在其中。

  • 否则,此人将围绕圆圈顺时针移动一个位置到下一个点。如果此人之前位于点 i(i

由于有K+1人,最终K个位子都会被占据,剩下的一个人没有位子。坐在圈子里第一个座位的人将有最好的位置。 (圆圈中的“第一个”座位定义为从点 1 顺时针方向的第一个座位。)

您的任务是确定谁将坐在第一个座位上,谁将是站起来的人。

输入 N 和 K。
K 个空格分隔的整数,表示有椅子的点,按升序排列。因此,此列表中的第一把椅子将是第一个座位
K+1 个空格分隔的整数,表示人的点,按升序排列。这些人按他们在此列表中的位置从 1 到 K+1 编号。

1≤N≤1000000
1≤K≤100000

输出
第一个座位的人
人站在原地

Sample   

Input   
10 3   
2 5 8   
3 4 6 8   

Output   
3   
1   

在第一秒,第四个人(在第 8 点)将立即坐在他们下方的椅子上。其他三个人将围绕圆圈移动一位。下一秒,第二个人(现在在位置 5)将坐在第二个座位上。第一个人和第三个人继续绕圈走,直到第三个人到达位置 2 坐下,第一个人没有椅子可以坐。

while(standing != 1)
{
    for (int i = 0; i < K + 1; i++)
    {
        if (sitting[i] == 0)
        {
            people[i]++;

            if (isSitting(people[i], chairs,i)) //this function checks if the current person is at a chair
            {
                sitting[i] = 1;
                standing--;
            }

            if (people[i] > N)
            {
                people[i] = 1;
            }
        }
    }

    }
   standingPerson = indexOf(sitting,K+1 ,0);

此尝试在几乎所有测试输入上都超时,仅通过了 8/30 个案例。


这是一些使用其他用户建议的 O(k) 解决方案的代码。转c++

    int seat1 = chairs[0], best = -1, accum = 1;
int unlucky[] = {0, -1};

for (int pos; pos < K + 1; pos++) {
    if (people[pos] <= seat1) {
        best = people[pos] + 1;
        accum -= 1;
    } else
        break;
}

if (accum < 0) {
    unlucky[0] = accum;
    unlucky[1] = 1;
}

int i = K, j = K - 1;
while (i >= 0 && people[i] > seat1) {
    if (chairs[j] >= people[i]) {
        accum += 1;
        j -= 1;
    } else {
        accum -= 1;
        i -= 1;
    }
    if (best == -1 && accum == 0) {
        best = i + 2;
    }
    if (accum < unlucky[0]) {
        unlucky[0] = accum;
        unlucky[1] = i + 2;
    }
}
fprintf(out_file, "%d\n%d", best, unlucky[1]);

【问题讨论】:

  • 提示:人和椅子的确切位置并不重要。一个人最终坐的椅子仅取决于人和椅子的顺序。你能看出为什么吗? (免责声明:确保这是真的,因为我目前睡眠不足。)
  • @user2357112 我有点想类似的东西,但不知道他们最终会坐在哪把椅子上
  • 解决这个问题的一种方法是意识到如果没有人站在一个人和下一张空椅子之间,那个人会坐在那把椅子上,你可以把这个人和椅子从问题。

标签: c++ python performance algorithm simulation


【解决方案1】:

要确定谁将坐在第一个座位上,我们需要从第一个座位开始反向计算座位/人数,并在人数等于座位数时停止。为了实现这一点,我们需要一个累加器,它在遇到座位时增加,在遇到人时减少。当这个累加器为零时停止。

要确定谁将是站起来的人,我们可以继续以相同的方式更新累加器,并找到该累加器第一次获得最小值的位置。对于这个累加器不是最小值的任何人的位置(或者当这个最小值比其他最小值更接近数组的开头时),我们可以在数组后面找到这个累加器具有相同值的位置,这意味着有足够的位置让这个人就座。

更详细的解释见下图。它显示所有可能的座位/人员位置的累加器值。虽然算法只遍历输入数组一次,但该图显示了这些数组遍历了 3 次的累加器值,因此可以看出问题的周期性。

每个数组遍历的值彼此相似。唯一的区别是,每次我们到达圆上的同一点时,值都会减一。

很容易注意到,除了一个之外的所有累加器值都具有以下属性:我们总是可以在圆圈中找到另一个具有相同值的点。这意味着这两点之间的座位数和人数相等。这意味着每个起始位置在这两个点之间的人也将坐在这两个点之间的某个位置:右边的所有人都离开这个间隔并且不要假装坐在这些座位上。左边的人都来得太晚了。

这些等值点之间的间隔的一些有趣情况是:(1) 当间隔跨越数组边界时,(2) 两个最小值之间的间隔,(3) 当人旁边有空座位时间隔非常短,或者如果它们在同一点。

只有一个位置 (4),对应于最后一个最小累加器的值(或者如果我们向后搜索,则为第一个)不具有此属性。右边没有相同值的点。处于此初始位置的人将保持站立状态。

这是 Python 中的工作代码:Ideone link

def solve(k, s, p):
  seat1 = s[0]
  best = -1
  unlucky = (0, -1) # min(accum), position
  accum = 1

  # process all persons between start of the array and the seat #1 position
  for i, pos in enumerate(p):
    if pos <= seat1:
      best = i+1
      accum -= 1
    else:
      break

  if accum < 0:
    unlucky = (accum, 1)

  # process all seats/persons in reverse direction
  i = k
  j = k-1
  while i >= 0 and p[i] > seat1:
    if s[j] >= p[i]: # a seat
      accum += 1
      j -= 1
    else: # a person
      accum -= 1
      i -= 1

    if best == -1 and accum == 0:
      best = i+2 # +1 because indexing starts with 0 & +1 because of pre-decrement

    if accum < unlucky[0]:
      unlucky = (accum, i+2)

  return (best, unlucky[1])


print(solve(3, [2,5,8], [3,4,6,8]))

由于每个座位/人只检查一次,时间复杂度为 O(k)。

【讨论】:

  • @user1411838:我实现了二次时间朴素算法来检查结果:ideone.com/cn12Su。结果是相同的。这意味着我仍有可能遗漏了什么,但可能性很小。
【解决方案2】:

首先让我们通过将表视为两个circular buffers 来简化这个问题。一张给椅子,一张给人。我们可以说缓冲区包含椅子“C”、人“P”和空位“E”。

所以假设我们说循环缓冲区从 1 开始,您的示例可以重写为

E C E E C E E C E E

E E P P E P E P E E

在最坏的情况下,您当前的解决方案将是O(N*K)。想象一下这样的配置:

P P E E E E E E E

E E E E E E E E C

您的算法通过移动人员缓冲区七次来解决上述问题。你通过检查所有人并单独转移每个人来实施转移。因此,您的算法使O(N)(办公桌大小)发生变化,每次变化都涉及将所有O(K) 人移动一个位置。因此O(N*K)。不过,我认为这是一个非常悲观的上限,平均而言你会快得多。


我认为一个好的O(K) 可以在两个指针的帮助下实现。一个指向人员缓冲区的指针和另一个指向椅子缓冲区的指针。这个想法是分别通过两个缓冲区并将等待的人与可用的椅子匹配。这个想法是,每当您看到椅子时,如果有人在等待,那么您可以将他与那张椅子相匹配。如果没有人在等待,则将椅子添加到可用椅子队列中。如果在我们通过两个缓冲区之后有可用的椅子和可用的人,那么我们知道这些人需要通过桌子的1 点才能找到椅子,我们可以通过以下方式将它们与可用的椅子匹配将最后一个等候的人与第一个可用的椅子相匹配。

除了两个指针之外,您还可以使用两个队列来跟踪可用的椅子和等候人员。下面是解决方案的伪代码版本。

queue<Person> personQueue;
queue<Chair> chairQueue;
int cptr=0, pptr=0;

while(cptr < K || pptr < K+1) 
    if (cptr >= K) {
        //no chairs ahead of us only behind us
        personQueue.addAllRemainingPersons()
    }
    else if(pptr >= K+1 || chair[cptr] < person[pptr]) {
        // the currently pointed at chair is at a lower position 
        // than the pointed at person,
        // so we match this chair with a waiting person instead
        match(personQueue.back, chair[cptr]);
        cptr++;
    }
    else {
        //add person to the waiting-for-chair queue
        personQueue.push_back(person[pptr]);
        pptr++;
    }
}
// at this point we have a situation with many chair in front of many persons
// for example
// CCCCPPPPP
// This we solve by matching the last person with the first chair 
// until only one is left
while(!cQueue.empty()) {
    match(personQueue.back, chairQueue.front);
}

确定谁是坐在第一把椅子上的人可以在匹配功能中进行,您有足够的信息可以分辨。仍然站着的人是唯一没有找到座位的人(唯一一个我们不会称之为匹配的人)。

【讨论】:

  • 这个想法在理论上似乎很简单,但我能想到的唯一方法是使用嵌套循环,我不太确定如何使用指针。我将如何编写循环缓冲区?感谢您的帮助
  • @user1411838 是的,我认为您可能需要一个嵌套循环。外部循环将检查是否只剩下一个人(可能是计数器),内部循环将移动指针。这很好,因为您知道指针在到达初始位置之前不能移动超过N 次,在此之前只会剩下一个人。
  • @user1411838 对于循环缓冲区,您可以使用固定大小的数组。然后指针可以是整数,它将索引该数组中的位置。如果指针到达数组末尾之外,则将指针设置为零(循环)。如果指针低于零,则将其设置为N,这将是数组的长度(再次循环)。
  • 我对整个嵌套循环的事情有点困惑,这个解决方案是否仍然是 O(N),如果是,请解释一下,所有这些对我来说都是新的
  • @user1411838 与所有算法一样,最好用纸和笔试一试。这样想吧。在外循环的每一步,您都会移除一个人和一把椅子。所以你知道外部循环运行K 次。内部循环仅用于查找下一个要删除的人。总的来说(在外循环的所有运行中),您将永远不会将内循环中的指针移动超过N 次,因为只有N 点需要检查。这是嵌套循环 = O(n^2) 之类的简单经验法则不起作用的算法之一 :)
猜你喜欢
  • 2014-12-19
  • 1970-01-01
  • 2016-02-13
  • 2012-08-14
  • 2013-09-12
  • 2018-08-02
  • 1970-01-01
  • 2019-09-15
  • 1970-01-01
相关资源
最近更新 更多