[Coursera][计算导论与C语言基础][Week 10]对于“数组应用练习”课后习题的思考题的一些想法

(首先,关于Honor Code,我咨询过了Help Center,分享课后练习的思考题的想法是可以的(注意不是代码),但要标明引用,引用格式来源于https://guides.lib.monash.edu/citing-referencing/apa-university-course-materials。)

北京大学(Producer). (2019) . 计算导论与C语言基础[Coursera] . Retrieved from https://www.coursera.org/learn/jisuanji-biancheng/programming/eWlOp/shu-zu-ying-yong-lian-xi

题目1:循环移动

注意: 总时间限制: 1000ms 内存限制: 65536kB

描述

  给定一组整数,要求利用数组把这组数保存起来,再利用实现对数组中的数循环移动。假定共有n个整数,则要使前面各数顺序向后移m个位置,并使最后m各数变为最前面的m各数。

  注意,不要用先输出后m个数,再输出前n-m个数的方法实现,也不要用两个数组的方式实现。

  要求只用一个数组的方式实现,一定要保证在输出结果时,输出的顺序和数组中数的顺序是一致的。

输入

  输入有两行:第一行包含一个正整数n和一个正整数m,第二行包含n个正整数。每两个正整数中间用一个空格分开。

输出

  输出有一行:经过循环移动后数组中整数的顺序依次输出,每两个整数之间用空格分隔。

样例输入

11 4
15 3 76 67 84 87 13 67 45 34 45

样例输出

67 45 34 45 15 3 76 67 84 87 13

提示

  这是一道经典的算法问题,在企业面试里出现概率很高。除了循环m次每次移动一个数以外(这样需要对数组操作m*n次),你还能想到更高效的算法吗(只用操作3*n次)?依然要求不使用额外数组,在原数组上移位之后顺序输出。

想法:

  思路1:

    其实最简单的想法,比循环m次每次移动一个数,更容易想到的方法就是扩大存储数的数组大小,但这个时候,就要操作$2n-m$次,空间复杂度就要变成$O(m+n)$,当$m$很大的时候就很不划算了。

  思路2:

    其实关键的问题在于我们移动位置的时候,没有空位,所以一个想法就是制造一个空位,在存储数的数组最后加一位,然后每次对空位操作,将空位前$m+1$的数移过来,直到数组第0位为空位。(说实话,程序编出来,尝试了几组数据可行,感觉也是可行,但证明的时候,就不行了。)


题目2:中位数

注意: 总时间限制: 2000ms 内存限制: 65536kB

描述

  中位数定义:一组数据按从小到大的顺序依次排列,处在中间位置的一个数或最中间两个数据的平均值(如果这组数的个数为奇数,则中位数为位于中间位置的那个数;如果这组数的个数为偶数,则中位数是位于中间位置的两个数的平均值).

  给出一组无序整数,求出中位数,如果求最中间两个数的平均数,向下取整即可(不需要使用浮点数)

输入

  该程序包含多组测试数据,每一组测试数据的第一行为N,代表该组测试数据包含的数据个数,1 <= N <= 15000.

  接着N行为N个数据的输入,N=0时结束输入

输出

  输出中位数,每一组测试数据输出一行

样例输入

4
10
30
20
40
3
40
30
50
4
1
2
3
4

样例输出

25
40
2

提示

  这是也一道经典的算法问题,在企业面试里出现概率很高,是“找到第K大的数”的变种。先排序再找中位数自然是很直接的做法,但排序本身很慢。我们只想找到第n/2大的数,对于其他数的顺序我们并不关心。那么怎么在不排序的前提下找到第n/2大的数呢?

想法

  我们直接讨论“找到第K大的数”。

  思路1:

    咨询了一下NOI银牌(目前),金牌(必定)选手#YZZX_hhr#,他跟我说这个问题其实等价于找“K个最大的数”,所以一个比较显然的算法,就是每次对整个数组访问一遍,找到最大的数,然后挑出来。然后从挑出来的数中找到最小的(这个可以通过一个变量,存储当前挑出来的数中最小的来实现)。时间复杂度在$O(kn)$,当$k$比较大的时候,比如$k>log(n)$时,就不如排序了。

  思路2:

    也是hhr给的想法,二分,先扫一遍,找到最大,最小,然后二分,显然时间复杂度$O(n*log(n))$,跟排序差不多。

  思路3:

    据hhr所言,这个有线性算法,但非常复杂,叫严格线性快速选择,据说有相关论文,没找到。


题目3:校门外的树

注意: 总时间限制: 1000ms 内存限制: 65536kB

描述

  某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。

  马路上有一些区域要用来建地铁,这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。

输入

  输入的第一行有两个整数L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。

输出

  输出包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。

样例输入

第一组
500 3
150 300
100 200
470 471
第二组
500 3
100 200
150 160
180 190

样例输出

第一组
298
第二组
400

提示

  由于数据范围不大(L<=10000),我们可以使用一个10001长度的数组来记录每一个坐标上有没有树。但想象一下如果数据范围很大,比如下面这个情况,你怎么办呢?

输入

 
5000000 3
1500000 3000000
1000000 2000000
4700000 4700001

输出

2999998
 
 想法:
  这道题就很经典了。
  思路1:
    其实我最开始的想法是线段树。。。后来想想没必要那么复杂。
  思路2:
    其实这道题的一个改进就是,原本用一个长度为L的数组记录每个位置被访问(就是被区间覆盖的次数),每次给定区间,不仅要遍历每一个元素,而且还占空间,所以这里我们使用差分法,就是构建一个新的数组,其中每一项是原本那个长度为L的数组的对应项减去前一项的值。这个时候给定区间后,只要在起点+1,在终点后一位-1就行了。注意,此时如果想从新数组恢复到原数组,只要一个for循环,边遍历边累加,每个位置累加的值就是原数组对应项的值。
  思路3:
    思路2还有一个问题就是如果L太大,并且区间的覆盖很稀疏,这个时候不仅浪费,而且可能开不了这么大的数组,所以一个优化就是:先读进所有的区间,然后进行离散,即对0,L,和所有区间的起点,终点进行升序排序,然后将他们重新对应新的数字(如1,5,2000 -> 1,2,3),这个时候只要至多$2M+2$的空间就够了,然后统计的时候,复原即可。
 
 
 
上一篇:Acwing.95 费解的开关(状压枚举)


下一篇:Coursera课程笔记----计算导论与C语言基础----Week 8