剑指offer(一)二维数组中的查找

  • 题目展示
    在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上往下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该函数。

  • 案例:
    一个二维数组,每行、每列都是递增排序。
    例如下面这个数组,如果查找数字7,则返回true,如果查找数组5,则返回false。
    1 2 8 9
    2 4 9 12
    4 7 10 13
    6 8 11 15

  • 暴力求解

    • 这个题要是按普通的二维遍历来查找的话,也能完成功能,但是就体现不出二维数组原先有序的优势了。
    • 而且如果数组非常大的话,会特别浪费时间,导致超时。
    • 这种方法和思路要第一时间抛弃,算法类的题目如果暴力求解,那根本体现不出优势
  • 思考题目
    • 题目给的有序的条件是必须要用的,但是注意到这个有序也不是单纯的有序
    • 行与行之间:同一列来说,上面的元素比下面的元素小
    • 列与列之间:同一行来说,前面的元素比后一列的元素小
    • 但是并不代表第二行的数一定大于第一行,比如第一行可以是1,2,3,4,而第二行可以是2,3,4,5。
    • 但是可以想到一点,就是无论去掉任何一行或者一列,左上角的元素一定是最小的,右下角的元素一定是最大的
  • 分析题目
    • 从数组中选取数字,和目标数字的关系有三种情况:=,<或>。
    • 如果是等于则查找成功;
    • 如果是数组中元素小于要查找的数字,说明要查找的数字应该在当前位置的右边或下边。
    • 如果是数组中元素大于要查找的数字,说明要查找的数字应该在当前位置的左边或上边。
    • 但是这两个区域还有可能有重叠,比如右边或下边会在右下角有重叠。
  • 解决思路
    • 先判断数组是否为空和数组内是否有值,而且元素是否在数组内(即元素大于数组左上角且小于数组右下角)
    • 如果查找从右上角开始,即使要查找的数字不在右上角,则每次可以剔除一列或一行。(大于右上角的元素则可以排除其所在的那一行,小于则可以排除那一列)
    • 也可以从左下角开始,思路同上,但是不能从左上角或者右下角开始。
  • 解题代码
public static boolean Find(int target, int [][] array) {

        int rows = array.length;//总行数
        int columns = array[0].length;//总列数

        if( array != null && rows > 0 && columns > 0
                && target >= array[0][0] && target <= array[rows -1][columns - 1])
        {
            int row = 0;//循环到的行数
            int column = columns - 1;//循环到的列数

            while( row < rows && column >= 0 ) {

                //相等则返回true
                if( array[row][column] == target )
                    return true;

                //判断该数是否大于右上角的数
                if( target > array[row][column] )
                {
                    ++row;//大于则去掉最上一行的比较
                } else 
                {
                    --column;//小于则去掉最后一列
                }
            }
        }
        return false;
}

果然,运行通过,280ms到780ms情况都存在,所以这种方法存在不确定性。

  • 这种方法仔细想的话,一般都能想到,能不能再优化一下呢,答案是可以
  • 算法优化
    • 如果在每一次判断后面再加一层判断,虽然会多一次比较,但是一旦成立,剩余比较时间将会缩小一半,适用于大型数组的比较,小型数组可能不如上面速度快,但是胜在高效和稳定。
  • 优化代码展示
public static boolean Find(int target, int [][] array) {

        int rows = array.length;//总行数
        int columns = array[0].length;//总列数

        if( array != null && rows > 0 && columns > 0
                && target >= array[0][0] && target <= array[rows -1][columns - 1])
        {
            int row = 0;//循环到的行数
            int column = columns - 1;//循环到的列数
            int rowSec;//中间行
            int colSec;//中间列

            while( row < rows && column >= 0 ) {

                //相等则返回true
                if( array[row][column] == target )
                    return true;

                //判断该数是否大于右上角的数
                if( target > array[row][column] )
                {
                    rowSec = (int) Math.ceil((row + rows)/2);//取中间一行的行数(首尾行的平均数向上取整)
                    if( target > array[rowSec][column] )//判断该值是否大于中间行数最后一列的值
                        row = rowSec + 1;//大于则可以将以上的行全部舍弃,小于则不作处理按照原思路求解
                    else
                        ++row;//大于则去掉最上一行的比较

                } else 
                {
                    colSec = (int) Math.floor( (column / 2) );//取中间的列数(首尾列的平均数向下取整,首列为0)
                    if( target < array[row][colSec] )//判断该值是否小于中间列数的第一行
                        column = colSec - 1;//小于则可以将该列后面的列全部舍弃,大于则不作处理按照原来的思路求解
                    else
                        --column;//小于则去掉最后一列
                }
            }
        }
        return false;
    }
  • 总结
    • 上面代码运行时间平均350ms左右,偏差不会太大
    • 和第一次代码比较,就在每次判断之后新增了一次判断,判断的是中间行(列)的比较,一旦成立,则会省去一半的无用比较
    • 这是我能想出来的优化方式,如果大家有更好的办法,欢迎评论交流,共同进步。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值