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
 


最好想的方式就是按照题目模拟这个过程,

  1. 让每个三明治和学生进行匹配
    1. 匹配成功,学生出队列,三明治出队列
    2. 匹配失败,学生从队首移到队尾,三明治不动,继续下一个学生

还有一点要注意的的是,结束循环的条件有2种:

  1. 当剩余的学生经过一次循环后,都没有匹配成功,说明这个三明治无人匹配成功,应该结束所有循环,剩余的学生数量就是无法吃午餐的数量
  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种情况之一:

  1. 顶部的三明治为0, 剩余学生全部为1;
  2. 顶部的三明治为1,剩余学生全部为0;

学生的顺序不影响分配三明治, 用一个字典统计出学生对于三明治的数量,key为三明治的类型,  value为吃此三明治的学生数量, 经过一轮循环后,字典内的最终结果为0:x个, 1:y个,

然后遍历三明治,每个三明治都从字典中移除一个学生,结束的条件就是

  1. 某个三明治对应的字典中学生数量为0,表示无人吃此三明治
  2. 三明治全部遍历完成

用字典是为了方便拓展, 比如三明治的品类变多,或者变成了其他类型的食物, 如果只是为了这道题的话,用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. 无法吃午餐的学生数量的主要内容,如果未能解决你的问题,请参考以下文章

栈无法吃午餐的学生数量(leetcode1700)

栈无法吃午餐的学生数量(leetcode1700)

今天朋友带领去谷歌ok2eat 餐厅吃免费午餐

五月集训(第14天) —— 栈

健身的饮食观念

BZOJ1899午餐(动态规划)