有趣的问题

Published: by Creative Commons Licence

  • Tags:

文件命名问题

给定n个文件名,第i个文件名记作namei,要求我们在文件系统中创建对应的n个文件。

文件系统的规则是这样的,如果我们要创建名字为x的文件,那么如果文件系统中已经存在同名文件了,我们就需要将新创建的文件名改成x(1),x(2),...中第一个未被使用的名字。

要求判断每个文件在创建后的文件名是啥。

题目地址

一个简单的做法,就是我们充当文件系统的角色,用哈希表维护所有出现的文件名,然后逐一去判断。下面给出代码:

String getName(String x, int index) {
  String y = x;
  if(index > 0) { 
    y += "(" + index + ")"
  }
  return fileSystem.contains(y) ? getName(x, index + 1) : y;
}

但是稍微思考一下就会发现,如果文件名都相同,那么时间复杂度会恶化到O(n2)

实际上上面的问题我们可以O(n)解决,做法就是为每个名字x,都维护一个计数器C(x),表示目前已知x的最大编号,初始值为1

接下來,当我们处理文件名x的时候,如果C(x)=1就尝试x,否则我们先尝试x(C(x)+1),如果文件系统存在了就尝试下一个。一直到找到第一个不存在的文件名x(t)。之后我们将C(x)更新为t

下面我们证明上面算法的时间复杂度是O(n)。可以发现上面过程中可能会导致时间复杂度超过O(n)的部分就是上面的循环尝试x(C(x)+1),x(C(x)+2),...的部分,这时候已经确认x存在于文件系统了。

考虑x(C(x)+1)尝试失败的原因,这个文件只可能是因为我们首次创建了一个名字叫做x(C(x)+1)的文件。同时发现x(C(x)+1)这个文件的创建也只会使得x的循环部分增加1。

因此我们知道最终只会创建最多n个名字,因此上面循环试错阶段总长度也是O(n)的,总的时间复杂度就是O(n)

环上最小相邻元素差

给定n个数a1,,an。我们将其重新排列组合,组成一个环b1,,bn,其中b1bn相邻。记这样的排列的费用为maxi|bi+1bi|。找出一个排列使得费用最小。

一道牛客的题目

对于n2的情况,这个问题是微不足道的。考虑n>2的情况。

我们先认为a1,,an是递增的(如果不是,就先排个序)。之后我们考虑序列a1,a3,a5,,a4,a2,即我们将下标为奇数的按原序排在前面,下标为偶数的按逆序排在后面。记c为这样排列的费用,我们证明c是最小的费用。

假设最优排列的费用t小于c,则我们可以找到一个下标i,满足ai+2ai=c。我们将下标小于等于i的数记作第一类数,将ai+1记作第二类数,将小标大于等于i+2的数记作第三类数。我们可以发现在最优排列中第一类数和第三类数是不允许相邻的。但是由于第一类数和第三类数都是存在的,且第二类数仅有一个。我们从环上删除唯一的第二类数,这时候环变成一个链表,此时第一类数和第三类数一定会相邻。

牛吃饲料问题

一条直线上,在位置0处有n条牛,第i条牛需要吃ai单位草才能吃饱。现在有m个地方提供饲料,第i个地方的位置在坐标xi。每个饲料点同时只能喂养一头牛,每头牛可以有如下每秒钟可以做如下操作:

  • 向左或向右移动1单位距离
  • 如果处于某个饲料点,则可以吃1单位饲料

问最少花多少时间,才能把所有牛喂饱。其中1ai,xi109,1n,m105

这道题来源于SRM739的HungryCowsMedium。

一开始我的想法是错误的,我认为最优解情况下一头牛只可能在一个饲料点吃草,这时候问题变成有m个背包,n个物品,判断是否有一种方案可以将所有物品装入到背包中。在背包数目多于2的情况下,问题是无法求解的。

但是实际上牛可以在多个位置吃草。比如a=[4,4,4]x=[2,4]的情况下,答案是9

我们可以发现如果答案是t,则位于xi的饲料点能提供的饲料量为max(0,txi)。我们按照这些草料产生的时间从大到小编号,第一份的编号为1。我们可以通过预先对xi进行排序,因此可以发现每个饲料点能提供的饲料量随着下标的增大而递减。

下面我们发现如果一头牛k如果在第i,j个饲料点都吃了草,且i<j,且记lk(i)为牛k在第i个位置吃的第一份草的编号,则rk(i)表示牛k在第i个位置吃的最后一份草的编号,可以发现rk(i)<lk(j)

我们可以发现第i头牛只能吃前L(i)个位置的草料。这就给了我们一个贪心的思路,就是我们按牛的食量从大到小进行排序。而牛从第一个位置的草料开始吃,并优先吃编号较大的草料。

众数问题

题目1:给定n个数a1,a2,,an,以及整数k,要求找出其中任意一个出现超过n/k次的元素,如果有多个,任意输出其中一个,否则输出1

我们可以不断从集合中删除k个不同的元素,直到不同的元素数目少于k,如果一个数出现超过n/k次,则其最终一定会被剩下来。这个过程可以O(kn)完成。

之后对每个出现的元素,考虑它们的出现次数即可。时间复杂度为O(kn),空间复杂度为O(k)。当然如果我们直接统计每个数出现的次数时间复杂度为O(n),空间复杂度为O(n)

题目2:给定n个数a1,a2,,an,记f(k)表示我们允许每次删除k个不同的元素,最多能执行多少次操作。要求输出f(1),f(2),,f(n)

一道题目

我们先进行离散化,记ci表示整数i出现的次数。对于给定的k,我们可以设计一个贪心算法,就是每次选择出现次数最多的k个数各删除一个。这个算法跑一轮的时间复杂度为O(nlog2n),总的时间复杂度为O(n2log2n)

我们发现如果一个数出现次数超过n/k,则我们每次都必定会选择它,换言之,问题实际上在问,在剩余的数中,我们每次可以选择k1个不同的数删除,问最多执行多少次操作。

命题:对于任意n0,如果每个数的出现次数不超过n/k,那么总共可以执行操作nk次。

证明:通过归纳法证明。当n<k的时候,因为每个数出现次数不超过n/k,即每个数出现次数都是0,因此答案是0。对于nk,且n1时命题成立,那么由于每个数出现次数都不超过n/k,因此出现次数最多的k个数,都至少出现了1次。我们将出现次数按从大到小排序,记做b1,b2,,bm。我们选择最多的k个数执行一次操作后,接下来需要证明所有数次数不超过(nk)/k=n/k1。很显然对于b11,,bk1是满足条件的,我们再考虑bk+1。由两种情况,一种是bk+1<b1,这时候由于bk+1b11,因此所有数都满足条件。还有一种情况是bk+1=b1,若bk+1>n/k1bk+1n/k。则由于b1b2bk+1,即k+1i=1bi(n/k)(k+1)>n,这是不可能的。因此我们证明了对于任意非负整数n,命题都成立。

模运算下的比较

题目1:给定一个序列a0,a1,,an1,其中ai=y+ix(modm)x是给定的数,要求计算有多少个j,满足j<n1aj<aj+1。其中1n,m,x,y109

如果x=0,则答案为0。否则我们可以通过统计有多少项j,满足ajaj+1来得到结果。由于x0,因此我们只需要考虑aj>aj+1的情况即可。

这个不是很好统计,但是如果我们能注意到此时一定有(j+1)x+ym=jx+ym+1,那么问题就迎刃而解了。

答案就是(n1)x+ym