)
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的整数数组nums1 n 2001 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 ListListInteger findMatrix(int[] nums) { ListListInteger 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: vectorvectorint findMatrix(vectorint nums) { vectorvectorint 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 IListIListint FindMatrix(int[] nums) { IListIListint res new ListIListint(); 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 Listint()); } 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): ListListInt { val res mutableListOfMutableListInt() 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: Veci32) - VecVeci32 { let mut res: VecVeci32 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 ListListInteger findMatrix(int[] nums) { Arrays.sort(nums); ListListInteger 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: vectorvectorint findMatrix(vectorint nums) { sort(nums.begin(), nums.end()); vectorvectorint 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 IListIListint FindMatrix(int[] nums) { Array.Sort(nums); IListIListint res new ListIListint(); 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 Listint()); } 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): ListListInt { nums.sort() val res mutableListOfMutableListInt() 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: Veci32) - VecVeci32 { let mut nums nums; nums.sort(); let mut res: VecVeci32 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 版本会原地修改传入的numssort.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 ListListInteger findMatrix(int[] nums) { MapInteger, Integer count new HashMap(); ListListInteger 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: vectorvectorint findMatrix(vectorint nums) { unordered_mapint, int count; vectorvectorint 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 IListIListint FindMatrix(int[] nums) { Dictionaryint, int count new Dictionaryint, int(); IListIListint res new ListIListint(); foreach (int num in nums) { int row count.GetValueOrDefault(num, 0); if (res.Count row) { res.Add(new Listint()); } 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): ListListInt { val count mutableMapOfInt, Int() val res mutableListOfMutableListInt() 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: Veci32) - VecVeci32 { let mut count: HashMapi32, usize HashMap::new(); let mut res: VecVeci32 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.javacount.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),仅供参考