LeetCode 2610 条件二维数组转换:三种解法深度剖析与多语言实现(NeetCode 仓库实战)
【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode
本文基于本仓库 articles/convert-an-array-into-a-2d-array-with-conditions.md 的技术讲解,结合仓库内的 Java / Kotlin 源码,系统拆解「将数组按条件转换为二维数组」这道经典题。读完本文,你将掌握暴力法、排序法、频率计数法三种递进式解法及其适用场景,理解「每个元素出现第几次就放入第几行」这一核心不变量,并拿到 Python、Java、C++、JavaScript、C#、Go、Kotlin、Swift、Rust 九种语言的可直接运行实现。
题目与核心要求
给定一个长度为n的整数数组nums(1 <= n <= 200,1 <= nums[i] <= n),要求将其转换为一个二维数组,并满足:
- 二维数组由0 个或更多个一维数组(行)组成;
- 每一行中元素互不重复(每个元素在行内最多出现一次);
- 二维数组中包含的整数集合与
nums完全一致(每个元素出现次数不丢失)。
换句话说,我们需要把所有数字按行"摊开",但任何一行都不能出现相同的数字。由于最大重复频次限制了行的数量,行数的最小值等于数组中出现次数最多的元素的频率——这正是后续三种解法共同围绕的结论。
前置知识
在动手之前,建议先熟悉两个基础能力(这也是原文档列出的 Prerequisites):
- 哈希表(频率计数):用来记录每个元素已经被放置了多少次,从而直接决定它该进入哪一行;
- 二维数组的动态构建:在运行过程中按需
append/add新的行(list of lists),而不是预先分配固定大小的二维矩阵。
解法一:暴力法(Brute Force)
核心直觉
最朴素的想法是:顺序扫描每一行,找到第一个不含当前数字的行放进去;如果所有已存在的行都含有该数字,就新建一行。这是一种贪心放置策略,天然保证"能用最少的行数装下所有数字"——因为只有遇到重复到无处可放时才会开辟新行。
算法步骤
- 初始化一个空的二维数组
res; - 遍历
nums中的每个数字:- 从行
0开始依次检查:若当前行不包含该数字,则选定此行; - 若所有已存在的行都包含该数字,则新建一个空行并选中它;
- 把数字追加到选中的行末尾;
- 从行
- 返回
res。
多语言实现
class Solution: def findMatrix(self, nums: List[int]) -> List[List[int]]: res = [] for num in nums: r = 0 while r < len(res): if num not in res[r]: break r += 1 if r == len(res): res.append([]) res[r].append(num) return respublic class Solution { public List<List<Integer>> findMatrix(int[] nums) { List<List<Integer>> res = new ArrayList<>(); for (int num : nums) { int r = 0; while (r < res.size()) { if (!res.get(r).contains(num)) { break; } r++; } if (r == res.size()) { res.add(new ArrayList<>()); } res.get(r).add(num); } return res; } }class Solution { public: vector<vector<int>> findMatrix(vector<int>& nums) { vector<vector<int>> res; for (int num : nums) { int r = 0; while (r < res.size()) { if (find(res[r].begin(), res[r].end(), num) == res[r].end()) { break; } r++; } if (r == res.size()) { res.push_back({}); } res[r].push_back(num); } return res; } };class Solution { /** * @param {number[]} nums * @return {number[][]} */ findMatrix(nums) { const res = []; for (const num of nums) { let r = 0; while (r < res.length) { if (!res[r].includes(num)) { break; } r++; } if (r === res.length) { res.push([]); } res[r].push(num); } return res; } }public class Solution { public IList<IList<int>> FindMatrix(int[] nums) { IList<IList<int>> res = new List<IList<int>>(); foreach (int num in nums) { int r = 0; while (r < res.Count) { if (!res[r].Contains(num)) { break; } r++; } if (r == res.Count) { res.Add(new List<int>()); } res[r].Add(num); } return res; } }func findMatrix(nums []int) [][]int { res := [][]int{} for _, num := range nums { r := 0 for r < len(res) { found := false for _, v := range res[r] { if v == num { found = true break } } if !found { break } r++ } if r == len(res) { res = append(res, []int{}) } res[r] = append(res[r], num) } return res }class Solution { fun findMatrix(nums: IntArray): List<List<Int>> { val res = mutableListOf<MutableList<Int>>() for (num in nums) { var r = 0 while (r < res.size) { if (num !in res[r]) { break } r++ } if (r == res.size) { res.add(mutableListOf()) } res[r].add(num) } return res } }class Solution { func findMatrix(_ nums: [Int]) -> [[Int]] { var res = [[Int]]() for num in nums { var r = 0 while r < res.count { if !res[r].contains(num) { break } r += 1 } if r == res.count { res.append([]) } res[r].append(num) } return res } }impl Solution { pub fn find_matrix(nums: Vec<i32>) -> Vec<Vec<i32>> { let mut res: Vec<Vec<i32>> = Vec::new(); for &num in &nums { let mut r = 0; while r < res.len() { if !res[r].contains(&num) { break; } r += 1; } if r == res.len() { res.push(Vec::new()); } res[r].push(num); } res } }复杂度分析
- 时间复杂度:O(n × m),其中
n是数组长度,m是数组中出现频率最高元素的频率。最坏情况(例如nums全为同一个数)下,每插入一个元素都要线性扫描前面的所有行。 - 空间复杂度:O(n),用于存放输出数组。
解法二:排序法(Sorting)
核心直觉
先排序,让所有相同的数字相邻。这样只需处理一组组连续的重复数字:同一组的数字必须分别进入不同的行,按顺序从行0开始逐行放置即可。行数的需求天然等于最大频率,而每行内元素互不重复也自动得到保证。
算法步骤
- 对输入数组排序;
- 初始化空的二维数组
res; - 用双指针遍历有序数组:
i指向当前组的第一个元素,j从i开始向后扫描所有与nums[i]相同的元素;- 行号
r从0开始递增,把这组元素依次放入第0、第1、第2……行; - 若
r已经等于res的行数(即现有行不够用),先append一个新行;
- 处理完一组后令
i = j,跳到下一组不同数字; - 返回
res。
多语言实现
class Solution: def findMatrix(self, nums: List[int]) -> List[List[int]]: nums.sort() res = [] i = 0 while i < len(nums): j = i r = 0 while j < len(nums) and nums[i] == nums[j]: if r == len(res): res.append([]) res[r].append(nums[i]) r += 1 j += 1 i = j return respublic class Solution { public List<List<Integer>> findMatrix(int[] nums) { Arrays.sort(nums); List<List<Integer>> res = new ArrayList<>(); int i = 0; while (i < nums.length) { int j = i; int r = 0; while (j < nums.length && nums[i] == nums[j]) { if (r == res.size()) { res.add(new ArrayList<>()); } res.get(r).add(nums[i]); r++; j++; } i = j; } return res; } }class Solution { public: vector<vector<int>> findMatrix(vector<int>& nums) { sort(nums.begin(), nums.end()); vector<vector<int>> res; int i = 0; while (i < nums.size()) { int j = i, r = 0; while (j < nums.size() && nums[i] == nums[j]) { if (r == res.size()) { res.push_back({}); } res[r].push_back(nums[i]); r++; j++; } i = j; } return res; } };class Solution { /** * @param {number[]} nums * @return {number[][]} */ findMatrix(nums) { nums.sort((a, b) => a - b); const res = []; let i = 0; while (i < nums.length) { let j = i; let r = 0; while (j < nums.length && nums[i] === nums[j]) { if (r === res.length) { res.push([]); } res[r].push(nums[i]); r++; j++; } i = j; } return res; } }public class Solution { public IList<IList<int>> FindMatrix(int[] nums) { Array.Sort(nums); IList<IList<int>> res = new List<IList<int>>(); int i = 0; while (i < nums.Length) { int j = i; int r = 0; while (j < nums.Length && nums[i] == nums[j]) { if (r == res.Count) { res.Add(new List<int>()); } res[r].Add(nums[i]); r++; j++; } i = j; } return res; } }func findMatrix(nums []int) [][]int { sort.Ints(nums) res := [][]int{} i := 0 for i < len(nums) { j := i r := 0 for j < len(nums) && nums[i] == nums[j] { if r == len(res) { res = append(res, []int{}) } res[r] = append(res[r], nums[i]) r++ j++ } i = j } return res }class Solution { fun findMatrix(nums: IntArray): List<List<Int>> { nums.sort() val res = mutableListOf<MutableList<Int>>() var i = 0 while (i < nums.size) { var j = i var r = 0 while (j < nums.size && nums[i] == nums[j]) { if (r == res.size) { res.add(mutableListOf()) } res[r].add(nums[i]) r++ j++ } i = j } return res } }class Solution { func findMatrix(_ nums: [Int]) -> [[Int]] { let nums = nums.sorted() var res = [[Int]]() var i = 0 while i < nums.count { var j = i var r = 0 while j < nums.count && nums[i] == nums[j] { if r == res.count { res.append([]) } res[r].append(nums[i]) r += 1 j += 1 } i = j } return res } }impl Solution { pub fn find_matrix(nums: Vec<i32>) -> Vec<Vec<i32>> { let mut nums = nums; nums.sort(); let mut res: Vec<Vec<i32>> = Vec::new(); let mut i = 0; while i < nums.len() { let mut j = i; let mut r = 0; while j < nums.len() && nums[i] == nums[j] { if r == res.len() { res.push(Vec::new()); } res[r].push(nums[i]); r += 1; j += 1; } i = j; } res } }复杂度分析
- 时间复杂度:O(n log n),瓶颈在排序本身,分组与放置均为线性扫描;
- 空间复杂度:O(n),用于存放输出数组。
注意:Go 与 Rust 版本会原地修改传入的
nums(sort.Ints(nums)/nums.sort()),若调用方需要保留原数组,请先拷贝。
解法三:频率计数法(Frequency Count,最优解)
核心直觉
这是三种解法中最优雅的一种,也是仓库源码实际采用的方案:
一个元素第 k 次出现,就把它放进第 k 行(从 0 开始计数)。
用哈希表记录"每个数字到目前为止已被放置的次数":第 1 次出现放第 0 行,第 2 次出现放第 1 行,第 3 次出现放第 2 行…… 这样既不需要排序,也不需要任何线性搜索,直接通过哈希查询得到目标行号。
算法步骤
- 建立哈希表
count,记录每个数字已被放置的次数(初始为0); - 初始化空的二维数组
res; - 遍历
nums中的每个数字:- 取出
count[num],它就是该数字应放入的行号row; - 若
res当前行数恰好等于row,说明这一行还不存在,先追加一个新行; - 将数字放入
res[row]; - 将
count[num]加 1;
- 取出
- 返回
res。
多语言实现
class Solution: def findMatrix(self, nums: List[int]) -> List[List[int]]: count = defaultdict(int) res = [] for num in nums: row = count[num] if len(res) == row: res.append([]) res[row].append(num) count[num] += 1 return respublic class Solution { public List<List<Integer>> findMatrix(int[] nums) { Map<Integer, Integer> count = new HashMap<>(); List<List<Integer>> res = new ArrayList<>(); for (int num : nums) { int row = count.getOrDefault(num, 0); if (res.size() == row) { res.add(new ArrayList<>()); } res.get(row).add(num); count.put(num, row + 1); } return res; } }class Solution { public: vector<vector<int>> findMatrix(vector<int>& nums) { unordered_map<int, int> count; vector<vector<int>> res; for (int num : nums) { int row = count[num]; if (res.size() == row) { res.push_back({}); } res[row].push_back(num); count[num]++; } return res; } };class Solution { /** * @param {number[]} nums * @return {number[][]} */ findMatrix(nums) { const count = new Map(); const res = []; for (const num of nums) { const row = count.get(num) || 0; if (res.length === row) { res.push([]); } res[row].push(num); count.set(num, row + 1); } return res; } }public class Solution { public IList<IList<int>> FindMatrix(int[] nums) { Dictionary<int, int> count = new Dictionary<int, int>(); IList<IList<int>> res = new List<IList<int>>(); foreach (int num in nums) { int row = count.GetValueOrDefault(num, 0); if (res.Count == row) { res.Add(new List<int>()); } res[row].Add(num); count[num] = row + 1; } return res; } }func findMatrix(nums []int) [][]int { count := make(map[int]int) res := [][]int{} for _, num := range nums { row := count[num] if len(res) == row { res = append(res, []int{}) } res[row] = append(res[row], num) count[num]++ } return res }class Solution { fun findMatrix(nums: IntArray): List<List<Int>> { val count = mutableMapOf<Int, Int>() val res = mutableListOf<MutableList<Int>>() for (num in nums) { val row = count.getOrDefault(num, 0) if (res.size == row) { res.add(mutableListOf()) } res[row].add(num) count[num] = row + 1 } return res } }class Solution { func findMatrix(_ nums: [Int]) -> [[Int]] { var count = [Int: Int]() var res = [[Int]]() for num in nums { let row = count[num, default: 0] if res.count == row { res.append([]) } res[row].append(num) count[num] = row + 1 } return res } }impl Solution { pub fn find_matrix(nums: Vec<i32>) -> Vec<Vec<i32>> { let mut count: HashMap<i32, usize> = HashMap::new(); let mut res: Vec<Vec<i32>> = Vec::new(); for &num in &nums { let row = *count.get(&num).unwrap_or(&0); if res.len() == row { res.push(Vec::new()); } res[row].push(num); count.insert(num, row + 1); } res } }复杂度分析
- 时间复杂度:O(n),单次遍历,每次哈希表读写均为摊还 O(1);
- 空间复杂度:O(n),哈希表与输出数组各占 O(n)。
仓库源码印证
本仓库中该题的两种已提交实现都采用了频率计数法,与上文讲解完全一致:
- java/2610-convert-an-array-into-a-2d-array-with-conditions.java:
count.getOrDefault(n, 0)取出"已放置次数"作为行号,res.size() == row时按需新建行,放置后count.put(n, ... + 1)自增; - kotlin/2610-convert-an-array-into-a-2d-array-with-conditions.kt:使用
count[n] ?: 0与res.size == row实现同样的逻辑。
从这两份源码可以确认频率计数法是本仓库官方推荐的标准解:它同时避免了暴力法的重复线性搜索与排序法的 O(n log n) 额外代价,是面试中最值得优先写出的答案。
三种解法对比一览
| 解法 | 核心思路 | 时间复杂度 | 空间复杂度 | 是否修改原数组 | 适用场景 |
|---|---|---|---|---|---|
| 暴力法 | 每来一个数字,线性扫描找到第一个不含它的行 | O(n × m) | O(n) | 否 | 思路直观,适合先想清楚题意 |
| 排序法 | 排序后把相同数字的重复组依次分发到各行 | O(n log n) | O(n) | 是(Go/Rust 原地排序) | 擅长排序、双指针的练习场景 |
| 频率计数法 | 第 k 次出现放第 k 行,哈希表记录已放置次数 | O(n) | O(n) | 否 | 面试与提交的推荐首选 |
其中
m表示数组中出现频率最高元素的频率,n表示数组长度。
常见陷阱与易错点
陷阱一:把"行号"和"总频率"搞混
这是最容易出错的地方。决定元素放到哪一行的,是它到目前为止已被放置的次数,而不是它在整个数组中的总出现次数。
# 错误:使用总频率作为行号 row = total_count[num] # 正确:使用"到目前为止已放置的次数"作为行号 row = count[num] # 放置后再 count[num] += 1若误用总频率,会出现行号跳跃(例如总频率为 3 的元素被直接放到第 3 行,而中间行从未被使用),既浪费行数,也可能因为行不存在而越界。
陷阱二:忘记按需创建新行
在向res[row]写入前,必须先确认该行存在。频率计数法中,只有当len(res) == row时(即该数字第一次出现在新的"深度"上)才需要新建行;其他时候行早已存在。
# 错误:行不存在时直接写入,导致越界崩溃 res[row].append(num) # 正确:先创建行再写入 if len(res) == row: res.append([]) res[row].append(num)陷阱三:暴力法中的重复线性搜索
暴力法每插入一个元素都要用contains/find线性检查行内是否已有该数字,导致整体复杂度为 O(n × m)。它是可行的(本题n <= 200,即使全重复也不会超时),但正如前文所述,频率计数法通过"以哈希计数直接定位行号"彻底消除了这类重复搜索,把复杂度降为 O(n)。若面试中被追问"能否更快",应从这一点切入升级方案。
举一反三:同类型题目的迁移思路
"用频率/计数直接决定位置"是哈希表类问题的高频套路,本仓库中还有大量同构题目可以对照练习:
- top-k-elements-in-list.md:同样以频率为核心,但目标是选出前 k 高频元素,引入了堆/桶排序两种进阶手段;
- find-all-duplicates-in-an-array.md:把数组本身当作哈希表(下标映射),用标记法找出出现两次的元素,体现"计数 + 索引复用"的另一面;
- subarray-sum-equals-k.md:用前缀和哈希表把"子数组和等于 k"的计数问题降为 O(n),与本文"用哈希计数省去重复扫描"的思路一脉相承。
理解本文"计数即定位"的核心思想后,再遇到"按出现次数/频率做分发、分组、统计"的题目,都可以优先考虑用哈希表把重复的线性工作压缩到 O(1) 查询。
小结
- 暴力法是理解题意的最小起点,复杂度 O(n × m);
- 排序法利用"相同元素相邻"简化分组,复杂度 O(n log n),但会原地修改数组;
- 频率计数法以"第 k 次出现放第 k 行"为不变量,做到 O(n) 时间、O(n) 空间,是本仓库 java/2610 与 kotlin/2610 官方实现所采用的方案,也是面试中的首选答案。
掌握"用哈希计数直接定位目标位置"这一思想,你就能从容应对一类频率相关题目的最优解推导。
【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考