利用二分查找法来解一道列表排序题
前言
学习过数据结构的同学应该都对二分查找法比较熟悉。二分查找法又被称为折半查找法,它的前提是所要查找的一列数据是有序的,在有序的基础上进行折半搜索,这样可以大大降低搜索时间。
在Python
中,有一个标准模块叫bisect
,它很好地实现了二分查找,并对边界情况进行了处理,所以,对于Python
用户而言,直接利用这个模块进行相应的操作,就会显得非常方便。
前几天在刷leetcode
时,碰到这样一道题目,它初看起来不难,但仔细思考却发现里面有很多陷阱,不过如果能利用好二分查找这一工具,那这道题目就迎刃而解。
有一个列表,它的每个元素都是有序整数数对,其形式如[[a1,b1], [a2,b2], [a3,b3],...[an, bn]]
,根据数的大小可知,在其中总会找到一个子集[[ai1, bi1], [ai2, bi2], [ai3, bi3],..., [aim, bim]]
,使得该子集中的元素满足ai1<ai2<ai3<...<aim
且bi1<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
,其中bisect
是bisect_right
的简写形式,insort
是insort_right
的简写形式,所在,从实际上来说,该标准库只有四个不同的函数。下面我们来一一说明。
这两个函数可在一列数组中查找某数,当所查找的数位于数组中时,返回该数应该处于其原位置的左侧或右侧,比如:
上述代码中,我们在数组
[0, 1, 2, 3, 4, 5]
中查找
1
这个元素,结果查找到了
1
位于数组的第1位(数组从0开始计位),所以它直接返回该位置,如果用函数
bisect_right
,则会返回
1
的下一个位置2:
对于不存在于数组中的数,这两者返回值一样,此时返回值代表可以在数组的当前位置将所给数字插入,如下所示:
这两个函数顾名思议,是将一个数插入已经排序的数组中,并形成新的有序数列,如果所给的数是在所给数列中,那么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_left
在arr3
中查询这些有序数对的第二个数,如果得到的位置等于arr3
的长度,则说明找到了新的值,将其加入arr3
,否则只需要将arr3
中相应位置进行更换即可,代码如下:
注意,这里的dp
存放的并不一定是真实符合条件的子集中数对的第二个数集合,它只是对有序数对第二个数的一种查找和替换,其长度说明了在这个已经按有序数对第一个数排好序的列表中,含有这么个长度的符合条件的子集而已。
小结
本文对一道数组题目进行了分析,并介绍了Python
的一个标准模块,这个问题并不容易理解,在此记录仅供备忘和大家参考。
标签: