二维数组内的二分查找<转载>
2013-08-30 18:15
225 查看
【题目】
已知一个二维整数数组(或者说,一个整数矩阵),每行从左到右递增,每列从上往下递增。要求定义一个函数,输入矩阵和某个整数k,判断k是否在这个矩阵内。
【思路】
像四叉树那样递归查找,这是一种思路,不过我不是很喜欢。我希望一个循环就能搞定,不希望碰到类似于回溯之类的逻辑。
如果一个循环就搞定,那么最好是能像切西瓜一样,把不要的部分一点点切掉,同时剩下的部分仍旧是规整的一块。
观察以下矩阵(为方便行文,矩阵内全是正整数,大于0小于10的数在十位上补0)。
01,02,08,09
02,04,09,12
04,07,10,13
06,08,11,15
易得,该矩阵的最小值m在左上角,最大值M在右下角。易证得,这不是个案,是个普遍的规律;并且,从这样的一个矩阵中任意圈出一个子矩阵,也符合这个规律。一个整数k,如果小于m或者大于M,则必然不在这个矩阵内。
继续探索这种矩阵的性质,看是否有线索。
将该矩阵分成上下两部分:
01,02,08,09
02,04,09,12
------------
04,07,10,13
06,08,11,15
如果一个整数k,大于上半部分右下角的值(这里是12),则k必然不在上半部分。
如果一个整数k,小于上半部分左上角的值(这里是04),则k必然不在下半部分。
将该矩阵分成左右两部分:
01,02 | 08,09
02,04 | 09,12
04,07 | 10,13
06,08 | 11,15
如果一个整数k,大于左半部分右下角的值(这里是08),则k必然不在左半部分。
如果一个整数k,小于右半部分左上角的值(这里是08),则k必然不在右半部分。
有以上推论做基础,我们就可以设计“切蛋糕”式的算法了。
【方法1】
先说个思路,不太严格:
01:在最上面的一条边上,通过二分法查找k,找到就退出。否则,找到比k大的最小整数,将其所在及右边的列统统“切掉”。
02:在最下面的一条边上,通过二分法查找k,找到就退出。否则,找到比k小的最大整数,将其所在及左边的列统统“切掉”。
03:在最左面的一条边上,通过二分法查找k,找到就退出。否则,找到比k大的最小整数,将其所在及下边的行统统“切掉”。
04:在最右面的一条边上,通过二分法查找k,找到就退出。否则,找到比k小的最大整数,将其所在及上边的行统统“切掉”。
05:以上每一步中,“切”之前的矩阵大小如果为0,则失败退出;以上四步结束后,如果还没找到,则返回01,继续切。
举例如下。在以下矩阵中寻找07。
01,02,08,09
02,04,09,12
04,07,10,13
06,08,11,15
过程如下:
01:最上面一行,找不到07,而比07大的最小整数是08。切一刀,得到以下矩阵
01,02
02,04
04,07
06,08
02:最下面一行,找不到07,而比07小的最小整数是06。切一刀,得到以下矩阵
02
04
07
08
03:最左面的一列……好吧,找07应该很容易了。顺利退出。
以上所描述的算法,有两个问题没解决:第一,是否已经最优;第二,怎样从数学上证明,“算法找到目标数k”的充分条件就是“矩阵含有目标数k”。后者不难证,前者要考虑。
【方法2】
仍旧使用这个例子:在以下矩阵中寻找07。
01,02,08,09
02,04,09,12
04,07,10,13
06,08,11,15
考察方法1切割矩阵的过程,我们可以发现,第二刀似乎白切了,只需要第一刀和第三刀。问题来了:能否把方法1中的第二步忽略掉?而第三步和第四步,能否也只保留一个?
先将这个算法写出来,再探讨它是否有效。
01:在最上面的一条边上,通过二分法查找k,找到就退出。否则,找到比k大的最小整数,将其所在及右边的列统统“切掉”。
02:在最右面的一条边上,通过二分法查找k,找到就退出。否则,找到比k小的最大整数,将其所在及上边的行统统“切掉”。
03:以上每一步中,“切”之前的矩阵大小如果为0,则失败退出;以上两步结束后,如果还没找到,则返回01,继续切。
需要证明有效性:
> 当k确实存在于矩阵中的时候,以上算法中的第一步和第二步都是可以执行的;
> 当算法结束的时候,如果没找到k,那么矩阵中肯定没有k
讨论如下:
* 令矩阵左上角的数为m,右下角的数为M。当k确实存在于矩阵中的时候,它必然满足m<=k<=M。
*如果我们以矩阵右上角为轴,将矩阵最右边一列逆时针旋转90度,与矩阵最上面一行拼起来,必然形成一个头为m尾为M的递增数列S。
* 如果m<=k<=M,那么要么能在S中找到k,要么能找到两个相邻的数p和q,满足p
*p和q要么同在最上面一行,要么同在最右边一列。因此,第一步和第二步,至少有一个能产生一个有效的“切割”,排除掉至少一行或者一列。
* 完成一个有效切割之后,k必然仍旧在余下的矩阵内,令其左上角的数为m'右下角的数为M'则必然满足m'=k<=M'
* 因此,“k确实存在于矩阵中”是“方法2能找到k”的充分条件。
经过以上的讨论,方法2可以改良一下:
01:令矩阵左上角的数为m,右下角的数为M。判断目标数k是否满足m<=k<=M。不满足,则失败退出。
02:以矩阵右上角为轴,将矩阵最右边一列逆时针旋转90度,与矩阵最上面一行拼起来,形成一个头为m尾为M的递增数列S。
03:在S中查找k。找到,则成功退出。否则,应该能找到两个相邻的数p和q,满足p
04:如果p和q同在最上面一行,则将q所在列及其右边的列“切掉”,返回01;
05:如果p和q同在最右边一列,则将p所在行及其上边的行“切掉”,返回01
该方法的时间复杂度,应该是O((h+w)*log(h+w)),其中h是矩阵高,w是矩阵宽。
应该还可以再优化。
以下为个人思路:
扫描矩阵的对角线,找到最小的子矩阵:
01,02,08,09
02,04,09,12
04,07,10,13
06,08,11,15
例如在上面的矩阵中寻找7.则4<7<10,则最下矩阵为:
4 9
7 10
在这个二维矩阵中查找7是否存在
注:这种方法更适合于方阵,如不是方阵可以将原来的数组划分成若干个方阵求解。题目中二维数组应该已经存在内存中。否则有投机取巧的“算法”。上次做一道类似ACM题目,我采取的策略是边输入数据,边比较输入的数据是否为要查找的数据.根本不需要保存数据.当然这有点投机取巧的嫌疑。
已知一个二维整数数组(或者说,一个整数矩阵),每行从左到右递增,每列从上往下递增。要求定义一个函数,输入矩阵和某个整数k,判断k是否在这个矩阵内。
【思路】
像四叉树那样递归查找,这是一种思路,不过我不是很喜欢。我希望一个循环就能搞定,不希望碰到类似于回溯之类的逻辑。
如果一个循环就搞定,那么最好是能像切西瓜一样,把不要的部分一点点切掉,同时剩下的部分仍旧是规整的一块。
观察以下矩阵(为方便行文,矩阵内全是正整数,大于0小于10的数在十位上补0)。
01,02,08,09
02,04,09,12
04,07,10,13
06,08,11,15
易得,该矩阵的最小值m在左上角,最大值M在右下角。易证得,这不是个案,是个普遍的规律;并且,从这样的一个矩阵中任意圈出一个子矩阵,也符合这个规律。一个整数k,如果小于m或者大于M,则必然不在这个矩阵内。
继续探索这种矩阵的性质,看是否有线索。
将该矩阵分成上下两部分:
01,02,08,09
02,04,09,12
------------
04,07,10,13
06,08,11,15
如果一个整数k,大于上半部分右下角的值(这里是12),则k必然不在上半部分。
如果一个整数k,小于上半部分左上角的值(这里是04),则k必然不在下半部分。
将该矩阵分成左右两部分:
01,02 | 08,09
02,04 | 09,12
04,07 | 10,13
06,08 | 11,15
如果一个整数k,大于左半部分右下角的值(这里是08),则k必然不在左半部分。
如果一个整数k,小于右半部分左上角的值(这里是08),则k必然不在右半部分。
有以上推论做基础,我们就可以设计“切蛋糕”式的算法了。
【方法1】
先说个思路,不太严格:
01:在最上面的一条边上,通过二分法查找k,找到就退出。否则,找到比k大的最小整数,将其所在及右边的列统统“切掉”。
02:在最下面的一条边上,通过二分法查找k,找到就退出。否则,找到比k小的最大整数,将其所在及左边的列统统“切掉”。
03:在最左面的一条边上,通过二分法查找k,找到就退出。否则,找到比k大的最小整数,将其所在及下边的行统统“切掉”。
04:在最右面的一条边上,通过二分法查找k,找到就退出。否则,找到比k小的最大整数,将其所在及上边的行统统“切掉”。
05:以上每一步中,“切”之前的矩阵大小如果为0,则失败退出;以上四步结束后,如果还没找到,则返回01,继续切。
举例如下。在以下矩阵中寻找07。
01,02,08,09
02,04,09,12
04,07,10,13
06,08,11,15
过程如下:
01:最上面一行,找不到07,而比07大的最小整数是08。切一刀,得到以下矩阵
01,02
02,04
04,07
06,08
02:最下面一行,找不到07,而比07小的最小整数是06。切一刀,得到以下矩阵
02
04
07
08
03:最左面的一列……好吧,找07应该很容易了。顺利退出。
以上所描述的算法,有两个问题没解决:第一,是否已经最优;第二,怎样从数学上证明,“算法找到目标数k”的充分条件就是“矩阵含有目标数k”。后者不难证,前者要考虑。
【方法2】
仍旧使用这个例子:在以下矩阵中寻找07。
01,02,08,09
02,04,09,12
04,07,10,13
06,08,11,15
考察方法1切割矩阵的过程,我们可以发现,第二刀似乎白切了,只需要第一刀和第三刀。问题来了:能否把方法1中的第二步忽略掉?而第三步和第四步,能否也只保留一个?
先将这个算法写出来,再探讨它是否有效。
01:在最上面的一条边上,通过二分法查找k,找到就退出。否则,找到比k大的最小整数,将其所在及右边的列统统“切掉”。
02:在最右面的一条边上,通过二分法查找k,找到就退出。否则,找到比k小的最大整数,将其所在及上边的行统统“切掉”。
03:以上每一步中,“切”之前的矩阵大小如果为0,则失败退出;以上两步结束后,如果还没找到,则返回01,继续切。
需要证明有效性:
> 当k确实存在于矩阵中的时候,以上算法中的第一步和第二步都是可以执行的;
> 当算法结束的时候,如果没找到k,那么矩阵中肯定没有k
讨论如下:
* 令矩阵左上角的数为m,右下角的数为M。当k确实存在于矩阵中的时候,它必然满足m<=k<=M。
*如果我们以矩阵右上角为轴,将矩阵最右边一列逆时针旋转90度,与矩阵最上面一行拼起来,必然形成一个头为m尾为M的递增数列S。
* 如果m<=k<=M,那么要么能在S中找到k,要么能找到两个相邻的数p和q,满足p
*p和q要么同在最上面一行,要么同在最右边一列。因此,第一步和第二步,至少有一个能产生一个有效的“切割”,排除掉至少一行或者一列。
* 完成一个有效切割之后,k必然仍旧在余下的矩阵内,令其左上角的数为m'右下角的数为M'则必然满足m'=k<=M'
* 因此,“k确实存在于矩阵中”是“方法2能找到k”的充分条件。
经过以上的讨论,方法2可以改良一下:
01:令矩阵左上角的数为m,右下角的数为M。判断目标数k是否满足m<=k<=M。不满足,则失败退出。
02:以矩阵右上角为轴,将矩阵最右边一列逆时针旋转90度,与矩阵最上面一行拼起来,形成一个头为m尾为M的递增数列S。
03:在S中查找k。找到,则成功退出。否则,应该能找到两个相邻的数p和q,满足p
04:如果p和q同在最上面一行,则将q所在列及其右边的列“切掉”,返回01;
05:如果p和q同在最右边一列,则将p所在行及其上边的行“切掉”,返回01
该方法的时间复杂度,应该是O((h+w)*log(h+w)),其中h是矩阵高,w是矩阵宽。
应该还可以再优化。
以下为个人思路:
扫描矩阵的对角线,找到最小的子矩阵:
01,02,08,09
02,04,09,12
04,07,10,13
06,08,11,15
例如在上面的矩阵中寻找7.则4<7<10,则最下矩阵为:
4 9
7 10
在这个二维矩阵中查找7是否存在
注:这种方法更适合于方阵,如不是方阵可以将原来的数组划分成若干个方阵求解。题目中二维数组应该已经存在内存中。否则有投机取巧的“算法”。上次做一道类似ACM题目,我采取的策略是边输入数据,边比较输入的数据是否为要查找的数据.根本不需要保存数据.当然这有点投机取巧的嫌疑。
相关文章推荐
- &lt;转载&gt;激发程序员创意的几本书
- 黑马程序员Java初级<四>---> 数组
- 数组问题之一维最大字段和问题<Java实现>
- <转载> Timer和ScheduledThreadPoolExecutor的区别
- <笔试><面试>编写一个排序函数,实现,既可以排序整形数组,又可以排序字符串。
- Java笔记1 Java编程基础<3>数组运用
- Windows中.exe程序的启动过程和C/C++运行时库<转载>
- List<Map>类型的JavaScript数组应用范例:图片多样化显示
- C# IEnumerable<T>、IEnumerator<T>、List<T>、ArrayList、[]数组各各的区别
- 艾伟_转载:数组排序方法的性能比较(中):Array.Sort<T> 实现分析
- 第四周(项目四扩展三)——数组做数据成员<数据存入文件>
- 转载一篇过来人谈的大学期间关于课外学习的意见&lt;出自左飞老师笔记&gt;
- Android数据存储五种方式总结<转载>
- <<Accelerated C# 2008>>笔记3容器&&数组&&迭代器
- <转载学习>子类对父类构造方法调用小结
- 黑马程序员 Java基础<一> 数组及排序
- <转>KMP算法中关于next数组的探究
- <C>char/char*/char**数组
- C#List<T>的用法详解(转载笔记)