LeetCode - 1700. 无法吃午餐的学生数量
Posted 想名真难
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode - 1700. 无法吃午餐的学生数量相关的知识,希望对你有一定的参考价值。
学校的自助午餐提供圆形和方形的三明治,分别用数字 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种情况之一:
- 顶部的三明治为0, 剩余学生全部为1;
- 顶部的三明治为1,剩余学生全部为0;
学生的顺序不影响分配三明治, 用一个字典统计出学生对于三明治的数量,key为三明治的类型, value为吃此三明治的学生数量, 经过一轮循环后,字典内的最终结果为0:x个, 1:y个,
然后遍历三明治,每个三明治都从字典中移除一个学生,结束的条件就是
- 某个三明治对应的字典中学生数量为0,表示无人吃此三明治
- 三明治全部遍历完成
用字典是为了方便拓展, 比如三明治的品类变多,或者变成了其他类型的食物, 如果只是为了这道题的话,用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), 并且和学生的顺序无关。
综合来看,此算法更优秀一点。
以上是关于LeetCode - 1700. 无法吃午餐的学生数量的主要内容,如果未能解决你的问题,请参考以下文章