Leetcode803. 打砖块--两种解法

Leetcode803. 打砖块—两种解法

方法一、深度优搜索(超时)

class Solution {
    public int[] hitBricks(int[][] grid, int[][] hits) {
        //可以采用深度优先解决此问题
    	int len=hits.length;
    	int[] ret=new int[len];
    	int m=grid.length;
    	int n=grid[0].length;
    	for(int i=0;i<len;i++) {
    		int x=hits[i][0];
    		int y=hits[i][1];
    		if(grid[x][y]==0) ret[i]=0;
    		else {
    			//开始对其四周进行搜索
                grid[x][y]=0;
    			for(int j=0;j<4;j++) {
    				int[][] visited=new int[m][n];
    				index=0;
    				boolean flag=dfs(grid,visited,x+row[j],y+col[j],m,n,false);
                    //System.out.println(flag);
    				if(!flag) {
                        visited=new int[m][n];
    					ret[i]+=index;
    					dfs(grid,visited,x+row[j],y+col[j],m,n,true);
    				}
    			}
    		}
    	}
    	return ret;
    }
    boolean dfs(int[][] grid,int[][] visited,int x,int y,int m,int n,boolean isClear) {
    	if(x<0 || x>=m || y<0 || y>=n || visited[x][y]==1 || grid[x][y]==0){
            return false;
        }
    	if(x==0) return true;
    	visited[x][y]=1;
    	if(isClear) grid[x][y]=0;
    	index++;
    	boolean flag=false;
    	for(int i=0;i<4;i++) {
    		int X=x+row[i];
    		int Y=y+col[i];
    		//已经深度优先到网格顶点
    		if(dfs(grid,visited,X,Y,m,n,isClear)) {
    			flag=true;
    			break;
    		}
    	}
    	return flag;
    }
    int index;
    int[] row=new int[] {1,0,-1,0};
    int[] col=new int[] {0,1,0,-1};
}

方法二、并查集+逆向思维

说明:简而言之就是在判断从后向前去补图 然后计算补充后的图中以房顶为根节点的一个连通图中结点增长的个数减去补充的那个点即为敲下这个结点后减少的结点。
class Solution {
    private int rows;
    private int cols;
    private int[]X=new int[] {1,0,-1,0};
    private int[]Y=new int[] {0,1,0,-1};
    
	public int[] hitBricks(int[][] grid, int[][] hits) {
    	//使用并查集+逆向思维解决此问题
    	this.rows=grid.length;
    	this.cols=grid[0].length;
    	//拷贝原数组
    	int[][] copy=new int[rows][cols];
    	for(int i=0;i<rows;i++) {
    		for(int j=0;j<cols;j++) {
    			copy[i][j]=grid[i][j];
            }
    	}
    	//先将砖块敲碎
    	int n=hits.length;
    	for(int i=0;i<n;i++) {
    		copy[hits[i][0]][hits[i][1]]=0;
    	}
    	//开始使用并查集 我们模拟size为房顶根节点
    	int size=this.rows*this.cols;
    	unionFind unionfind=new unionFind(size+1);
    	//首先将网格顶部的砖结点与根节点相连接
    	for(int i=0;i<cols;i++) {
    		if(copy[0][i]==1)unionfind.union(i, size);
    	}
    	//开始进行合并,因为我们在遍历时为顺序遍历 所以合并时可以只看其左边和上边
    	for(int i=1;i<rows;i++) {
    		for(int j=0;j<cols;j++) {
    			if(copy[i][j]==1) {
    				//上边
    				if(copy[i-1][j]==1) unionfind.union(getIndex(i, j), getIndex(i-1, j));
    				//左边
    				if(j-1>=0 && copy[i][j-1]==1) {
    					unionfind.union(getIndex(i, j), getIndex(i, j-1));
    				}
    			}
    		}
    	}
    	/* 题外介绍:
    	 * 至此,整个图已经合并完,我们亦可以知道此时图中存在多少个连通图
    	 */
    	//开始逆向补砖
    	int[] ret=new int[n];
    	for(int i=n-1;i>=0;i--) {
    		int x=hits[i][0];
    		int y=hits[i][1];
    		if(grid[x][y]==0) {  //敲打的为空砖头
    			ret[i]=0;
    			continue;
    		}
    		else {
    			//开始对其上下左右进行合并
    			int org=unionfind.getCount(size);
    			if(x==0) unionfind.union(y,size);  //此处一定注意 如果敲打的为房顶应让其首先与根相连
                for(int j=0;j<4;j++) {
    				int preX=x+X[j]; int preY=y+Y[j];
    				if(preX>=0 && preX<rows && preY>=0 && preY<cols && copy[preX][preY]==1) {
    					unionfind.union(getIndex(preX, preY), getIndex(x, y));
    				}
    			}
    			int cur=unionfind.getCount(size);
    			ret[i]=Math.max(0, cur-org-1);
    			copy[x][y]=1;  //补上敲打的砖头
    		}
    	}
    	return ret;
    }
    
	//将二维坐标转化为一维坐标
	public int getIndex(int x,int y) {
		return x*cols+y;
	}
	
	private class unionFind{
		int[] parent;
		int[] size;
		unionFind(int n){
			parent=new int[n];
			size=new int[n];
			for(int i=0;i<n;i++) {
				parent[i]=i;
				size[i]=1;
			}
		}
		
		int find(int x) {
			if(parent[x]!=x) {
				parent[x]=find(parent[x]);
			}
			return parent[x];
		}
		
		public void union(int x,int y) {
			int rootX=find(x);
			int rootY=find(y);
			if(rootX==rootY) return ;  //两个已经为合并
			parent[rootX]=rootY;
			size[rootY]+=size[rootX];
		}
		
		public int getCount(int x) {
			int root=find(x);
			return size[root];
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值