03. 二维数组中的查找 -----------《剑指Offer》--(Java版)

《剑指Offer》(Java语言版答案)github链接:

点这里

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

1 2  3    4     5

3 5  7    8     9

6 8 11  22  33

 

1. 最暴力最无脑的是遍历,在这里我就不写了,如果写不出来遍历,最好还是重新学学Java语法。

2. 稍微优化一点点就是遍历每一行,然后在每一行中进行二分搜索.

二分搜索的核心代码:

                int head = 0, last = array[x].length - 1;
                while (head <= last) {
                    int mid = (head + last) / 2;
                    if (array[x][mid] == target) {
                        return true;
                    } else if (array[x][mid] > target) {
                        last = mid - 1;
                    } else {
                        head = mid + 1;
                    }
                }
/**
     * 常规解法:
     * 使用遍历列*二分查找列中的行.
     */
    public boolean FindByBinSearch(int target, int[][] array) {
        // 判断行于列是否都正常
        if (array.length == 0 || array[0].length == 0) {
            return false;
        }
        // 遍历每一行
        for (int i = 0, x = 0; i < array.length; i++) {
            // 如果目标值落入这一行的范围内,则进行二分查找
            if (array[i][0] <= target && array[i][array[i].length - 1] >= target) {
                // 对这一行进行二分查找.
                x = i;
                int head = 0, last = array[x].length - 1;
                while (head <= last) {
                    int mid = (head + last) / 2;
                    if (array[x][mid] == target) {
                        return true;
                    } else if (array[x][mid] > target) {
                        last = mid - 1;
                    } else {
                        head = mid + 1;
                    }
                }
            } else if (target < array[i][0]) {
                // 比这一行最小值都小,那么接下来的行中也不存在目标值.
                return false;
            }
        }

        return false;
    }

当然Java也有自带的二分搜索方法.

Arrays.binarySearch(int[],int);//当然也支持部分数组搜索

Arrays.binarySearch(long[],long);

Arrays.binarySearch(short[],short);

Arrays.binarySearch(char[],char);

..等

通过部分二分,我们就可以把时间复杂度从O(n*m)变为O(n*log m) 或 O(m * log n),以及部分剪枝(小于最小值,则break,大于最大值则搜索下一列)

当然这不是最优,因为题目中的条件只使用了一部分.

3. 建立模型

行有序递增,列有序递增。
那么对于左上角,右上角,左下角,右下角,存在这样的属性:
左上:全局最小
右上:所属行最大,所属列最小
左下:所属列最大,所属行最小
右下:全局最大.

所以我们应该选择右上或者左下:这样能有效分辨出行与列的区别。

我们试着从右上来找:

 

先从一行的最大值开始找,(因为行的最大值同时也是列的最小值)

如果行的最大值比目标值小,那么进入前一列:(该列的最小值比目标值还小,所以该列被抛弃)。

如果行的最大值比目标值大,那么进入下一行:(该行的最大值都比目标值小,所以该行被抛弃).


    /**
     * 遍历*二分,题目中给出的两个有序,只使用了一个(另一个几乎没有使用)
     * 如何使用两个有序呢?  博客中有具体的图示 URL: https://mp.csdn.net/postedit/88371064
     * 行有序递增,列有序递增。
     * 那么对于左上角,右上角,左下角,右下角,存在这样的属性:
     * 左上:全局最小
     * 右上:所属行最大,所属列最小
     * 左下:所属列最大,所属行最小
     * 右下:全局最大.
     * 接下来以右上为例讲解:
     * <p>
     * 如果目标值大于右上角,那么这一行的值都不符合,扔掉当前行。
     * 如果目标值小于右上角,那么这一列的值都不符合,扔掉当前列。
     * 对矩阵递归进行上述步骤的切割。
     */
    public boolean Find(int target, int[][] array) {
        if (array.length == 0 || array[0].length == 0) {
            return false;
        }
        // 先从矩阵的右上角开始找,这样至少可以剪掉一行或一列.
        int x = 0, y = array[0].length - 1;
        return Find(array, target, x, y);
    }

    /**
     * @param array  查询数组.
     * @param target 目标值.
     * @param x      行的上限。
     * @param y      列的上限。
     * @return  是否查找到目标值
     */
    private boolean Find(int[][] array, int target, int x, int y) {
        if (x == array.length || y == -1) {
            return false;
        }
        if (array[x][y] == target) {
            return true;
        } else if (array[x][y] > target) {
            // 说明矩阵的最后一列无用
            return Find(array, target, x, y - 1);
        } else {
            // 说明矩阵剩余的第一行无用
            return Find(array, target, x + 1, y);
        }
    }

 

最终代码如下:


/**
 * 在一个二维数组中(每个一维数组的长度相同),
 * 每一行都按照从左到右递增的顺序排序,
 * 每一列都按照从上到下递增的顺序排序。
 * 请完成一个函数,
 * 输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
 * <p>
 * {1, 2,  3,    4,     5,
 * <p>
 * 3, 5,  7,    8,     9,
 * <p>
 * 6, 8, 11,   22,     33}
 */
public class _003_Find {
    /**
     * 常规解法:
     * 使用遍历列*二分查找列中的行.
     * 时间复杂度:O(n*log(n)),空间O(n^2)
     */
    public boolean FindByBinSearch(int target, int[][] array) {
        // 判断行于列是否都正常
        if (array.length == 0 || array[0].length == 0) {
            return false;
        }
        // 遍历每一行
        for (int i = 0, x = 0; i < array.length; i++) {
            // 如果目标值落入这一行的范围内,则进行二分查找
            if (array[i][0] <= target && array[i][array[i].length - 1] >= target) {
                // 对这一行进行二分查找.
                x = i;
                int head = 0, last = array[x].length - 1;
                while (head <= last) {
                    int mid = (head + last) / 2;
                    if (array[x][mid] == target) {
                        return true;
                    } else if (array[x][mid] > target) {
                        last = mid - 1;
                    } else {
                        head = mid + 1;
                    }
                }
            } else if (target < array[i][0]) {
                // 比这一行最小值都小,那么接下来的行中也不存在目标值.
                return false;
            }
        }

        return false;
    }

    /**
     * 遍历*二分,题目中给出的两个有序,只使用了一个(另一个几乎没有使用)
     * 如何使用两个有序呢?  博客中有具体的图示 URL: https://mp.csdn.net/postedit/88371064
     * 行有序递增,列有序递增。
     * 那么对于左上角,右上角,左下角,右下角,存在这样的属性:
     * 左上:全局最小
     * 右上:所属行最大,所属列最小
     * 左下:所属列最大,所属行最小
     * 右下:全局最大.
     * 接下来以右上为例讲解:
     * <p>
     * 如果目标值大于右上角,那么这一行的值都不符合,扔掉当前行。
     * 如果目标值小于右上角,那么这一列的值都不符合,扔掉当前列。
     * 对矩阵递归进行上述步骤的切割。
     */
    public boolean Find(int target, int[][] array) {
        if (array.length == 0 || array[0].length == 0) {
            return false;
        }
        // 先从矩阵的右上角开始找,这样至少可以剪掉一行或一列.
        int x = 0, y = array[0].length - 1;
        return Find(array, target, x, y);
    }

    /**
     * @param array  查询数组.
     * @param target 目标值.
     * @param x      行的上限。
     * @param y      列的上限。
     * @return  是否查找到目标值
     */
    private boolean Find(int[][] array, int target, int x, int y) {
        if (x == array.length || y == -1) {
            return false;
        }
        if (array[x][y] == target) {
            return true;
        } else if (array[x][y] > target) {
            // 说明矩阵的最后一列无用
            return Find(array, target, x, y - 1);
        } else {
            // 说明矩阵剩余的第一行无用
            return Find(array, target, x + 1, y);
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值