学校的自助午餐提供圆形和方形的三明治,分别用数字 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), 并且和学生的顺序无关。
综合来看,此算法更优秀一点。
