学校的自助午餐提供圆形和方形的三明治,分别用数字 0 和 1 表示。所有学生站在一个队列里,每个学生要么喜欢圆形的要么喜欢方形的。
餐厅里三明治的数量与学生的数量相同。所有三明治都放在一个 栈 里,每一轮:
如果队列最前面的学生 喜欢 栈顶的三明治,那么会 拿走它 并离开队列。
否则,这名学生会 放弃这个三明治 并回到队列的尾部。
这个过程会一直持续到队列里所有学生都不喜欢栈顶的三明治为止。
给你两个整数数组 students 和 sandwiches ,其中 sandwiches[i] 是栈里面第 i 个三明治的类型(i = 0 是栈的顶部), students[j] 是初始队列里第 j 名学生对三明治的喜好(j = 0 是队列的最开始位置)。请你返回无法吃午餐的学生数量。
示例 1:
输入:students = [1,1,0,0], sandwiches = [0,1,0,1]
输出:0
解释:
- 最前面的学生放弃最顶上的三明治,并回到队列的末尾,学生队列变为 students = [1,0,0,1]。
- 最前面的学生放弃最顶上的三明治,并回到队列的末尾,学生队列变为 students = [0,0,1,1]。
- 最前面的学生拿走最顶上的三明治,剩余学生队列为 students = [0,1,1],三明治栈为 sandwiches = [1,0,1]。
- 最前面的学生放弃最顶上的三明治,并回到队列的末尾,学生队列变为 students = [1,1,0]。
- 最前面的学生拿走最顶上的三明治,剩余学生队列为 students = [1,0],三明治栈为 sandwiches = [0,1]。
- 最前面的学生放弃最顶上的三明治,并回到队列的末尾,学生队列变为 students = [0,1]。
- 最前面的学生拿走最顶上的三明治,剩余学生队列为 students = [1],三明治栈为 sandwiches = [1]。
- 最前面的学生拿走最顶上的三明治,剩余学生队列为 students = [],三明治栈为 sandwiches = []。
所以所有学生都有三明治吃。
示例 2:
输入:students = [1,1,1,0,0,1], sandwiches = [1,0,0,0,1,1]
输出:3
链接:https://leetcode.cn/problems/number-of-students-unable-to-eat-lunch
最好想的方式就是按照题目模拟这个过程,
还有一点要注意的的是,结束循环的条件有2种:
class Solution {
func countStudents(_ students: [Int], _ sandwiches: [Int]) -> Int {
var sandwichesArray = sandwiches
var studentsArray = students
while true {
// 最顶部的三明治
let topSandwich = sandwichesArray[0]
let result = canFindStudent(studentsArray, topSandwich)
// 三明治能找到学生
if result.canFind {
sandwichesArray.remove(at: 0)
studentsArray = result.students
} else {
// 三明治不能找到学生,结束循环
break
}
// 三明治为空, 结束循环
if sandwichesArray.count == 0 {
break
}
}
return studentsArray.count
}
/// 最顶部的三明治能不能找到合适的学生
/// - Parameters:
/// - students: 学生数组
/// - sandwiches: 顶部的三明治
/// - Returns: YES: 能找到; NO: 不能找到; 修改后的学生数组
func canFindStudent(_ students: [Int], _ topSandwich: Int) -> (canFind: Bool,students: [Int]) {
// 临时队列, 修改学生信息
var tempStudentArray = students
// 默认不能找到
var canFind = false
for oneStudent in students {
// 匹配成功, 返回结果
if oneStudent == topSandwich {
tempStudentArray.remove(at: 0)
canFind = true
break
} else {
// 匹配不成功,移到队尾, 继续下一次
let topStudent = tempStudentArray.remove(at: 0)
tempStudentArray.append(topStudent)
}
}
return (canFind, tempStudentArray)
}
}
假设N是学生数组的数量,由于三明治数量和学生数量一致,所以也是N
算法最差的情况下需要O(N^2), 最好的情况下需要O(N), 和学生的顺序有关。
还有一种思路是采用整体思考的方式, 由于学生不吃三明治会从队首移动到队尾,学生的顺序并不影响分配三明治,
出现无法吃午餐,一定是下面2种情况之一:
学生的顺序不影响分配三明治, 用一个字典统计出学生对于三明治的数量,key为三明治的类型, value为吃此三明治的学生数量, 经过一轮循环后,字典内的最终结果为{0:x个, 1:y个},
然后遍历三明治,每个三明治都从字典中移除一个学生,结束的条件就是
用字典是为了方便拓展, 比如三明治的品类变多,或者变成了其他类型的食物, 如果只是为了这道题的话,用2个变量来记录也是可以的。
class Solution {
func countStudents(_ students: [Int], _ sandwiches: [Int]) -> Int {
// 统计出学生对于三明治的数量,
// 三明治为key,value为吃此三明治的学生数量,最终结果为{0:x个, 1:y个}
// 用字典是为了方便拓展, 比如三明治的品类变多,或者变成了其他类型的食物
var studentDic = [Int:Int]()
for oneStu in students {
if var count = studentDic[oneStu] {
count += 1
studentDic[oneStu] = count
} else {
studentDic[oneStu] = 1
}
}
// 由于学生的顺序并不影响分配三明治,只要找到无人吃的三明治,剩余的学生数量就是所有的无法吃午餐的数量,同时由于学生数量和三明治数量是一致的,剩余的三明治数量也可以作为结果返回
// 找出无法在匹配成功的三明治
for (i,oneSandwiche) in sandwiches.enumerated() {
// 三明治找学生,
// 1.找到了,把此学生从字典中移除
// 2.找不到,返回剩余的三明治数量,此数量和剩余学生数量是相等的
var count = studentDic[oneSandwiche] ?? 0
if count > 0 {
count -= 1
studentDic[oneSandwiche] = count
} else {
return sandwiches.count - i
}
}
return 0
}
}
此算法只需要2次遍历即可完成,时间复杂度为O(N), 并且和学生的顺序无关。
综合来看,此算法更优秀一点。