2741.力扣每日一题6/26 Java

  • 博客主页:音符犹如代码
  • 系列专栏:算法练习
  • 关注博主,后期持续更新系列文章
  • 如果有错误感谢请大家批评指出,及时修改
  • 感谢大家点赞👍收藏⭐评论✍ 

目录

知识点

思路

解题方法

时间复杂度

空间复杂度


知识点

位运算:

  • 概念:对二进制位进行操作的运算。比如代码中的 (1 << n) 表示将数字 1 左移 n 位,得到一个整数,其在二进制表示下只有第 n + 1 位为 1,其他位为 0。
  • 基本用法:包括左移(<<)、右移(>>)、与(&)、或(|)、异或(^)等操作。

动态规划(结合记忆化搜索):

  • 概念:将一个复杂问题分解为若干个子问题,通过求解子问题并保存结果,避免重复计算,从而高效解决原问题。
  • 基本用法:
    • 定义状态:如这里的 memo[s][i] 表示在某种状态 s 和选择了元素 i 时的结果。
    • 状态转移方程:即如何从已有的状态计算出其他状态,如代码中根据不同条件计算 res 并更新记忆数组。

思路

dp[1 << 0][0] = dp[1][0] = 1 (表示选择数字 2 的方案数为 1 )
dp[1 << 1][1] = dp[2][1] = 1 (表示选择数字 3 的方案数为 1 )
dp[1 << 2][2] = dp[4][2] = 1 (表示选择数字 6 的方案数为 1 )

解题方法

dp[u][j] 表示当前状态下的方案数,其中 u 是一个二进制数,表示当前已经选择的数字的集合(通过位运算表示状态压缩),j 表示最后一次选择的数字的索引。

  1. 初始化时,对于每个单独的数字 i(即只包含数字 i 的集合),将 dp[1 << i][i] 设置为 1,表示只有一个数字的排列方案数为 1。
  2. 然后通过两层循环遍历所有可能的状态 u 和数字索引 j 。如果数字 j 不在当前集合 u 中(通过位运算判断),则再通过内层的循环遍历之前已经选择过的数字 k(在集合 u 中)。如果数字 k 和数字 j 满足题目中特别排列的条件(要么 nums[j] % nums[k] == 0 要么 nums[k] % nums[j] == 0 ),则更新状态 dp[u | (1 << j)][j],即加入数字 j 后的方案数,等于原来的方案数加上之前状态 dp[u][k] 的方案数,并对结果取模防止溢出。
  3. 最后,通过遍历所有可能的最后一次选择的数字 j,计算所有满足条件的排列方案数的总和,并对总和取模得到最终答案。

时间复杂度


O(n2∗2)

空间复杂度

𝑂(𝑛∗2𝑛)O(n∗2n)
 

Code

class Solution {
    public int specialPerm(int[] nums) {
        int mod = (int) 1e9 + 7;
        int n = nums.length;
        // dp[u][j] := 当前集合为 u,最后一次选择为 j 时的特殊排列方案数
        int[][] dp = new int[1 << n][n]; 
        for (int i = 0; i < n; i++) {  
            // 选择 i 后,集合中增加 i 
            dp[1 << i][i] = 1;  
        }
        for (int u = 0; u < (1 << n); u++) {  
            // 考虑当前集合为 u 时,加入新的数到集合中 
            for (int j = 0; j < n; j++) {  
                // 集合 u 中不存在 j 时,j 才能加入集合 
                if ((u >> j & 1) == 0) {  
                    // 判断上一个选择的数 k 是否与 j 满足题意要求 
                    for (int k = 0; k < n; k++) {  
                        // k 不在集合中时,说明上一个数选择的数不可能是 k 
                        if ((u & (1 << k)) > 0) {  
                            // k 和 j 满足题意要求时才能形成特殊排列 
                            if ((nums[j] % nums[k] == 0 || nums[k] % nums[j] == 0)) {  
                                dp[u | (1 << j)][j] = (dp[u | (1 << j)][j] + dp[u][k]) % mod;  
                            } 
                        } 
                    } 
                } 
            } 
        } 
        int sum = 0;  
        // 集合为 nums,遍历最后一次选择的数 
        for (int j = 0; j < n; j++) {  
            sum = (sum + dp[(1 << n) - 1][j]) % mod;  
        } 
        return sum;  
    }
}

大自然的每一个领域都是美妙绝伦的。——亚里士多德

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值