vlambda博客
学习文章列表

利用二分查找法来解一道列表排序题

前言

学习过数据结构的同学应该都对二分查找法比较熟悉。二分查找法又被称为折半查找法,它的前提是所要查找的一列数据是有序的,在有序的基础上进行折半搜索,这样可以大大降低搜索时间。
Python中,有一个标准模块叫bisect,它很好地实现了二分查找,并对边界情况进行了处理,所以,对于Python用户而言,直接利用这个模块进行相应的操作,就会显得非常方便。
前几天在刷leetcode时,碰到这样一道题目,它初看起来不难,但仔细思考却发现里面有很多陷阱,不过如果能利用好二分查找这一工具,那这道题目就迎刃而解。
题目如下:
有一个列表,它的每个元素都是有序整数数对,其形式如[[a1,b1], [a2,b2], [a3,b3],...[an, bn]],根据数的大小可知,在其中总会找到一个子集[[ai1, bi1], [ai2, bi2], [ai3, bi3],..., [aim, bim]],使得该子集中的元素满足ai1<ai2<ai3<...<aimbi1<bi2<bi3<...<bim。现在要问的是,在这所有符合条件的子集中,其长度最长会是多少?
以一个例子来说明,比如有一个列表是这样的:[[5, 4], [6, 4], [6, 7], [2, 3]],在其中[[2, 3], [5, 4], [6, 7]]是最长的符合要求的子集,因此最长的子集长度是3。
有兴趣的同学先不要往下看,可以自行解答一下。



为了解答这个问题,我们先来介绍一下Python中的bisect标准库。

bisect标准库

bisect 标准库主要实现二分查找功能,它其中包含六个函数,分别是bisect, bisect_left, bisect_right, insort, insort_left, insort_right,其中bisectbisect_right的简写形式,insortinsort_right的简写形式,所在,从实际上来说,该标准库只有四个不同的函数。下面我们来一一说明。
bisect_left、bisect_right
这两个函数可在一列数组中查找某数,当所查找的数位于数组中时,返回该数应该处于其原位置的左侧或右侧,比如:

上述代码中,我们在数组 [0, 1, 2, 3, 4, 5] 中查找 1 这个元素,结果查找到了 1 位于数组的第1位(数组从0开始计位),所以它直接返回该位置,如果用函数 bisect_right ,则会返回 1 的下一个位置2:

利用二分查找法来解一道列表排序题

对于不存在于数组中的数,这两者返回值一样,此时返回值代表可以在数组的当前位置将所给数字插入,如下所示:

利用二分查找法来解一道列表排序题

insort_left、insort_right
这两个函数顾名思议,是将一个数插入已经排序的数组中,并形成新的有序数列,如果所给的数是在所给数列中,那么insort_left是插入同数左侧,而insort_right则是插入同数右侧,但这两种区别仅限于内部运行,对于外在表现而言,两者相同:

利用二分查找法来解一道列表排序题

对于不在所给数列中的数,两个函数反应一致:

对题目的分析
现在我们来分析一下题目,由于所给的列表中每个元素都是有序数对,为了求得满足条件的子集,我们将采用两步来解决这个问题,以[[5, 4], [6, 4], [6, 7], [2, 3]]为例来说明。
第一步,先按有序数对的第一个数进行升序排列:
[[2, 3], [5, 4], [6, 4], [6, 7]]
观察上述列表,会注意到如果能将[6,7]放在[6,4]前面,则更容易找到找出符合条件的子集,因此我们在上述排序的基础上,在第一个数相同的有序数对中,再以第二个数的降序进行排列,这样的结果如下:
[[2, 3], [5, 4], [6, 7], [6, 4]]
第二步,遍历整个数组,对有序数对的第二个数进行分析,此时可设立一个空列表arr3,然后用birect.birect_leftarr3中查询这些有序数对的第二个数,如果得到的位置等于arr3的长度,则说明找到了新的值,将其加入arr3,否则只需要将arr3中相应位置进行更换即可,代码如下:

注意,这里的dp存放的并不一定是真实符合条件的子集中数对的第二个数集合,它只是对有序数对第二个数的一种查找和替换,其长度说明了在这个已经按有序数对第一个数排好序的列表中,含有这么个长度的符合条件的子集而已。

小结

本文对一道数组题目进行了分析,并介绍了Python的一个标准模块,这个问题并不容易理解,在此记录仅供备忘和大家参考。