「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。
记录 1 道算法题
等式方程的可满足性
思路
这道题的要求是给定一个等式 'a==b'。这个等式的特点是只有4位,也就是说除去中间的 == 或者 != 之外,等式两边是一个字母,他们可能是同一个字母,也可能不是同一个字母。
所以我们可以得出范围是26个英文字母。
如果在多个等式中,我们可以给出现的字母各赋值一个数字,使每一个等式都成立。只要有一个等式不符合就返回 false。都符合就返回 true。
例子:
a!=b, b==c 我们可以提供 a = 任何数,b 和 c 是同一个数就可以。返回 true
a==b, b!=a 我们无法提供一个数让 a==b 同时 b!=a。返回false
我们可以想象成帮派合并,相等的是同一个帮派,当有一个字母他在帮派里,又不等于帮派的时候,就是 false,其余情况都是 true。
我们可以分类成多个相等的阵营,多个不相等的阵营。这两种阵营之间应该是不相互交叉的。相等是一个很大的阵营,因为与阵营内的字母相等就会被吸纳进这个阵营,但可以有多组没有交集的相等。比如: a==b, c==d。
不相等是跟相等没有交集的,无论分成了多少个相等阵营和不相等阵营,相等阵营跟不相等阵营就是水火不容。
提到合并,很容易想到并查集。并查集就是解决集合的合并问题,也能很清晰的知道两个字母是不是在一个集合里面。
实现
那我们先准备一个数组,来存放字母之间的关系。同时因为我们得到的是字母,所以我们还需要做字母和关系数组下标的映射。利用 JavaScript 的特点,我们动态的扩增关系数组。
const map = {} // 映射
const parent = [] // 关系数组
for(let i = 0; i < arr.length; i++) {
const item = arr[i]
// 等式两边的字母
const a = item[0]
const b = item[3]
if (map[a] == undefined) {
create(map, parent, a)
}
if (map[b] == undefined) {
create(map, parent, b)
}
}
这个 create 方法实现了扩增关系数组。在 parent 最后添加上新增位置的下标。并在 map 做映射。
function (map, parent, a) {
const i = parent.length
map[a] = parent.push(i) - 1
}
然后我们要开始处理等式。我们采取的策略是等号时给字母进行合并。不等号时给字母进行检查,如果他们是一个集合就返回 false。如果不是一个集合就不做任何操作。因为 create 的时候已经分配了另一个集合。
但是只有不等号的时候才返回 false,总感觉考虑少了。
如果是在不等号的时候检查集合,已经合并过了后才检查得出来。那假如先检查了不等于分配了另一个集合。但下一个又等于,就进行合并。但实际上不应该合并。所以在等于的分支也要进行检查。
但是检查起来很麻烦,因为 create 默认分配了不同的集合。所以我们不知道他的默认的不同还是不等于的不同。
所以采取的策略是,先遍历一遍只处理等号,目的是建立字母之间的联系。然后再遍历一遍只处理不等号,做检验。这样就解决了等号时的检验问题。
所以完整代码如下:
function equationsPossible(equations) {
const map = {}
const parent = []
// 第一遍建立联系,只检查等号
for (let i = 0; i < equations.length; i++) {
const item = equations[i]
const a = item[0]
const b = item[3]
const equal = item[1] === '='
if (map[a] == undefined) {
create(map, parent, a)
}
if (map[b] == undefined) {
create(map, parent, b)
}
if (equal) {
const j = map[a]
const k = map[b]
if (a !== b) {
// 找到下标,然后往前合并
const n1 = find(parent, j)
const n2 = find(parent, k)
if (n1 < n2) {
parent[n2] = n1
} else if (n1 > n2) {
parent[n1] = n2
}
}
}
}
// 第二遍只检查不等号
for (let i = 0; i < equations.length; i++) {
const item = equations[i]
const a = item[0]
const b = item[3]
const equal = item[1] === '='
if (!equal) {
const j = map[a]
const k = map[b]
// 如果不是相同字母就检查是不是同一个集合
if (a !== b) {
const n1 = find(parent, j)
const n2 = find(parent, k)
if (n1 === n2) {
return false
}
} else {
// 同一个字母自己返回 false 了
return false
}
}
}
// 全部正确
return true
}
function create(map, parent, a) {
const i = parent.length
map[a] = parent.push(i) - 1
}
// 并查集的经典查找方法,一直读下标,直到下标相等
function find(parent, i) {
if (parent[i] !== i) {
parent[i] = find(parent, parent[i])
}
return parent[i]
}
结束