算法之腐烂的橘子

下面要分享的是一道来自leetcode的简单算法题,让我们一起进步成长。
使用语言:JavaScript

说明:使用到队列 + 多源广度搜索

题目:
// 994. 腐烂的橘子
// 在给定的网格中,每个单元格可以有以下三个值之一:

// 值 0 代表空单元格;
// 值 1 代表新鲜橘子;
// 值 2 代表腐烂的橘子。
// 每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。
// 返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。

// 示例 1:
// 输入:[[2,1,1],[1,1,0],[0,1,1]]
// 输出:4

// 示例 2:
// 输入:[[2,1,1],[0,1,1],[1,0,1]]
// 输出:-1
// 解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个正向上。

// 示例 3:
// 输入:[[0,2]]
// 输出:0
// 解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。

// 提示:
// 1 <= grid.length <= 10
// 1 <= grid[0].length <= 10
// grid[i][j] 仅为 0、1 或 2

简单版:

function orangesRotting1(grid) {
	if(grid == 0 || grid.length == 0) return 0;
	var minute = 1;
	var flag = true;
	var r = grid.length;
	var c = grid[0].length;
	var note = new Array(r);

	for(var i = 0; i < r; i ++) {
		note[i] = new Array(c);
		for(var j = 0; j < c; j ++) {
			if(grid[i][j] == 2) {
				note[i][j] = minute;
			} else {
				note[i][j] = 0;
			}
		}
	}

	while(flag) {
		flag = false;
		for(var i = 0; i < r; i ++) {
			for(var j = 0; j < c; j ++) {
				if(grid[i][j] == 2 && note[i][j] == minute) {
					// console.log(i, j)
					if(i > 0 && grid[i - 1][j] == 1) {
						grid[i - 1][j] = 2;
						note[i - 1][j] = minute + 1;
						flag = true;
					}
					if(i < r - 1 && grid[i + 1][j] == 1) {
						grid[i + 1][j] = 2;
						note[i + 1][j] = minute + 1;
						flag = true;
					}

					if(j > 0 && grid[i][j - 1] == 1) {
						grid[i][j - 1] = 2;
						note[i][j - 1] = minute + 1;
						flag = true;
					}
					if(j < c - 1 && grid[i][j + 1] == 1) {
						grid[i][j + 1] = 2;
						note[i][j + 1] = minute + 1;
						flag = true;
					}
				}
			}
		}

		flag && minute ++;
		// console.log(note);
	}

	for(var i = 0; i < r; i ++) {
		for(var j = 0; j < c; j ++) {
			if(grid[i][j] == 1) {
				return -1;
			}
		}
	}
	return minute - 1;
}

进阶版:
基本解题思路:
先找出所有的腐烂的橘子,把他们都排到一个队列里面保存起来,然后从队列里按个获取出来,然后依据这个从队列里面获取出来的腐烂的橘子,判断它的周围(周围指:上下左右)是否有新鲜的橘子,如果有,就让这些新鲜的橘子腐烂,然后再把新腐烂的橘子在存到队列中,然后我们在从队列中获取橘子,在判断它的周围有没有新鲜的橘子…,依次搜索下去,找到队列里面没有橘子。

那么大家肯定会有疑问?
1.多源广度搜索的多源在哪里体现?
答:我们在第一次搜索时到的所有腐烂的橘子都放到了对队列里面,这些第一次搜索得到的橘子就是最初的源,如果有多个,就是多源了,而且这些源是属于同一层的(就是说,他们会同时使周围的新鲜橘子腐烂)

2.你都使用队列了,每一次取出一个腐烂的橘子,怎么会同时使周围的新鲜橘子腐烂呢?
答:队列的一个特性是先进先出,我们第一次搜索得到的橘子,就都排在队列的队头,队列里面第一个橘子去污染新鲜橘子后,就把这个橘子从队列中去掉,把新腐烂的橘子加到队尾,然后获取队列里面的第一个橘子,在去腐烂它周围新鲜的橘子,依次类推,只有在第一次搜索得到的腐烂橘子污染完成结束后,第二批腐烂的橘子才会去污染它周围的的新鲜橘子…

其次,这里的同时确实指的是同一个时刻,但是在代码实现运行上,我们如果是理解成同一个时段会更好。在第一个时段,队列里面的第一批腐烂的橘子去污染周围的新鲜橘子;第二个时段,队列里的第二批腐烂的橘子去污染周围新鲜的橘子…依次类推,腐烂的橘子污染完周围的新鲜橘子后,就从队头里去掉,所以在队列为空的时候,就污染完成

然后我们在判断是否还有存在新鲜的橘子。

3.那么,我们怎么记录它的污染全部新鲜橘子要多少分钟呢?
答:我们可以是使用一个map对象,可以把腐烂橘子在数组中的位置和在哪一个时间(即第几分钟)进行污染的形成一直对应关系。如果每一个腐烂的橘子(叫做A)污染了一个新鲜的橘子(叫做B),B的位置对应的时间段就是在A对应的时间段上加1.然后我们每一个有个橘子被污染,我们就记录下来,比较得出时间段的最大值。

function orangesRotting2(grid) {
	if(grid == null || grid.length == 0) return -1;
	// 用来扩大橘子的腐烂范围(上下左右)
	// eg: [2, 3]是腐烂的橘子
	// 那么就可以可能使[2, 4],[2, 1],[1, 3],[3, 3]这四个位置上的新鲜橘子腐烂
	var a = [-1, 0, 1, 0];
	var b = [0, -1, 0, 1]; 

	var ans = 0;
	var r = grid.length;
	var c = grid[0].length;
	var map = new Map();
	var queue = new Array();

	for(var i = 0; i < r; i ++) {
		for(var j = 0; j < c; j ++) {
			if(grid[i][j] == 2) {
				var code = i * c + j;
				// 把腐烂橘子排到队列的最后面
				queue.push(code);
				map.set(code, 0);
			}
		}
	}
	

	while(queue.length != 0) {
		// 把队列的第一个给拿出来
		var code = queue.shift();
		// 计算出这个橘子的位置
		i = Math.floor(code / c);
		j = code % c;

		for(var k = 0; k < 4; k ++) {
			// 寻找橘子上下左右的位置
			var nI = i + a[k];
			var nJ = j + b[k];

			if(nI < r && nI >= 0 && nJ >= 0 && nJ < c && grid[nI][nJ] == 1) {
				// 使新鲜橘子腐烂
				grid[nI][nJ] = 2;
				var ncode = nI * c + nJ;
				// 把腐烂橘子排到队列最后面
				queue.push(ncode);
				map.set(ncode, map.get(code) + 1);
				ans = map.get(ncode) > ans ? map.get(ncode) : ans;
			}
		}
		
	}

	// 判断是否还有新鲜的橘子
	for(var i = 0; i < r; i ++) {
		for(var j = 0; j < c; j ++) {
			if(grid[i][j] == 1) {
				return -1
			}
		}
	}
	return ans;

}

高阶版:
基本思路:
1.先找出腐烂的橘子,同时找出新鲜橘子的个数,把腐烂的橘子存在一个队列里面
2.遍历这个队列,在声明一个队列nextQueue来存第二批腐烂的橘子,然后在让第一批腐烂的橘子去污染新鲜的橘子,把被污染的橘子保存在nextQueue里,并且新鲜橘子的个数要减1,在第一批腐烂的橘子完成无污染后,把nextQueue里面腐烂的橘子给到最初队列,然后继续遍历这个队列,循环操作,直到这个队列里没有值

最后,判断新鲜橘子的个数是不是为0既可。

function orangesRotting(grid) {
	if(grid == null || grid.length == 0 || grid[0].length == 0) return -1;
	const a = [0, 1, 0, -1];
	const b = [1, 0, -1, 0];

	let minute = 0;
	let fresh = 0;
	const R = grid.length;
	const C = grid[0].length;
	let queue = [];
	

	for(let r = 0; r < R; r ++) {
		for(let c = 0; c < C; c ++) {
			if(grid[r][c] == 2) {
				queue.push([r, c]);
			} else if(grid[r][c] == 1){
				fresh ++;
			}
		}
	}

	while(queue.length != 0 && fresh) {
		let nextQueue = [];
		while(queue.length != 0) {
			let badOrange = queue.shift();
			for(let k = 0; k < 4; k ++) {
				let i = badOrange[0] + a[k];
				let j = badOrange[1] + b[k];
				if(i >= 0 && i < R && j >= 0 && j < C && grid[i][j] == 1) {
					grid[i][j] = 2;
					nextQueue.push([i, j]);
					fresh --;
				}
			}
		}
		minute ++;
		queue = nextQueue;
	}
	return fresh == 0 ? minute : -1; 
}

如果本文章有何处错误,麻烦及时指出,我们一起成长,一起进步。
谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值