剑指offer-面试题4:二维数组中的查找 解法

题目描述

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

例如下面的二维数组,每行和每列都是递增排序。如果在这个数组中查找数字7,则返回true;如果查找数字5,则返回false
[[1, 2, 8, 9],
[2, 4, 9, 12],
[4, 7, 10, 13],
[6, 8, 11, 15]]

解法一(暴力法):

暴力法不用多做解释了吧,遍历扫描整个二维数组,遍历到要查找的数字则返回true,否则在程序末尾返回false

//暴力解法(遍历所有元素)
public boolean Find1(int target, int [][] array)
{
    if(array==null)
    {
        return false;
    }
    for(int i=0;i<array.length;i++)
    {
        for(int j=0;j<array[i].length;j++)
        {
            if(array[i][j]==target)
            {
                return true;
            }
        }
    }
    return false;
}

时间复杂度:O(n2),空间复杂度:没有用到辅助空间,O(1)

显然这种遍历整个数组的方法不够“高级”,也没有利用到该数组的特性——每行从左到右递增,每列从上到下递增。那么有没有方法可以利用这一特性,使我们不必遍历整个数组呢?

解法二(二分查找+缩小查找范围):

首先,假设给定二维数组array[n1][n2]。我们将查找范围定为整个数组,即以array[0][n]为右上角,以array[n1][0]为左下角的矩形范围。需要注意的是,查找范围的的右上角固定不变,左下角为array[row][inel],即初始状态row=n1,line=0

  1. 列搜索:在第line列的第0~row数字中进行二分查找。若找到目标数字则返回true,否则:
    (1)若目标数字小于第line最小的数字,则可断定数组中没有目标数字,返回false.否则:
    (2)更改row为本列小于目标数字的最大数字所在的行,进入第2步
  2. 行搜索:在第row行的第line~n2数字中二分查找。若找到目标数字则返回true,否则:
    (1)若目标数字大于第row行最大的数字,则可断定数组中没有目标数字,返回false,否则:
    (2)更改line为本列大于目标数字的最小数字所在的列。到这里row都得到了更新,完成了查找范围的缩小。返回第1步循环
public boolean Find2(int target, int [][] array)
{
    if(array==null || array.length==0 || array[0].length==0) //验证空指针或空数组
    {
        return false;
    }
    if(array[0][0]>target || array[array.length-1][array[0].length-1]<target) //target大于最大值或小于最小值,返回false
    {
        return false;
    }
    int line=0;
    int row=array.length-1;
    while(true)
    {
        //在列上搜索,二分查找
        int head=0,tail=row;
        while(head<=tail)
        {
            int mid=(head+tail)/2;
            if(array[mid][line]==target)
            {
                return true;
            }
            else if(array[mid][line]>target)
            {
                tail=mid-1;
            }
            else
            {
                head=mid+1;
            }
        }
        if(tail<0) //目标数字小于本列最小的数字,可断定二维数组中没有目标数字
        {
            break;
        }
        //在行上查找(二分查找)
        row=tail;
        head=line;
        tail=array[0].length-1;
        while(head<=tail)
        {
            int mid=(head+tail)/2;
            if(array[row][mid]==target)
            {
                return true;
            }
            else if(array[row][mid]>target)
            {
                tail=mid-1;
            }
            else
            {
                head=mid+1;
            }
        }
        if(head>(array[0].length-1)) //目标数字大于本行最大的数字,可断定二维数组中没有目标数字
        {
            break;
        }
        line=head;
    }
    return false;
}

时间复杂度:一次行或列上的二分查找时间复杂度为 log(n),每次列查找会伴随一次行查找,行列查找的循环会进行n次,因此为O(nlogn)
空间复杂度:没有用到辅助空间,O(1)

解法二虽然利用了给定二维数组的特性,缩小了查找范围,降低了时间复杂度,但实现和理解起来较为繁琐,尤其是边界条件较为难理解,作者在写代码时感觉很明显。是否还有更简单的方法?

解法三:

解法三是作者参考“剑指offer”提供的官方解法,当看到这个解法时,顿时感觉醍醐灌顶,茅塞顿开

原来可以这样,我怎么想不到……

以上面给出的二维数组为例,查找数字7:每次选取查找范围最左下角的数字,从左下角的数字6开始,6小于7,我们可以确定6所在的列的所有元素均小于7,将这一列剔除出查找范围,比较6右边的8;8大于7,可以确定8所在的行的所有元素均大于7,剔除8所在的行,比较8上面的7;查找到了7

扫描二维码关注公众号,回复: 9510967 查看本文章
public boolean Find(int target, int [][] array)
{
    if(array==null || array.length==0 || array[0].length==0) //验证空指针或空数组
    {
        return false;
    }
    if(array[0][0]>target || array[array.length-1][array[0].length-1]<target) //target大于最大值或小于最小值,返回false
    {
        return false;
    }
    int row=array.length-1,line=0; //从左下角元素开始比较
    while(row>=0 && line<=array[0].length-1)
    {
        int aim=array[row][line]; //当前关注的元素的值
        if(aim==target)
        {
            return true;
        }
        else if(aim<target)
        {
            //当前关注的元素所在的列的值都小于target,line右移
            line++;
        }
        else
        {
            //当前关注的元素所在的行的值都大于target,row上移
            row--;
        }
    }
    return false;
}    

时间复杂度:最坏情况下,需要在行和列上交替遍历,共查找n1+n2次,因此为O(n)
空间复杂度:没有用到辅助空间,O(1)

发布了9 篇原创文章 · 获赞 0 · 访问量 215

猜你喜欢

转载自blog.csdn.net/qq_24210431/article/details/104377048