题目描述

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

思考

  1. 二分查找
a1 a2 a3 a4 a5 a6
b1
c1
d1
e1
f1

总结

题目的理解错了,错误的认为,矩阵满足这样的规律:

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16

其实这只是一个特例,题目只说举证中的元素满足“左小右大,上小下大”的特点,更一般的矩阵是这样的:

1 2 8 9
2 4 9 12
4 7 10 13
7 8 15 15

正确理解的题目的意思以后,我编写出了满足题目要求的代码。具体的思路是:遍历每一行,对每一行进行二分查找。

package com.learn.java;

import java.util.Arrays;

/**
 * @author xzy
 * @date 2020-03-25 18:56
 * 说明:
 */
public class Main {
    public static void main(String[] args) {
        Main main = new Main();
        int[][] array = new int[][]{
                {1, 2, 8, 9},
                {2, 4, 9, 12},
                {4, 7, 10, 13},
                {6, 8, 11, 15}
        };
        for (int i = 0; i < 40; i++) {
            System.out.println("target:" + i + " find:" + main.find(i, array));
        }
    }

    /**
     * @param target - 查找目标
     * @param array  - 查找数组
     * @return 找到:元素下标  找不到:-1
     */
    public int binarySearch(int target, int[] array) {
        // head:查找区间头 middle:查找区间中 end:查找区间尾
        int head = 0;
        int end = array.length - 1;
        int middle = end / 2;

        //1. 不可能找得到
        if (target < array[head] || target > array[end]) {
            return -2;
        }

        //2. 二分查找
        while (head <= end) {
            if (target == array[middle]) {
                //1. 找到
                return middle;
            } else if (target < array[middle]) {
                //2.向左侧继续找
                end = middle - 1;
                middle = (end - head) / 2 + head;
            } else {
                //3.向右侧继续找
                head = middle + 1;
                middle = (end - head) / 2 + head;
            }
        }
        return -1;
    }

    public boolean find(int target, int[][] array) {
        for (int row = 0; row < array[0].length; row++) {
            if (binarySearch(target, array[row]) >= 0) {
                return true;
            }
        }
        return false;
    }
}

优化

仔细的琢磨“左小右大,上小下大”

1 2 3 9
2 4 9 12
4 7 10 13
7 8 15 15
  1. 行角度
第1行最右边的元素,是第一行最大的。

第2行最右边的元素,是前两行最大的。

第3行最右边的元素,是前三行最大的。

第4行最右边的元素,是前四行最大的。
  1. 列角度
第1列最后一个元素,是第一列最大的。

第2列最后一个元素,是前两列最大的。

第3列最后一个元素,是前三列最大的。

第4列最后一个元素,是前四列最大的。

新的算法思想:从行列角度出发,从第一行开始,将target与每一行最后一个元素比较,排除target不可能存在的行,同时排除target不可能存在的列。

以target == 11为例,将target与第一行最后一个元素9比较,发现target > 9,而9又是第1行最大的元素,显然,target不可能存在于第一行。继续将target与第二行最后一个元素12比较,发现target<12,而12又是第4列后3行中最小的元素,显然,target不可能存在于第4列。

上述算法不断缩小元素可能存在的范围,范围的缩小过程是这样的:

  1. target > 9
1 2 3 9
2 4 9 12
4 7 10 13
7 8 15 15
  1. target < 12
2 4 9 12
4 7 10 13
7 8 15 15
  1. target > 9
2 4 9
4 7 10
7 8 15
  1. target > 10
4 7 10
7 8 15
  1. target < 15

    7 8 15
  2. target > 8

    7 8
  3. 已经没有范围缩小到0,可以确认,矩阵中不存在target。

利用新的思想,新的实现代码:

public boolean find2(int target, int[][] array) {

    //rowMax:行坐标最大值 columnMax:列坐标最大值
    int rowMax = array.length - 1;
    int columnMax = array[0].length - 1;

    //空矩阵判断
    if (rowMax < 0 || columnMax < 0) {
        return false;
    }

    //target是否有可能存在于矩阵
    if (target < array[0][0] || target > array[rowMax][columnMax]) {
        return false;
    }

    //(rowPoint,columnPoint)当前检查元素的坐标
    int rowPoint = 0;
    int columnPoint = columnMax;
    while (rowPoint <= rowMax && columnPoint >= 0) {
        if (target == array[rowPoint][columnPoint]) {
            return true;
        } else if (target > array[rowPoint][columnPoint]) {
            //不可能在这一行
            rowPoint++;
        } else {
            //不可能在这一列
            columnPoint--;
        }
    }
    return false;
}