Medium

题目描述

给定两个长度为 n 的整数数组 nums1nums2。你可以对 nums1 执行任意次数的分割合并操作:

  • 选择一个子数组 nums1[L..R]
  • 移除该子数组,留下前缀 nums1[0..L-1](如果 L = 0 则为空)和后缀 nums1[R+1..n-1](如果 R = n - 1 则为空)
  • 将移除的子数组(保持原始顺序)重新插入到剩余数组的任意位置(即任意两个元素之间、开头或末尾)

返回将 nums1 转换为 nums2 所需的最少分割合并操作次数。

示例 1:

输入:nums1 = [3,1,2], nums2 = [1,2,3]
输出:1
解释:
- 分割出子数组 [3](L = 0, R = 0);剩余数组为 [1,2]
- 将 [3] 插入末尾;数组变为 [1,2,3]

示例 2:

输入:nums1 = [1,1,2,3,4,5], nums2 = [5,4,3,2,1,1]
输出:3
解释:
- 移除索引 0-2 的 [1,1,2];剩余 [3,4,5];在位置 2 插入 [1,1,2],得到 [3,4,1,1,2,5]
- 移除索引 1-3 的 [4,1,1];剩余 [3,2,5];在位置 3 插入 [4,1,1],得到 [3,2,5,4,1,1]
- 移除索引 0-1 的 [3,2];剩余 [5,4,1,1];在位置 2 插入 [3,2],得到 [5,4,3,2,1,1]

约束条件:

  • 2 <= n == nums1.length == nums2.length <= 6
  • -10^5 <= nums1[i], nums2[i] <= 10^5
  • nums2nums1 的一个排列

解题思路

这是一个典型的状态空间搜索问题。由于数组长度最大为6,状态空间相对较小,我们可以使用BFS找到最短路径。

核心思路:

  1. 状态表示:将每个数组状态作为搜索节点
  2. BFS搜索:从初始状态 nums1 开始,目标状态为 nums2
  3. 状态转移:对于当前状态,枚举所有可能的子数组 [L, R],移除后在所有位置重新插入
  4. 去重优化:使用哈希表记录已访问状态,避免重复搜索

详细步骤:

  • 初始化队列,放入起始状态和操作次数0
  • 使用集合记录已访问的状态(将数组转为元组作为键)
  • 对每个出队状态,生成所有可能的下一状态:
    • 枚举所有子数组 [L, R]
    • 移除该子数组,得到剩余部分
    • 在剩余部分的每个位置插入被移除的子数组
  • 当找到目标状态时返回操作次数

由于约束条件保证了较小的搜索空间,BFS能够高效找到最优解。

代码实现

class Solution {
public:
    int minSplitMerge(vector<int>& nums1, vector<int>& nums2) {
        queue<pair<vector<int>, int>> q;
        set<vector<int>> visited;
        
        q.push({nums1, 0});
        visited.insert(nums1);
        
        while (!q.empty()) {
            auto [curr, ops] = q.front();
            q.pop();
            
            if (curr == nums2) {
                return ops;
            }
            
            int n = curr.size();
            // 枚举所有可能的子数组 [L, R]
            for (int L = 0; L < n; L++) {
                for (int R = L; R < n; R++) {
                    // 提取子数组
                    vector<int> subarray(curr.begin() + L, curr.begin() + R + 1);
                    // 构建剩余数组
                    vector<int> remaining;
                    for (int i = 0; i < L; i++) {
                        remaining.push_back(curr[i]);
                    }
                    for (int i = R + 1; i < n; i++) {
                        remaining.push_back(curr[i]);
                    }
                    
                    // 在所有可能位置插入子数组
                    for (int pos = 0; pos <= remaining.size(); pos++) {
                        vector<int> next = remaining;
                        next.insert(next.begin() + pos, subarray.begin(), subarray.end());
                        
                        if (visited.find(next) == visited.end()) {
                            visited.insert(next);
                            q.push({next, ops + 1});
                        }
                    }
                }
            }
        }
        
        return -1;
    }
};
class Solution:
    def minSplitMerge(self, nums1: List[int], nums2: List[int]) -> int:
        from collections import deque
        
        q = deque([(tuple(nums1), 0)])
        visited = {tuple(nums1)}
        
        while q:
            curr_tuple, ops = q.popleft()
            curr = list(curr_tuple)
            
            if curr == nums2:
                return ops
            
            n = len(curr)
            # 枚举所有可能的子数组 [L, R]
            for L in range(n):
                for R in range(L, n):
                    # 提取子数组
                    subarray = curr[L:R+1]
                    # 构建剩余数组
                    remaining = curr[:L] + curr[R+1:]
                    
                    # 在所有可能位置插入子数组
                    for pos in range(len(remaining) + 1):
                        next_arr = remaining[:pos] + subarray + remaining[pos:]
                        next_tuple = tuple(next_arr)
                        
                        if next_tuple not in visited:
                            visited.add(next_tuple)
                            q.append((next_tuple, ops + 1))
        
        return -1
public class Solution {
    public int MinSplitMerge(int[] nums1, int[] nums2) {
        var q = new Queue<(int[], int)>();
        var visited = new HashSet<string>();
        
        q.Enqueue((nums1, 0));
        visited.Add(string.Join(",", nums1));
        
        while (q.Count > 0) {
            var (curr, ops) = q.Dequeue();
            
            if (curr.SequenceEqual(nums2)) {
                return ops;
            }
            
            int n = curr.Length;
            // 枚举所有可能的子数组 [L, R]
            for (int L = 0; L < n; L++) {
                for (int R = L; R < n; R++) {
                    // 提取子数组
                    var subarray = curr.Skip(L).Take(R - L + 1).ToArray();
                    // 构建剩余数组
                    var remaining = curr.Take(L).Concat(curr.Skip(R + 1)).ToArray();
                    
                    // 在所有可能位置插入子数组
                    for (int pos = 0; pos <= remaining.Length; pos++) {
                        var next = remaining.Take(pos)
                                          .Concat(subarray)
                                          .Concat(remaining.Skip(pos))
                                          .ToArray();
                        string nextKey = string.Join(",", next);
                        
                        if (!visited.Contains(nextKey)) {
                            visited.Add(nextKey);
                            q.Enqueue((next, ops + 1));
                        }
                    }
                }
            }
        }
        
        return -1;
    }
}
var minSplitMerge = function(nums1, nums2) {
    const n = nums1.length;
    const target = nums2.join(',');
    
    if (nums1.join(',') === target) return 0;
    
    const queue = [nums1.slice()];
    const visited = new Set([nums1.join(',')]);
    let operations = 0;
    
    while (queue.length > 0) {
        const size = queue.length;
        operations++;
        
        for (let i = 0; i < size; i++) {
            const current = queue.shift();
            
            // Try all possible subarrays [L, R]
            for (let L = 0; L < n; L++) {
                for (let R = L; R < n; R++) {
                    const subarray = current.slice(L, R + 1);
                    const remaining = [...current.slice(0, L), ...current.slice(R + 1)];
                    
                    // Try inserting subarray at all possible positions
                    for (let pos = 0; pos <= remaining.length; pos++) {
                        const newArray = [...remaining.slice(0, pos), ...subarray, ...remaining.slice(pos)];
                        const newState = newArray.join(',');
                        
                        if (newState === target) return operations;
                        
                        if (!visited.has(newState)) {
                            visited.add(newState);
                            queue.push(newArray);
                        }
                    }
                }
            }
        }
    }
    
    return -1;
};

复杂度分析

复杂度分析
时间复杂度O(n! × n³)
空间复杂度O(n! × n)

说明:

  • 时间复杂度:最多有 n! 种不同的数组排列状态,对每个状态需要枚举 O(n²) 个子数组,每个子数组有 O(n) 个插入位置
  • 空间复杂度:队列和哈希表最多存储 O(n!) 个状态,每个状态占用 O(n) 空间