力扣-队列&广度优先搜索(循环队列、数据流中的移动平均数、墙与门、岛屿数量、完全平方数)

力扣官网-队列&栈
队列:先进先出

循环队列

思路

主要就是运用取余,rear和front就可以一直++,这样便于分辨队列的满或空,在给队列元素赋值的时候注意需要对len取余即可

代码

class MyCircularQueue {
	int[] queue;
	int front,rear,len;
    public MyCircularQueue(int k) {
    	queue=new int[k];
    	front=rear=0;
    	len=k;
    }
    
    public boolean enQueue(int value) {
    	if((rear-front)==len) return false;
        if((rear==front)){
            queue[rear%len]=value;
            //所有赋值行为都要取余
            rear++;
            //rear指向的是队列末尾的下一位
            return true;
        }
    	queue[rear%len]=value;
        rear++;
    	return true;
    }
    
    public boolean deQueue() {
    	if(rear==front) return false;
    	front++;
    	return true;
    }
    
    public int Front() {
        if(rear==front) return -1;
    	return queue[front%len];

    }
    
    public int Rear() {
        if(rear==front) return -1;
    	return queue[(rear-1)%len];
    	//返回的时候注意要把rear减一再取余
    }
    
    public boolean isEmpty() {
    	return (rear==front);
    }
    
    public boolean isFull() {
    	return ((rear-front)==len);
    }
}

在这里插入图片描述

练习-数据流中的移动平均数

class MovingAverage {
    	int[] queue;
    	int front,rear,len;
        /** Initialize your data structure here. */
        public MovingAverage(int size) {
    		queue=new int[size];
    		rear=front=0;
    		this.len=size;
        }
        public double next(int val) {
        	double res=0;
        	if(rear==0) {
        		queue[rear]=val;
        		rear++;
        		return (double)val;
        	}
            if  ((rear-front)==len) 
        		front++;
            queue[rear%len]=val;
            //注意这里要取余!!!!
			rear++;

        	for(int i=front;i<rear;i++) {
        		res+=queue[i%len];
        		//这里可以更简便,每次加入新元素时,在前一次的求和结果基础上减掉要去掉的元素,再加上新元素,下面详写
        	}
        	return res/(double)(rear-front);
        	
        }
    }

简化

作者:光辉岁月
链接:https://leetcode-cn.com/leetbook/read/queue-stack/k1msc/?discussion=kFiNev

class MovingAverage {
    int[] arr;
    int size;
    int ptr;
    int sum;
    int len;
    /** Initialize your data structure here. */
    public MovingAverage(int size) {
        this.arr = new int[size];
        this.size = size;
        this.ptr = 0;
        this.sum = 0;
        this.len = 0;
    }
    
    public double next(int val) {
        if(len<size){
            sum += val;
            arr[ptr%size] = val;
            ptr++;
            len++;
            return ((double)sum)/len;
        }
        sum -= arr[ptr%size];
        arr[ptr%size] = val;
        sum += val;
        ptr++;
        return ((double)sum)/len;

    }
}

java的内置队列

// "static void main" must be defined in a public class.
public class Main {
    public static void main(String[] args) {
        // 1. 新建一个队列
        Queue<Integer> q = new LinkedList();
        // 2. 获得第一个元素,如果队列为空则返回null
        System.out.println("The first element is: " + q.peek());
        // 3. offer是进队列,poll是出队列
        q.offer(5);
        q.offer(13);
        q.offer(8);
        q.offer(6);
        q.poll();
        // 4. Get the first element.
        System.out.println("The first element is: " + q.peek());
        // 5. Get the size of the queue.
        System.out.println("The size is: " + q.size());
    }
}

队列与BFS(广度优先搜索)

适用于求目标节点与根节点的最短路径
原理:
我们首先将根结点排入队列。然后在每一轮中,我们逐个处理已经在队列中的结点,并将所有邻居添加到队列中。这些节点并不是马上被处理,而是在下一轮被处理。
如:
在这里插入图片描述
第一轮:队列:[A]
第二轮:队列:A,[ B,C,D]
第三轮:队列:A,B,C,D,[E,F,G]

(如果允许节点重复的话)
第四轮:队列:A,B,C,D,E,F,G,[G]

练习1-墙与门

在这里插入图片描述

题解

先找出所有门,然后依次从这个门出发广度优先搜索,将房间的值与到当前门的步数作比较,如果后者更小则将其设为房间门的值

class Solution {
    	class Position{
        	int x;
        	int y;
        	public Position(int x,int y) {
        		this.x=x;
        		this.y=y;
    			// TODO Auto-generated constructor stub
    		}
        }
    	private void init(int[][] rooms,Queue<Position> queue) {
    		//找到所有门
    		for(int i=0;i<rooms.length;i++) {
        		for (int j=0;j<rooms[0].length;j++) {
        			if (rooms[i][j]==0) 
						queue.offer(new Position(i, j));
        			}
        		}
    		}
    	private void explore(int [][]rooms,int explore_x,int explore_y,Queue<Position> rooms_queue,int step) {
    		//从当前位置开始向四周探索
    		try {
			    //如果探索位置的值大于step,则加入队列,并修改该房间的值。
    			//这里的step指的是到当前门的步数
				if(rooms[explore_x][explore_y]>step) {
				rooms_queue.offer(new Position(explore_x, explore_y));
				rooms[explore_x][explore_y]=step;
				}
			} catch (java.lang.ArrayIndexOutOfBoundsException e) {
				//如果超出范围则不理会
				// TODO: handle exception
			}
    	}
        public void wallsAndGates(int[][] rooms) {
        	Queue<Position> door_queue=new LinkedList<Position>() ;
        	//先找到所有门
        	init(rooms, door_queue);
        	Queue<Position> rooms_queue=new LinkedList<Position>();
        	//再从每一个门出发,修改房间的值
        	for(Position door_position:door_queue) {
				int step=1;
				//到这个门需要的步数
        		rooms_queue.offer(door_position);
        		while(!rooms_queue.isEmpty()) {
        			//这里一开始写的是rooms.queue !=null 导致陷入死循环,详情看下面的知识点讲解
        			int length=rooms_queue.size();
        			for(int i=0;i<length;i++) {
        				Position herePosition=rooms_queue.poll();
        				int here_x=herePosition.x;
        				int here_y=herePosition.y;
        				explore(rooms, here_x+1, here_y, rooms_queue, step);
        				//右
        				explore(rooms, here_x-1, here_y, rooms_queue, step);
        				//左
        				explore(rooms, here_x, here_y+1, rooms_queue, step);
        				//上
        				explore(rooms, here_x, here_y-1, rooms_queue, step);	
        				//下
        			}
					step++;
					//每一轮处理后,step++,表示到门的距离增加了
        		}
        	}
        	}}

题解2

主要思路与题解1一样,只不过并不是一个一个门进行广度优先搜索,而且所有门同时开始搜索,
执行快了,但占的内存变多了

    class Solution {
    	class Position{
        	int x;
        	int y;
        	public Position(int x,int y) {
        		this.x=x;
        		this.y=y;
    			// TODO Auto-generated constructor stub
    		}
        }
    	private void init(int[][] rooms,Queue<Position> queue) {
    		//找到所有门,并将它们加入队列
    		for(int i=0;i<rooms.length;i++) {
        		for (int j=0;j<rooms[0].length;j++) {
        			if (rooms[i][j]==0) 
						queue.offer(new Position(i, j));
        			}
        		}
    		}
    	private void explore(int [][]rooms,int explore_x,int explore_y,Queue<Position> rooms_queue,int step) {
    		//从当前位置开始向四周探索
    		try {
			    //如果探索位置的值大于step,则加入队列,并修改该房间的值。
    			//这里的step指的是到当前门的步数
				if(rooms[explore_x][explore_y]>step) {
				rooms_queue.offer(new Position(explore_x, explore_y));
				rooms[explore_x][explore_y]=step;
				}
			} catch (java.lang.ArrayIndexOutOfBoundsException e) {
				//如果超出范围则不理会
				// TODO: handle exception
			}
    	}
        public void wallsAndGates(int[][] rooms) {
        	Queue<Position> rooms_queue=new LinkedList<Position>() ;
        	//先找到所有门
        	init(rooms, rooms_queue);
        	//从所有门同时出发
				int step=1;
			//到某一个门需要的步数
        		while(!rooms_queue.isEmpty()) {
        			//这里一开始写的是rooms.queue !=null 导致陷入死循环,详情看下面的知识点讲解
        			int length=rooms_queue.size();
        			for(int i=0;i<length;i++) {
        				Position herePosition=rooms_queue.poll();
        				int here_x=herePosition.x;
        				int here_y=herePosition.y;
        				explore(rooms, here_x+1, here_y, rooms_queue, step);
        				//右
        				explore(rooms, here_x-1, here_y, rooms_queue, step);
        				//左
        				explore(rooms, here_x, here_y+1, rooms_queue, step);
        				//上
        				explore(rooms, here_x, here_y-1, rooms_queue, step);	
        				//下
        			}
					step++;
					//每一轮处理后,step++,表示到门的距离增加了
        		}
        	}
        	}

题解3

用int[]取代自己定义的position类,但好像并没有提升效率(
用if-continue取代try-catch,效率大幅upup
注意这种解法是从所有门出发,同时走,
所以只要是被重新设定了值的房间一定是被设定了到达某一个门的最小值。

    class Solution {
        private static final List<int[]> distance=Arrays.asList(new int[] {0,1},new int[] {0,-1},new int[] {1,0},new int[] {-1,0});
        //移动的四个方向

    	private void init(int[][] rooms,Queue<int[]> queue) {
    		//找到所有门,并将它们加入队列
    		for(int i=0;i<rooms.length;i++) {
        		for (int j=0;j<rooms[0].length;j++) {
        			if (rooms[i][j]==0) 
						queue.offer(new int[] {i, j});
        			}
        		}
    		}
        public void wallsAndGates(int[][] rooms) {
        	int m=rooms.length;
        	int n=rooms[0].length;
        	Queue<int[]> rooms_queue=new LinkedList<int[]>() ;
        	//先找到所有门
        	init(rooms, rooms_queue);
        	//从所有门同时出发
			//到某一个门需要的步数
        		while(!rooms_queue.isEmpty()) {
        			//这里一开始写的是rooms.queue !=null 导致陷入死循环,详情看下面的知识点讲解
        			int length=rooms_queue.size();
        			for(int i=0;i<length;i++) {
        				int[] herePosition=rooms_queue.poll();
        				int here_x=herePosition[0];
        				int here_y=herePosition[1];
        				for(int[] move:distance) {
        					int new_x=here_x+move[0];
        					int new_y=here_y+move[1];
        					if(new_x>=m||new_x<0||new_y>=n||new_y<0||rooms[new_x][new_y]!=2147483647)
        						//若超界或不为空房间则跳过
        						continue;
        					rooms[new_x][new_y]=rooms[here_x][here_y]+1;
        					rooms_queue.add(new int[] {new_x,new_y});
        				}
        			}
					//每一轮处理后,step++,表示到门的距离增加了
        		}
        	}
        	}

知识点

方法调用参数

救命,关于java方法调用与参数的关系我竟然忘了

java程序设计语言总是采用按值调用,也就是说,方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。简单说:一个方法不能修改一个基本数据类型的参数
但 传参还有另一种方法:按引用传参,这是可以修改原数据的,因为虽然方法中的只是拷贝,但他们引用的是同一个对象,即可以改变一个对象参数的状态
但一个方法并不能让一个对象参数引用一个新的对象

啊好像不是这个问题(

null与[ ]

先看代码:

public class No2 {
	public static void main(String[] args) {	
		Queue<Integer> testqueue=new LinkedList<Integer>();
		System.out.println(testqueue);//[]
		System.out.println(testqueue==null);//false
		testqueue.add(1);
		System.out.println(testqueue);//[1]
		testqueue=null;
		System.out.println(testqueue);//null
		System.out.println(testqueue==null);//true
		testqueue.add(1);//报错:Exception in thread "main" java.lang.NullPointerException
	}
}

当队列为空时,queue是已经分配了内存的,可以添加数据,只不过此时长度为0。
当队列为null时,表示其不存在,未分配内存,不能进行操作。

队列要用isEmpty()而不是==null

练习2-岛屿数量

在这里插入图片描述

思路

跟上一题差不多
先找陆地,找到后利用广度优先搜索将与其相连的陆地置为海洋(挖海)
然后再找陆地,如果还有就再挖海
最后挖了几次海就是原先有几片陆地

力扣题解还给了深度优先搜索和并查集力扣

题解

    class Solution {
        private static List<int[]> distance=Arrays.asList(new int[] {1,0},new int[] {-1,0},new int[] {0,1},new int[] {0,-1});
    	private void dig(char[][] grid,int i,int j) {
    		//挖海,将与所给位置相邻的陆地全部挖掉
    		//挖的方法就是广度优先算法
    		int width=grid.length;
    		int height=grid[0].length;
    		Queue<int[]> queue=new LinkedList<int[]>();
    		queue.offer(new int[] {i,j});
    		while (!queue.isEmpty()) {
    			int length=queue.size();
    			for(int n=0;n<length;n++) {
    				int [] here=queue.poll();
    				int here_x=here[0];
    				int here_y=here[1];
    				for (int[] dis:distance) {
    					int xx=here_x+dis[0];
    					int yy=here_y+dis[1];
    					if(xx>=width||xx<0||yy>=height||yy<0||grid[xx][yy]!='1')
    						continue;
    					grid[xx][yy]='0';
    					queue.offer(new int[] {xx,yy});
    				}
    			}
    		}
    		
    		
    	}
        public int numIslands(char[][] grid) {
        	int cou=0;
        	for(int i=0;i<grid.length;i++) {
        		for(int j=0;j<grid[0].length;j++) {
        			//如果发现一块陆地,就调用挖海,每完成一次挖海,cou++,最后挖了几次海说明原来就有几片陆地
        			if (grid[i][j]=='1') {
        				dig(grid, i, j);
        				cou++;
        			}
        		}
        	}
        	return cou;
        }
    }

练习3-打开转盘锁

在这里插入图片描述

题解1

思路是广度优先搜索,每一个字符串都有八种(2*4)变化可能
然后处理避免重复以及deadend

class Solution {
		HashSet<String>set=new HashSet<String>();
		Queue<String> queue=new LinkedList<String>();
		HashSet<String> deadendSet=new HashSet<String>();
		private String operation(String a,int mode) {
			//定义拨号码
			if(mode==1) {
				if(a.equals("9")) return "0";
				return String.valueOf(Integer.valueOf(a)+1);
			}
			else {
				if(a.equals("0")) return "9";
				return String.valueOf(Integer.valueOf(a)-1);
			}
		}
	    public int openLock(String[] deadends, String target) {
	    	for(String i:deadends)
	    		//储存deadend
	    		deadendSet.add(i);
	  
	    	if(deadendSet.contains("0000"))return -1;
	    	if(target.equals("0000")) return 0;
	    	//两种特殊情况
	    	
	    	int step=1;
	    	String initString="0000";
	    	set.add(initString);
	    	queue.offer(initString);
	    	while(!queue.isEmpty()) {
	    		int length=queue.size();
	    		for(int i=0;i<length;i++) {
	    			String nowString=queue.poll();
	    			String no0=nowString.substring(0,1);
	    			String no1=nowString.substring(1,2);
	    			String no2=nowString.substring(2,3);
	    			String no3=nowString.substring(3,4);
	    			//char与int相加时,char会转成ASCII码,所以用字符串来加
	    			for(int j=0;j<8;j++) {
	    				//每轮拨号码的八种可能
	    				switch (j) {
						case 0: nowString=operation(no0, 1)+no1+no2+no3;break;
						case 1: nowString=operation(no0, -1)+no1+no2+no3;break;
						case 2: nowString=no0+operation(no1, 1)+no2+no3;break;
						case 3: nowString=no0+operation(no1, -1)+no2+no3;break;
						case 4: nowString=no0+no1+operation(no2, 1)+no3;break;
						case 5: nowString=no0+no1+operation(no2, -1)+no3;break;
						case 6: nowString=no0+no1+no2+operation(no3, 1);break;
						case 7: nowString=no0+no1+no2+operation(no3, -1);break;							
						}
	    				if(set.contains(nowString)||deadendSet.contains(nowString))
	    					//重复或为deadend
							continue;
	    				System.out.println("step:"+step+"   "+nowString);
						if(nowString.equals(target)) return step;
						set.add(nowString);
						queue.offer(nowString);
	    			}
	    		}
				step++;
	    	}
	    	return -1;
	    }
	}
   

题解2

题解1效率双低…
看了官方解答,思路是一样的,但是实现过程有区别
用两重循环来取代了我的switch-case配上自定义

for(int m=0;m<4;m++) {
	for(int n=-1;n<=1;n++) {
	    int changed=(nowString.charAt(m)-'0'+n+10)%10;
	    //这里利用字符相减获得该位与'0'的差值,然后进行变换
	    //因为可能会变换出-1,所以先加上十,再对十取余
	    String changedString=
	    nowString.substring(0,m)+changed+nowString.substring(m+1,4);
	    ...
	    }
	}
	class Solution {
		HashSet<String>set=new HashSet<String>();
		Queue<String> queue=new LinkedList<String>();
		HashSet<String> deadendSet=new HashSet<String>();
	    public int openLock(String[] deadends, String target) {
	    	for(String i:deadends)
	    		//储存deadend
	    		deadendSet.add(i);
	  
	    	if(deadendSet.contains("0000"))return -1;
	    	if(target.equals("0000")) return 0;
	    	//两种特殊情况
	    	
	    	int step=1;
	    	String initString="0000";
	    	set.add(initString);
	    	queue.offer(initString);
	    	deadendSet.add(initString);
	    	while(!queue.isEmpty()) {
	    		int length=queue.size();
	    		for(int i=0;i<length;i++) {
	    			String nowString=queue.poll();
	    			for(int m=0;m<4;m++) {
	    				for(int n=-1;n<=1;n++) {
	    					int changed=(nowString.charAt(m)-'0'+n+10)%10;
	    					//这里利用字符相减获得该位与'0'的差值,然后进行变换
	    					//因为可能会变换出-1,所以先加上十,再对十取余
	    					String changedString=nowString.substring(0,m)+changed+nowString.substring(m+1,4);
	    					if(set.contains(changedString)||deadendSet.contains(changedString))
		    					//重复或为deadend
								continue;
							if(changedString.equals(target)) return step;
							set.add(changedString);
							queue.offer(changedString);
	    				}
	    			}
	    		}
				step++;
	    	}
	    	return -1;
	    }
	}

为什么改过之后还是效率感人?
哦,照搬官方解效率一样感人,那没事了

题解3

看了大佬解法,可以双向奔赴,从“0000”和target同时广度优先搜索,效率upup

	class Solution {
		
		HashSet<String>set=new HashSet<String>();
		Queue<String> queue1=new LinkedList<String>();
		Queue<String> queue2=new LinkedList<String>();
		HashSet<String> deadendSet=new HashSet<String>();
		HashSet<String>targetSet=new HashSet<String>();
		
	    public int openLock(String[] deadends, String target) {
	    	for(String i:deadends)
	    		//储存deadend
	    		deadendSet.add(i);
	  
	    	if(deadendSet.contains("0000"))return -1;
	    	if(target.equals("0000")) return 0;
	    	//两种特殊情况
	    	
	    	int step=1;
	    	String initString="0000";
	    	set.add(initString);
	    	queue1.offer(initString);
	    	//"0000"队列
	    	
	    	targetSet.add(target);
	    	queue2.offer(target);
	    	//target队列
	    	
	    	while((!queue1.isEmpty())&&(!queue2.isEmpty())) {
	    		int length1=queue1.size();
	    		int length2=queue2.size();

	    		for(int i=0;i<length1;i++) {
	    			//"0000"队列方向
	    			String nowString=queue1.poll();
	    			for(int m=0;m<4;m++) {
	    				for(int n=-1;n<=1;n++) {
	    					int changed=(nowString.charAt(m)-'0'+n+10)%10;
	    					//这里利用字符相减获得该位与'0'的差值,然后进行变换
	    					//因为可能会变换出-1,所以先加上十,再对十取余
	    					String changedString=nowString.substring(0,m)+changed+nowString.substring(m+1,4);
	    					if(set.contains(changedString)||deadendSet.contains(changedString))
		    					//重复或为deadend
								continue;
							if(targetSet.contains(changedString)) return step;
							//这里的return step 不单单看target,而是看targetSet
							set.add(changedString);
							queue1.offer(changedString);
	    				}
	    			}
	    		}
	    		step++;
	    		for(int i=0, j=0;i<length2;i++) {
	    			//"target队列方向
	    			String nowString=queue2.poll();
	    			for(int m=0;m<4;m++) {
	    				for(int n=-1;n<=1;n++) {
	    					int changed=(nowString.charAt(m)-'0'+n+10)%10;
	    					//这里利用字符相减获得该位与'0'的差值,然后进行变换
	    					//因为可能会变换出-1,所以先加上十,再对十取余
	    					String changedString=nowString.substring(0,m)+changed+nowString.substring(m+1,4);
	    					if(targetSet.contains(changedString)||deadendSet.contains(changedString))
		    					//重复或为deadend
								continue;
							if(set.contains(changedString)) return step;
							//同理
							targetSet.add(changedString);
							queue2.offer(changedString);
	    				}
	    			}
	    		}
				step++;
	    	}
	    	
	    	return -1;
	    }
	}

char与int相加时注意ASCII、巧用取余、双向奔赴

练习4-完全平方数

在这里插入图片描述

题解

就广度优先加呗


class Solution {
    public int numSquares(int n) {
    	int a=(int)Math.sqrt((double)n);
    	Queue<Integer> search=new LinkedList<Integer>();
    	HashSet<Integer> usedHashSet=new HashSet<Integer>();
    	int step=1;
    	if (a*a==n) return 1;
    	for(int i=1;i<=a;i++) {
    		search.offer(i*i);
    		usedHashSet.add(i*i);
    		//保证不重复
    	}
    	while(!search.isEmpty()) {
    		step++;
    		int length=search.size();
    		for (int i = 0; i < length; i++) {
        		int tem=search.poll();
				for(int j=1;j<=a;j++) {
					int newtem=tem+j*j;
					if(newtem==n) return step;
					if(newtem>n||usedHashSet.contains(newtem)) continue;
					search.add(newtem);
                    usedHashSet.add(newtem);
				}
			}
    	}
    	return -1;
    }
}

题解2

看了看题解,获得小启发,可以反过来进行广度优先,就是用减法。

class Solution {
    public int numSquares(int n) {
    	int a=(int)Math.sqrt((double)n);
    	Queue<Integer> search=new LinkedList<Integer>();
    	HashSet<Integer> usedHashSet=new HashSet<Integer>();
    	int step=1;
    	if (a*a==n) return 1;
    	for(int i=1;i<=a;i++) {
    		search.offer(n-i*i);
    		usedHashSet.add(n-i*i);
    	}
    	while(!search.isEmpty()) {
    		step++;
    		int length=search.size();
    		for (int i = 0; i < length; i++) {
        		int tem=search.poll();
				for(int j=1;j<=Math.sqrt((double)tem);j++) {
					int newtem=tem-j*j;
					if(newtem==0) return step;
					if(usedHashSet.contains(newtem)) continue;
					search.add(newtem);
					usedHashSet.add(newtem);
				}
			}
    	}
    	return -1;
    }
}

反向思考

队列和广度优先搜索 done

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值