一天一大 leet(判断二分图)难度:中等-Day20200716
时间:2022-07-25
本文章向大家介绍一天一大 leet(判断二分图)难度:中等-Day20200716,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
题目:
给定一个无向图 graph,当这个图为二分图时返回 true。
如果我们能将一个图的节点集合分割成两个独立的子集 A 和 B,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,我们就将这个图称为二分图。
graph 将会以邻接表方式给出,graph[i]表示图中与节点 i 相连的所有节点。每个节点都是一个在 0 到 graph.length-1 之间的整数。这图中没有自环和平行边:graph[i] 中不存在 i,并且 graph[i]中没有重复的值。
示例:
- 示例 1
输入: [[1,3], [0,2], [1,3], [0,2]]
输出: true
解释:
无向图如下:
0----1
| |
| |
3----2
我们可以将节点分成两组: {0, 2} 和 {1, 3}。
- 示例 2
输入: [[1,2,3], [0,2], [0,1,3], [0,2]]
输出: false
解释:
无向图如下:
0----1
| |
| |
3----2
我们不能将节点分割成两个独立的子集。
注意
- graph 的长度范围为 [1, 100]。
- graph[i] 中的元素的范围为 [0, graph.length - 1]。
- graph[i] 不会包含 i 或者有重复的值。
- 图是无向的: 如果 j 在 graph[i]里边, 那么 i 也会在 graph[j]里边。
抛砖引玉
- 理解下题意:
位置 |
节点 |
A |
B |
---|---|---|---|
0 |
[1,3] |
[0] |
[1,3] |
1 |
[0,2] |
[0,0] |
[1,3] |
2 |
[1,3] |
[0,0,2] |
[1,3,1,1,3] |
3 |
[0,2] |
[0,0,0,2] |
[1,3,1,1,3] |
结果 |
- |
[0,2] |
[1,3] |
逻辑
- A 填充索引
- b 填充值
- A 填充前判断该索引 i 在 B 中存在吗:
- A 中存在 graph[i]中的值,则将 graph[i]填充 A,i 填充 B
- A 中不存在 graph[i]中的值,则将 graph[i]填充 B,i 填充 A AB 均为去重填充
- 不存在,
- 存在,将改值 graph[i]填充到 A,A 为去重填充
- 按照以上规则,发现当 graph[i]中的值在 A 中出现或者在 B 中出现收受影响的并不是改组值,需要对单个值进行处理
换种思路:
- 有连接的数据一定不在一组中则分别不同的组
- 那从 i=0 遍历所有点,对确定在连接线两端的数组分组
- graph[i]会有多个值,那把他们都分到与 i 不同的组
- 在遍历 graph[i]时会遇到与 i 相同的节点,为了避免重复遍历则声明 dp 作为记录,存放过的节点不再操作
注意
- A 优先填充索引
- 填充过的数据在遍历索引时不能重复填充,避免默认值与逻辑值冲突
- 一个元素填充过 A 之后又在遍历中填充 B 则说明无法生成二分图 返回 false
实现
- 按节点遍历,使用递归填充其索引 i 对应的值 graph[i]
- 递归参数:索引,填充到的数组标记
- 递归的终止条件:
- 已填充过,即在 dp 中出现过
/**
* @param {number[][]} graph
* @return {boolean}
*/
var isBipartite = function (graph) {
let _result = false,
dp = new Map(),
A = new Map(),
B = new Map()
for (let i = 0; i < graph.length; i++) {
// 跳过已经存存放的节点,如不跳过已存放节点,那默认填充A会与已填充元素冲突
if (!dp.has(i)) {
pushItem(i, 'A')
}
}
function pushItem(i, flag) {
// 已经存存放的节点确认是否冲突
if (dp.has(i)) {
_result = _result || flag !== dp.get(i)
return
}
// 存入A组
if (flag === 'A') {
A.set(i)
dp.set(i, 'A')
} else {
// 存入B组
B.set(i)
dp.set(i, 'B')
}
// graph[i]的所有元素都不能与i在同一个分组
for (let j = 0; j < graph[i].length; j++) {
pushItem(graph[i][j], flag === 'A' ? 'B' : 'A')
}
}
return !_result
}
优化
上面最终生成了 A,B 两个组,但是这两个组其实并没有参与判断,则优化删除 A,B 两个组对象
/**
* @param {number[][]} graph
* @return {boolean}
*/
var isBipartite = function (graph) {
let _result = false,
dp = new Map()
for (let i = 0; i < graph.length; i++) {
// 跳过已经存存放的节点,如不跳过已存放节点,那默认填充A会与已填充元素冲突
if (!dp.has(i)) {
pushItem(i, 'A')
}
}
function pushItem(i, flag) {
// 已经存存放的节点确认是否冲突
if (dp.has(i)) {
_result = _result || flag !== dp.get(i)
return
}
dp.set(i, flag)
// graph[i]的所有元素都不能与i在同一个分组
for (let j = 0; j < graph[i].length; j++) {
pushItem(graph[i][j], flag === 'A' ? 'B' : 'A')
}
}
return !_result
}
其他解法
- 声明一个存储对象 dp 记录每个元素的分组 A 组标记 1,B 组标记-1
- 声明一个 queue(存放索引时存放一个,存放索引对应的值是存放多个)
- 遍历 graph 将其索引 i 放入 queue,在从其取出带上标记存放到 dp
- 放入 graph[i]对应的值到 queue 依次取出带上标记存放到 dp
- 每次 queue 从取出元素时切换标记
/**
* @param {number[][]} graph
* @return {boolean}
*/
var isBipartite = function (graph) {
let dp = new Map()
for (let i = 0; i < graph.length; i++) {
if (dp.has(i)) continue
let queue = [i]
dp.set(i, 1)
while (queue.length) {
let j = queue.shift()
let A = dp.get(j),
B = -A
for (let k = 0; k < graph[j].length; k++) {
let item = graph[j][k]
if (!dp.has(item)) {
dp.set(item, B)
queue.push(item)
} else if (dp.get(item) != B) {
return false
}
}
}
}
return true
}
- 我们来继续研究 mybatis 框架sql映射文件的属性
- 开源.NET邮件服务器
- 次次获得《头脑王者》满分的秘诀
- 如何在ASP.NET 2.0中定制Expression Builders
- codeproject 几篇asp.net文章
- .NET Web 自动化测试工具
- Javascript数组常用方法[包含MS AJAX.NET的prototype扩展方法]示例
- 10步骤优化SQL Server 数据库性能
- 漫谈语音合成之Char2Wav模型
- [基础]Javascript中的继承示例代码
- javascript天生就具备类似c#中的"委托"功能
- 使用MonoDevelop开发跨平台的应用程序
- Pycharm使用技巧总结
- [基础]电话/手机常见验证的Javascript示例
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- [javascript] elementui和vue下复制粘贴上传图片
- SQL Server通过创建临时表遍历更新数据
- 对于 JavaScript 中循环之间的技术差异概述
- 初识 webpack 原理——自定义插件
- 高性能解决线程饥饿的利器 StampedLock
- 前端应该知道的web调试工具——whistle
- 最强大的 CSS 布局 —— Grid 布局
- SAP Spartacus ProductConnector和ProductService实现
- Sorted Adjacent Differences(CodeForces - 1339B)【思维+贪心】
- Redis 发布订阅,小功能大用处,真没那么废材!
- R语言实现生物序列的降维比对
- Tomcat NIO(9)-IO线程-Overall流程和关键类
- 有限元平面四边形等差单元python编程
- 动图演示:手撸堆栈的两种实现方法!
- [Go] 实战项目在线客服GO-FLY -在gin框架使用IP识别库转换IP为城市