繁体   English   中英

模拟练习的时间复杂性

[英]Time complexity on simulation exercise

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

Time Limit: 1 second

有K个席位可供选择,每个席位由一个圆圈周围的实际座位代表。 K + 1人最初也站在圈子周围的点上。 圆圈上的点从1到N顺时针标记,使得点1紧跟在点N之后。没有两个人最初站在同一点,并且没有两个椅子将在同一点。

每一秒,所有仍然站立的人都会做同样的事情(同时):

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

  • 否则,该人将围绕圆圈顺时针移动一个位置到下一个点。 如果此人之前在i点(i <N),则该人现在将处于i + 1点。 如果此人之前在N点,则该人现在将在第1点。

由于有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/3​​0个案例。


以下是一些使用其他用户建议的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]);

首先,让我们将表视为两个循环缓冲区来简化这个问题。 一个用于椅子,一个用于人。 我们可以说缓冲区包含椅子'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);
}

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

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

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

有关详细说明,请参见下图。 它显示所有可能的座位/人员位置的累加器值。 虽然算法只遍历输入数组一次,但此图表显示这些数组的累加器值经过三次,因此可以看到问题的周期性。

累加器值

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

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

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

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

这是Python中的工作代码: Ideone链接

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)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM