从一道算法面试题看我国信息科技的原创性不足:查找包含所有元素的最短子数组

前不久我遇到这样一道算法面试题:在一个包含重复元素的数组中,找到一个最短子数组,要求该子数组包含了整个数组的所有元素,例如给定数组:7, 3, 7, 3, 1, 3, 4, 1,包含所有元素的最短子数组为 7, 3, 1, 3, 4。

我们先看看如何处理该问题。给定一个数组a[0…n],假设包含所有元素的最短子数组为a[t…h],我们如何找到数组的起始下标t,和结尾下标h呢。我们试想一下把数组往前或往后扩展一下,我们先看往前扩展一个元素,也就是数组a[t-1,t,…h],根据原来假设,a[t…h]是包含所有元素的最短子数组,那么我们必然得出一个结论,那就是a[t-1]一定出现在子数组a[t…h]中,假设a[t-1]不存在a[t…h]中,那么a[t…h]就不能包含数组的所有元素,由此产生矛盾,同理可推论a[h+1]也一定存在子数组a[t…h]中。

现在问题在于,我们并不知道t和h的值,但我们可以确定的是,只要任何一个子数组,如果它包含了数组的所有元素,那么最短子数组就有可能被这个子数组所包含,所以算法要点就是先找到一个包含所有元素的子数组,然后再看看能不能对其进行压缩,看看是否能在一个包含所有元素的子数组中,确定最短子数组。

算法第一步是查找给定数组中的所有元素,做到这个不难,我们先遍历数组,然后将当前访问到的元素加入哈希表,如果元素在表中已经存在,说明该元素是重复元素,可以直接忽略,如此遍历一遍后,我们就能得到该数组的所有元素。

第二步我们用两个指针start, end,一开始他们都指向起始元素,然后end开始往后遍历。由于第一步我们已经记录了数组所有元素,我们让这些元素都对应一个计数值1,当end遍历的元素的计数为1时就表明我们访问到了一个数组新元素,如果其计数已经是0,说明这个元素已经遍历过了。当遍历到新元素时,我们统计已经遍历到的新元素数量,如果新元素数量等于第一步中我们统计的元素数量个数,那说明当前数值a[start…end]包含了所有元素。

第三步我们看能不能对a[start…end]进行压缩,在第二步遍历元素时,我们同时用另外一个哈希表elements_count记录遍历到的元素次数,如果元素a[end]被遍历了两次,那么elements_count[a[end]] 就等于2,当a[start…end]包含所有元素后,我们开始从start对数组进行压缩,有就是看a[start]这个元素是不是多余的,此时我们在elements_count中查询,如果elements_count[a[start]]的值大于1,那说明在当前数组中a[start]被访问了多次,因此我们可以压缩它,也就是执行start +=1,这样数组的长度就能缩短一个元素,如此一直进行直到elements_count[a[start]]的值为1,此时我们不能继续对a[start…end]进行压缩。

此时我们得到的子数组a[start…end]可能是包含所有元素的最短子数组,也有可能不是。我们需要继续探寻,以确认后面是否会存在包含所有元素但长度更短的子数组。具体做法是我们执行start += 1,这样子数组a[start…end]就不再是包含了所有元素的子数组,因为此时它缺少了元素a[start-1],但我们可以再此基础上快速构建一个包含所有元素的子数组,那就是继续让end往后遍历,一旦a[end]等于a[start-1]时,子数组a[start…end]又再次包含了所有元素,于是我们又能重复前面提到的压缩步骤,当end抵达数组末尾后,当前所能找到的包含所有元素,而且长度又是最短的子数组就是我们所需要的子数组,具体代码实现如下:

def sub_array_contains_all_element(array):
    single_element_map = {}
    elements_count = 0
    freq_map = {}
    for a in array:
        if (a in single_element_map) is False:
            single_element_map[a] = 1
            elements_count = elements_count + 1
        freq_map[a] = 0


    start = 0
    end = 0
    sub_array_start = 0
    sub_array_end = len(array) - 1

    while end < len(array):
        freq_map[array[end]] += 1
        if single_element_map[array[end]] == 1:
            single_element_map[array[end]] = 0
            elements_count -= 1

        if elements_count != 0:
            end += 1
        else: #当前数组包含了所有元素,看看能不能压缩长度
            while start <= end:
                if freq_map[array[start]] > 1:
                    freq_map[array[start]] -= 1
                    start += 1
                else:
                    if end - start < sub_array_end - sub_array_start:
                        sub_array_start = start
                        sub_array_end = end
                    break
            '''
            start 指向的元素计数为1,越过当前元素后,end 往后移动时必须再次遇到array[start]对应的元素,
            所得数组才能包含所有元素,于是才能再次进行压缩
            '''
            single_element = array[start]
            start += 1
            end += 1
            single_element_map[single_element] = 1 #这里的设置让end往后挪动时必须再次遇到single_element才能再次压缩
            elements_count += 1

    return sub_array_start, sub_array_end

array = [7, 3, 7, 3, 1, 3, 4, 1, 4,3,1,7,7,3,4]
start, end = sub_array_contains_all_element(array)
print(f"sub array start:{start}, end:{end}, with elements: {array[start:end+1]}")

上面代码运行后输出结果为:
sub array start:8, end:11, with elements: [4, 3, 1, 7]
由于我们的算法只需要对数组进行两次遍历,因此算法复杂度为O(n)。接下来说说为何从这道题我就能感觉我们信息科技行业的创新性不足呢,这是因为这道题最初来自Leetcode,使用这道题进行面试的公式在”借用“基础上进行了”微创新“,它给题目加了很多屁话烟雾弹,我记得当时题目是这样的:”小明最近加班辛苦,好不容易得到休假,于是他想出现旅游放松一下。它找来旅行社制定了一个景点游历计划,但是旅行社的计划并不是最优的,有些景点可能会去过之后后面又会再次进行重复参观,假设景点用数组表示为{7, 3, 7, 3, 1, 3, 4, 1},不同的数字表示不同的景点,由于小明假期有限,因此需要在最短时间内将所有景点参观完…"。

我们可以看到本来很清楚的题意被这样“创新”后意思变得模糊起来,你必须要反复阅读这些文字多次你才有可能知道它想让你干什么,于是算法题硬生生的被其改造成语文阅读理解,它会无缘无故浪费你大量的时间和精力。通过观察我发现,我们国内绝大多数公式用于算法面试的题目都不是原创,百分之九十九来自LeetCode,例如国内模仿hackerrank的牛客网就是这样,题目提取自LeetCode,然后用一些干扰人的屁话进行包装,,如果你刷LeetCode的话,你几乎看不到与题意无关的多余描述,

设想一下你连用于面试的题目都做不到原创,你在技术上又怎么可能有创新的底气呢,单单从一些面试算法题上就能看出为何我国的科技行业与老美总是有很大差距的原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值