根据搜索结果,我已经了解了 LeetCode 3533 的题目要求和解题思路。这是一个状态压缩动态规划问题,核心思路是:

1. 排序:先对 `nums` 排序,这样按顺序搜索时,第一个找到的可行解就是字典序最小的
2. 预处理:计算每个数字的位数和 `10^len % k`
3. 状态压缩 DP:`dp[mask][mod]` 表示已选数字集合为 `mask`,当前拼接数模 `k` 为 `mod` 是否可行
4. 路径重建:通过记忆化搜索重建字典序最小的排列

以下是 Rust 实现:

```rust
use std::collections::HashMap;

impl Solution {
    pub fn concatenated_divisibility(nums: Vec<i32>, k: i32) -> Vec<i32> {
        let n = nums.len();
        let k = k as usize;
        let mut nums = nums;
        
        // 排序,保证按顺序找第一个可行解就是字典序最小的
        nums.sort();
        
        // 预处理:计算每个数字的位数和 10^len % k
        let mut lengths = vec![0; n];
        let mut pow10_mod = vec![0; n];
        
        for i in 0..n {
            let num = nums[i] as usize;
            let len = num.to_string().len();
            lengths[i] = len;
            
            // 计算 10^len % k
            let mut pow = 1usize;
            for _ in 0..len {
                pow = (pow * 10) % k;
            }
            pow10_mod[i] = pow;
        }
        
        // 状态压缩 DP
        // dp[mask][mod] = true/false,表示是否可行
        // 使用 HashMap 或二维数组,这里用 Vec<Vec<Option<bool>>>
        let mut memo = vec![vec![None; k]; 1 << n];
        
        // 先判断是否存在可行解
        if !Self::dfs(&nums, &pow10_mod, &mut memo, k, 0, 0, n) {
            return vec![];
        }
        
        // 重建路径:按排序后的顺序,每次选第一个能导致可行解的数字
        let mut result = Vec::with_capacity(n);
        let mut mask = 0usize;
        let mut mod_val = 0usize;
        
        for _ in 0..n {
            for i in 0..n {
                if (mask >> i) & 1 == 1 {
                    continue; // 已选过
                }
                
                let new_mod = (mod_val * pow10_mod[i] + nums[i] as usize) % k;
                let new_mask = mask | (1 << i);
                
                // 检查选这个数后是否仍能完成
                if Self::dfs(&nums, &pow10_mod, &mut memo, k, new_mask, new_mod, n) {
                    result.push(nums[i]);
                    mask = new_mask;
                    mod_val = new_mod;
                    break;
                }
            }
        }
        
        result
    }
    
    fn dfs(
        nums: &[i32],
        pow10_mod: &[usize],
        memo: &mut Vec<Vec<Option<bool>>>,
        k: usize,
        mask: usize,
        mod_val: usize,
        n: usize,
    ) -> bool {
        // 所有数字都已选完
        if mask == (1 << n) - 1 {
            return mod_val == 0;
        }
        
        // 记忆化
        if let Some(res) = memo[mask][mod_val] {
            return res;
        }
        
        // 尝试每个未选的数字
        for i in 0..n {
            if (mask >> i) & 1 == 1 {
                continue;
            }
            
            let new_mod = (mod_val * pow10_mod[i] + nums[i] as usize) % k;
            let new_mask = mask | (1 << i);
            
            if Self::dfs(nums, pow10_mod, memo, k, new_mask, new_mod, n) {
                memo[mask][mod_val] = Some(true);
                return true;
            }
        }
        
        memo[mask][mod_val] = Some(false);
        false
    }
}
```

关键思路说明:

1. 排序保证字典序:先对 `nums` 排序,在 DFS 和重建路径时都按升序尝试,这样第一个找到的可行解就是字典序最小的排列。

2. 模运算拼接:拼接 `a` 和 `b` 的数学表示为 `a * 10^len(b) + b`。在模 `k` 意义下,如果当前余数为 `mod`,拼接数字 `nums[i]`(长度为 `len`)后的新余数为:
   
```
   new_mod = (mod * (10^len % k) + nums[i]) % k
   ```

3. 状态压缩:用 `mask`(二进制位)表示已选数字集合,`dp[mask][mod]` 记录该状态是否可行,避免重复计算。

4. 路径重建:先通过 DFS 填充记忆化表,然后贪心地在每一步选择排序后第一个能导致可行解的数字,保证字典序最小。

复杂度:
- 时间:O(n × 2^n × k),其中 n ≤ 13,k ≤ 100
- 空间:O(2^n × k)

 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐