LEETCODE 1434. Number of Ways to Wear Different Hats to Each Other 解题思路分析

题目大意:

每个人戴不同帽子的方案数

总共有 n 个人和 40 种不同的帽子,帽子编号从 1 到 40 。

给你一个整数列表的列表 hats ,其中 hats[i] 是第 i 个人所有喜欢帽子的列表。

请你给每个人安排一顶他喜欢的帽子,确保每个人戴的帽子跟别人都不一样,并返回方案数。

由于答案可能很大,请返回它对 10^9 + 7 取余后的结果。

示例 1:

输入:hats = [[3,4],[4,5],[5]]
输出:1
解释:给定条件下只有一种方法选择帽子。
第一个人选择帽子 3,第二个人选择帽子 4,最后一个人选择帽子 5。

示例 2:

输入:hats = [[3,5,1],[3,5]]
输出:4
解释:总共有 4 种安排帽子的方法:
(3,5),(5,3),(1,3) 和 (1,5)

示例 3:

输入:hats = [[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]
输出:24
解释:每个人都可以从编号为 1 到 4 的帽子中选。
(1,2,3,4) 4 个帽子的排列方案数为 24 。

示例 4:

输入:hats = [[1,2,3],[2,3,5,6],[1,3,7,9],[1,8,9],[2,5,7]]
输出:111

提示:

  • n == hats.length
  • 1 <= n <= 10
  • 1 <= hats[i].length <= 40
  • 1 <= hats[i][j] <= 40
  • hats[i] 包含一个数字互不相同的整数列表。

如果想查看本题目是哪家公司的面试题,请参考以下免费链接: https://leetcode.jp/problemdetail.php?id=1434

解题思路分析:

这是一道不错的题目,运用到的知识点很全面,也是面试官比较青睐的一类题型。

回到题目,我们看到本题的结果会非常大,通常情况下,这需要使用到动态规划(DP)来解题。本题也不例外,另外,之前的文章我也多次强调过,所有的动态规划实际上都可以转化为递归加记忆数组的形式来解题。个人认为相比于DP,这种解题方式更像是dfs,也更便于理解。本题我们也会使用递归方式来做讲解。

本题的大意是(最多)10个人去选择40顶帽子,并且每个人的选择不能重复。题目给出的参数是每个人可以选择的帽子列表,因此在递归时,我们可以按照人的编号进行递归,对于当前人,它可以选择的帽子是题目给定的列表,我们在当前递归中尝试循环选择每一顶帽子,如果当前帽子还没有被他人指定,那么我们可以选择当前帽子,并递归到下一个人。循环中的每一个递归函数都会返回一个结果(选择的可能性),我们将所有结果相加,即是本层递归的返回结果。这个递归过程实际上相当于dfs找路径数,当递归完最后一个人后,结束当前路径递归并返回1。

以上是典型的递归思路,并没有太大的难度,但是本题的难点在于如何构建记忆数组。如果你熟悉我们文章的话,你应该了解,记忆数组的定义实际上取决于递归函数中的参数。那些可变的参数即是记忆数组(DP数组)的组成。例如本题,记忆数组中的可变参数是记录当前人的index,以及哪些帽子已经被选择过的状态信息。两个变量参数代表需要2维记忆数组。index还好说,被选择过的状态信息也不难,我们可以使用一个boolean型的访问数组来表示,并且该boolean数组也可以使用二进制数来替代,但问题是题目给出一共有40顶帽子,也就是说我们需要一个40位的二进制数来记录每顶帽子的选择情况,这显然超出了int型的最大值。因此这会导致建立二维记忆数组时报错。

这就是本题另一个精妙之处,如果你不能想通这个难点,你就无法进阶到动态规划的下一个境界。实际上对于这类问题,我们需要换个角度去思考,当发现给定的参数的数量级超越了记忆数组的承受范围时,我们应尝试变换题目的参数,比如本题给出的数组hats ,它代表了每个人所有喜欢帽子的列表。假设人和帽子之间的关系是相互的,我们可以将该变量转化为每顶帽子喜欢人的列表。这样一来,在递归时,我们以帽子的index作为递归对象,递归中,循环该帽子所有喜欢的人的列表,如果当前人已经被其他帽子选择时,跳过。否则选择当前人,并递归到下一顶帽子。当所有人都被帽子选择时,返回1。当递归完最后一顶帽子后仍有人尚未被帽子选择时,返回0。这样,我们在建立记忆数组时,一维变量是帽子的index,二维变量是所有人被选择的状态,因为题目中最多只有10个人,因此利用一个10位的二进制数即可表示该状态。

实现代码:

Integer[][] memo; // 记忆数组
public int numberWays(List<List<Integer>> hats) {
    int maxBit=0; // 按照人数,计算出一个最大的二进制数
    for(int i=0;i<hats.size();i++){
        maxBit+= (1<<i);
    }
    // 初始化记忆数组
    memo=new Integer[41][maxBit+1];
    // 将题目给出的每个人喜欢的帽子列表,转化为每顶帽子喜欢的人的列表
    List<Integer>[] people = new List[41];
    for(int i=0;i<hats.size();i++){
        List<Integer> hatList=hats.get(i);
        for(int j=0;j<hatList.size();j++){
            if(people[hatList.get(j)]==null){
                people[hatList.get(j)]=new ArrayList<>();
            }
            people[hatList.get(j)].add(i);
        }
    }
    // 递归求解
    return help(people,0,0,maxBit);
}
int help(List<Integer>[] people, int index, int bit,int maxBit){
    // 如果所有人都被选择,返回1
    if(bit==maxBit) return 1;
    // 递归完所有帽子,仍有人未被选择时,返回0。
    if(index==41) return 0;
    // 如果记忆数组中存在当前解,直接返回
    if(memo[index][bit]!=null) return memo[index][bit];
    // 取得当前帽子喜欢人的列表
    List<Integer> peopleList = people[index];
    // 当前帽子不选择任何人。
    long res=help(people, index+1, bit,maxBit);
    if(peopleList==null) return (int)res;
    // 当前帽子循环选择每个他喜欢的人
    for(int p : peopleList){
        // 如果当前人已经被其他帽子选择,跳过
        if(((bit>>p) & 1) == 1) continue;
        // 选择当前人并递归至下一个帽子
        res+=help(people,index+1, bit | (1<<p),maxBit);
    }
    // 将当前结果计入记忆数组
    memo[index][bit]=(int)(res%1000000007);
    return (int)(res%1000000007);
}

本题解法执行时间22ms。

Runtime: 22 ms, faster than 71.40% of Java online submissions for Number of Ways to Wear Different Hats to Each Other.

Memory Usage: 39.7 MB, less than 100.00% of Java online submissions for Number of Ways to Wear Different Hats to Each Other.

本网站文章均为原创内容,并可随意转载,但请标明本文链接
如有任何疑问可在文章底部留言。为了防止恶意评论,本博客现已开启留言审核功能。但是博主会在后台第一时间看到您的留言,并会在第一时间对您的留言进行回复!欢迎交流!
本文链接: https://leetcode.jp/leetcode-1434-number-of-ways-to-wear-different-hats-to-each-other-解题思路分析/
此条目发表在leetcode分类目录,贴了, , , , 标签。将固定链接加入收藏夹。

发表评论

您的电子邮箱地址不会被公开。