目录
题目描述
你总共需要上 numCourses 门课,课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite ,其中 prerequisites[i] = [ai, bi] 表示如果你想选 bi 课程,你 必须 先选 ai 课程。
有的课会有直接的先修课程,比如如果想上课程 1 ,你必须先上课程 0 ,那么会以 [0,1] 数对的形式给出先修课程数对。
先决条件也可以是 间接 的。如果课程 a 是课程 b 的先决条件,课程 b 是课程 c 的先决条件,那么课程 a 就是课程 c 的先决条件。
你也得到一个数组 queries ,其中 queries[j] = [uj, vj]。对于第 j 个查询,您应该回答课程 uj 是否是课程 vj 的先决条件。返回一个布尔数组 answer ,其中 answer[j] 是第 j 个查询的答案。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/course-schedule-iv
举例:
输入:numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
输出:[false,true]
解释:课程 0 不是课程 1 的先修课程,但课程 1 是课程 0 的先修课程。
思路分析
- 此题最容易想到的是拓扑排序,其思路如下:
- 创建一个map集合,key用于存储课程号,value用于存储该课程号下的所有去重先修课程
- 定义一个长度为numCourses的数组,用于存放每个课程号的入度
- 创建一个栈,用于存放入度为0的课程号
- 将栈中的课程号对应的先修课程号存入map的value中
- 在集合中找到queries(i)(0)的课程号对应的所有先修课程values,如果values中包含queries(i)(1),则返回TRUE,否则返回false
- 当然,也可以使用递归来实现,其思路如下:
- 我们的目的是要判断queries(i)(1)的先修课程是否是queries(i)(0),因此
- 创建一个一维数组array,以数组下标作为先修课程号,数组内容为去重的课程号
- 递归逻辑:将先修课程号a下所有课程号放入先修课程号b下去重的课程号中
- 递归退出:当b中的课程号不再包含a中的课程号,则递归结束
解决方案
1、拓扑排序
def checkIfPrerequisite03(numCourses: Int, prerequisites: Array[Array[Int]], queries: Array[Array[Int]]): List[Boolean] = {
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
val list: ListBuffer[Boolean] = mutable.ListBuffer[Boolean]()
// 创建一个map集合,key作为课程号,value的值作为key的先修课程号
val map: mutable.HashMap[Int, mutable.Set[Int]] = new mutable.HashMap[Int, mutable.Set[Int]]()
// 定义一个长度为numCourses的数组,存放每个课程号的入度
val array: Array[Int] = new Array[Int](numCourses)
// 初始化map
for (i <- 0 until(numCourses)){
map.put(i, mutable.HashSet())
}
for (i <- prerequisites.indices){
map(prerequisites(i)(0)).add(prerequisites(i)(1))
// 统计每个课程号的入度
array(prerequisites(i)(1)) += 1
}
// 创建一个栈,用于存放入度为0的课程号
val stack: mutable.Stack[Int] = new mutable.Stack[Int]()
for (i <- array.indices){
if (array(i)== 0){
stack.push(i)
}
}
while (stack.nonEmpty){
val tmp: Int = stack.pop()
for (i <- map(tmp)){
for (j <- 0 until(numCourses)){
if (map(j).contains(tmp)){
// 如果课程j的先修课程号中包含了入度为0的课程号,则将入度为0的先修课程号加入到课程j中
map(j).add(i)
}
}
// 将课程号tmp的入度减1,如果减1后为0,则将其课程号入栈
array(i) -= 1
if (array(i) == 0){
stack.push(i)
}
}
}
queries.foreach(c => {
if (map.contains(c(0)) && map(c(0)).contains(c(1))) {
list.append(true)
} else {
list.append(false)
}
})
list.toList
}
2、递归
def addMap(a: Int, b: Int, map: Array[mutable.Set[Int]]): Unit = {
// 把a里面的元素放到b里面来,只要b里面全都有了a里面的元素即可
for (i <- map(a)){
if (! map(b).contains(i)){
map(b).add(i)
addMap(i,b,map)
}
}
}
def checkIfPrerequisite02(numCourses: Int, prerequisites: Array[Array[Int]], queries: Array[Array[Int]]): List[Boolean] = {
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
val map: Array[mutable.Set[Int]] = new Array[mutable.Set[Int]](numCourses)
val list: ListBuffer[Boolean] = mutable.ListBuffer[Boolean]()
// 初始化map
for (n <- 0 until numCourses){
map(n) = mutable.Set()
}
for (i <- prerequisites.indices){
map(prerequisites(i)(1)).add(prerequisites(i)(0))
}
for (i <- 0 until(numCourses)) {
for (j <- 0 until(numCourses)){
if (map(j).contains(i)){
// 如果j里面的元素有i,就把i里面的元素放到j里面来
addMap(i,j,map)
}
}
}
for (i <- queries.indices){
list.append(map(queries(i)(1)).contains(queries(i)(0)))
}
list.toList
}
两种算法的执行结果
代码总结
- 当定义另一个定长的map数组,数组内容没有默认值时,必须记得初始化,否则其元素是null。
- val map: Array[mutable.Set[Int]] = new Array[mutable.Set[Int]](numCourses)
- scala也提供了栈(Stack)的数据结构,
- 栈主要两个操作: 入栈(push)和出栈(pop)。
优越性比较
在此我以LeetCode提交结果做简要的分析吧,
由图可以发现,
- 同为递归算法,但java的执行用时和内存消耗是最小的,这也说明了scala语言虽然比java简洁,开发效率高,但由于scala底层调用了java,导致其执行效率明显不足。
- 同为scala语言,递归算法确实比拓扑排序要略胜一筹。