九度_题目1384:二维数组中的查找

题目1384:二维数组中的查找

题目描述

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

输入

输入可能包含多个测试样例,对于每个测试案例,

输入的第一行为两个整数m和n(1<=m,n<=1000):代表将要输入的矩阵的行数和列数。

输入的第二行包括一个整数t(1<=t<=1000000):代表要查找的数字。

接下来的m行,每行有n个数,代表题目所给出的m行n列的矩阵(矩阵如题目描述所示,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。

输出

对应每个测试案例,

输出”Yes”代表在二维数组中找到了数字t。

输出”No”代表在二维数组中没有找到数字t。


样例输入:
3 3
5
1 2 3
4 5 6
7 8 9
3 3
1
2 3 4
5 6 7
8 9 10
3 3
12
2 3 4
5 6 7
8 9 10
样例输出:
Yes
No
No
!!!话说这道题真心做的是历史甚远。 刚开始做这道题的时候,java根本就没有人出现过AC。 然后就一直等大神的答案。 结果久久未到。 最近因为各种原因又开始准备做些题,来练练手。 便翻出了这道题。 大神wzqwsrf的一句话,一语惊醒梦中人。 我马上将Scanner改为了StreamTokenizer,此后便一马平川。改写后的代码,第一次便AC了。
这道题简单概述,就是怎样以最快的速度,查找二维数组中是否含有某个数。 在此,拙见,提供给大家三种解题思路,供大家参考,也欢迎大家指正。 当然,如果有更好的算法,也希望不吝赐教。 大家共同进步!
先描述三种算法的思路。然后对其中两种,附上代码。呵呵呵,因为另一种,我没有具体的写。 最后对于新学到的知识StreamTokenizer,给出链接供大家参考。

在正式开始前,先说下这道题的一个漏洞。 这道题,虽然是查找二维数组中是否含有某个数,但是却是要求要自己输入数组的。 导致,有人发现,可以在边输入的过程中,边判断是否等于查询的数。 这种方法被多人应用。 在此不加以评判。
图中数据,是为了说明问题随意而写,不具有代表性。 只为了说明问题。 举例中,均以查找数据80为目的。 按题意我的m=7, n=9, t=80. 结果输出应该为No.
方法一,称之为: 多次二分查找 直接上图。
红色表示所查数据可能出现的地方。
步骤描述: 1.按题意,矩阵数组中,每行数据均从小到大,每列数据也是从小到大。所以对于某些行,如果判定右侧的值小于目标值t,则整行值均比t小。但是右侧的值大于t,并不能说明这行不会含有数据t。   所以这一步的目的是缩减所要查询的行数,我们通过比对每行最右侧的值,找到一个最大的小于t的值,计此行的行标为row,则有行标小于等于row的所有行的值全部小于t.(第一次二分查找) 2.对于符合要求的行,进行逐行查找(多次二分查找),实现题目的要求。
没有对此方法实现,有兴趣的同学可以自己实现。
这只是个人的初步想法。 朋友们可以去实践。
此方法的优缺点在于: 优点:(1)比较容易理解,(2)对于查找矩阵上层的数据,查询时间小。(3)参数传递简单。 缺点: (1)如果查找的值位于左下侧,则查询时间较长。(2)查询时间不稳定。
方法二, 渐进查找
思想来自《剑指offer》,方法思想差不多同方法一,但是没有用到二分查找。 只是逐次改变行和列的下标,每次都缩减要查找的数据范围,使其更加逼近所要查找的数。 仍从右上角开始,如果此数较所查找数小,则行+1,如果比所查找数大,则列-1.
思想是一步步缩小查找范围,但是没有涉及到二分查找。
不画图了,见谅。 上代码。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;

public class Main {

	private	boolean flag = false;
    private int[][] matric;
	
	private void inputOutput() throws IOException{
    	StreamTokenizer st = new StreamTokenizer(new BufferedReader(
                new InputStreamReader(System.in)));
        while(st.nextToken() != StreamTokenizer.TT_EOF){
        	flag = false;
            int row = (int)st.nval;
            st.nextToken();
            int column = (int)st.nval;
            st.nextToken();
            int cand = (int)st.nval;
            if(!(0<row && row<=1000 && 0<column && column<=1000 && 0<cand && cand<=1000000)){
                continue;
            }
            matric = new int[row][column];
            for(int i=0; i<row; i++)
                for(int j=0; j<column; j++){
                	st.nextToken();
                    matric[i][j] = (int)st.nval;
                }
            findNumber(row, column, cand);
            if(flag == false)
                System.out.println("No");
            else
                System.out.println("Yes");
        }
    }
	
	private void findNumber(int row, int column, int cand) {
		if(row<1 || column<1)
			return;
		int i = 0;
		int j = column-1;
		while(i<row && j>=0){
			if(matric[i][j] == cand){
				flag = true;
				return;
			}
			else if(matric[i][j] >cand){
				j--;
			}
			else
				i++;
		}
		return;
	}

	public static void main(String[] args) throws IOException {
		Main main = new Main();
		main.inputOutput();
	}
}



方法三,称之为 分块查找
算法的主题思想是用一个矩阵的对角线去确定所查数据的可能范围。 1.对于一个不是方正的矩阵,先找到它的最大方正,其余部分则使用迭代用相同的方法处理。   图中,则用细蓝线划分,左侧为方针,右侧为剩余部分。剩余部分中包含多个方正。 2.对于方正而言,先判断t值是不是小于方正的最小值(最左上角的值,图中为1),或者大于方正的最大值(最右下角的值,图中为95)。如果满足其一,则此方正中不可能存在我们所要找的值。   这样做的目的是为了减少一些多余的运算。 3.对于可能存在要查找值的方正,则沿对角线查找,如果找到则返回,无的话则找到界值。(绿线)   例子中,我们要找80,则有33<80<81。 然后根据此两界值,则重新去迭代判断红色框中的数据(可能存在80的区域)。 4. 以上过程多次迭代。则最终会得到结果。
此方法的优缺点在于: 优点:对于大数据量,不论所要查找的数据大小,都可以快速定位至较小的范围。 缺点:(1)多次迭代,(2)参数传递麻烦
以下为第二种方法的代码,已通过。AC
import java.io.*;

public class Main {
	
	private	boolean flag = false;
    private int[][] matric;
    
    void inputOutput() throws IOException{
    	StreamTokenizer st = new StreamTokenizer(new BufferedReader(
                new InputStreamReader(System.in)));
        while(st.nextToken() != StreamTokenizer.TT_EOF){
        	flag = false;
            int row = (int)st.nval;
            st.nextToken();
            int column = (int)st.nval;
            st.nextToken();
            int cand = (int)st.nval;
            if(!(0<row && row<=1000 && 0<column && column<=1000 && 0<cand && cand<=1000000)){
                continue;
            }
            matric = new int[row][column];
            for(int i=0; i<row; i++)
                for(int j=0; j<column; j++){
                	st.nextToken();
                    matric[i][j] = (int)st.nval;
                }
            findNumber(0, row-1, 0, column-1 ,cand);
            if(flag == false)
                System.out.println("No");
            else
                System.out.println("Yes");
        }
    }
 
    private void findNumber(int srow, int erow, int scol, int ecol, int cand) {
    	if(matric[srow][scol]>cand || matric[erow][ecol]<cand)
    		return;
    	int mrow = srow;
    	int ncol = scol;
    	boolean flagb = (erow-srow > ecol-scol) ? true : false;
    	int temp = (flagb==true)?(ecol-scol):(erow-srow);
    	int mhigh = srow + temp;
    	int nhigh = scol + temp;
//    	Do the excess part
    	if(flagb==true){
    		if(mhigh+1<=erow){
    			findNumber(mhigh+1,erow,scol,ecol,cand);
    		}
    	}
    	else{
    		if(nhigh+1<=ecol){
    			findNumber(srow,erow,nhigh+1,ecol,cand);
    		}
    	}
//    	Do the rectangle part
    	for(; mrow<=mhigh && ncol<=nhigh; mrow++, ncol++){
    		if(matric[mrow][ncol]==cand){
    			flag = true;
    			return;
    		}
    		if(mrow<mhigh && ncol<nhigh){
    			if(matric[mrow][ncol]<cand && matric[mrow+1][ncol+1]>cand)
    				break;
    		}
    	}
    	if(mrow<mhigh && ncol<nhigh){
    		findNumber(srow, mrow, ncol+1, nhigh, cand);
    		findNumber(mrow+1, mhigh, scol, ncol, cand);
    	}
		return;
	}

	public static void main(String[] args) throws IOException {
        Main con = new Main();
        con.inputOutput();
    }
}

部分参数说明:srow, erow, mhigh分别为行起始点,行结束点,行当前分割位置;cand为t。
这题代码不知道写了多少个版本,我存了的至少有四个。 遇到的问题有: (1)参数传递。刚开始写的时候总是把小的方正重新复制,然后把方正作为参数传递。后面觉得,直接复制传递方正会用掉很多的时间。所以便全部改写成了坐标传递。具体的时间影响未经考证。如果有谁考证过,悉心听教。 (2)写了很多个版本的输入都用的是,Scanner,但是都没有通过。前文已说,在wzqwsrf的提醒下改用了StreamTokenizer。便得到了AC。最终得证,StreamTokenizer的速度比Scanner要快很多。估计一倍至少是有的。但是好像apache在新版本的java中却不推荐使用Tokenizer。是这样吗???
本来准备贴个原先版本的代码,但是因为事隔太久,竟有点搞不通参数传递的问题。便作罢。 不过原先的代码,写的很乱,也没有什么可借鉴之处。
对于方法二和三的时间效率,实际运行结果二为1820ms,三为1840ms.空间也均在31600kb左右。 结果原因未曾考究。 但方法三种的多次迭代可能会造成时间上的消耗。
学习StreamTokenizer的两个链接: http://sulifeng.iteye.com/blog/577000
http://www.wangchao.net.cn/bbsdetail_14840.html
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值