这道算法题来自LintCode平台编号319,题目名为"方阵排队"。我们需要编写一个Java方法public int maxPeopleNumber(int[] height),其核心功能是根据给定的身高数组,计算出能够组成完美方阵的最大人数。
所谓"完美方阵",指的是一个k×k的方阵,其中第i行第j列的人的身高必须严格大于第(i-1)行第j列的人,同时严格大于第i行第(j-1)列的人。换句话说,方阵中每个人的身高都必须大于其上方和左侧的人。
这个问题可以转化为寻找数组中最长的满足特定条件的子序列。具体来说,我们需要找到数组中最长的子序列,使得这个子序列能够被排列成一个方阵,满足行列递增的条件。
经过分析,我们发现这个问题与经典的"最长递增子序列"(LIS)问题有相似之处,但增加了二维排列的约束条件。因此,我们需要在LIS算法的基础上进行扩展。
我们采用动态规划来解决这个问题,具体步骤如下:
直接按照上述思路实现的时间复杂度是O(n²),对于较大的输入可能不够高效。我们可以考虑以下优化:
java复制import java.util.Arrays;
public class Solution {
public int maxPeopleNumber(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
Arrays.sort(height);
int n = height.length;
int[] dp = new int[n];
Arrays.fill(dp, 1);
int maxLen = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (height[i] > height[j] && dp[i] < dp[j] + 1) {
dp[i] = dp[j] + 1;
if (dp[i] > maxLen) {
maxLen = dp[i];
}
}
}
}
return maxLen * maxLen;
}
}
java复制import java.util.Arrays;
public class Solution {
public int maxPeopleNumber(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
Arrays.sort(height);
int n = height.length;
int[] dp = new int[n];
int[] tails = new int[n];
int size = 0;
for (int num : height) {
int i = 0, j = size;
while (i != j) {
int m = (i + j) / 2;
if (tails[m] < num) {
i = m + 1;
} else {
j = m;
}
}
tails[i] = num;
if (i == size) {
size++;
}
}
return size * size;
}
}
这个优化版本使用了二分查找来维护一个tails数组,将时间复杂度从O(n²)降低到了O(n log n)。
当输入数组为空或null时,直接返回0,因为无法组成任何方阵。
如果数组中所有身高都相同,那么最大方阵只能是1×1,因为无法满足严格递增的条件。
在排序后,如果有多个相同身高的元素,我们需要确保它们不会出现在同一个方阵中。这是通过动态规划过程中严格的大于比较来保证的。
两个版本都需要O(n)的额外空间来存储dp数组或tails数组。
对于较大的输入(n > 10^4),必须使用优化版本才能获得较好的性能。在实际面试或竞赛中,建议先实现基础版本确保正确性,然后考虑优化。
java复制// 测试用例1:普通情况
int[] heights1 = {1, 2, 3, 4};
// 期望输出:4 (2×2方阵)
// 测试用例2:无法组成2×2方阵
int[] heights2 = {1, 1, 2, 3};
// 期望输出:1 (只能组成1×1方阵)
// 测试用例3:空输入
int[] heights3 = {};
// 期望输出:0
java复制// 测试用例4:单个元素
int[] heights4 = {5};
// 期望输出:1
// 测试用例5:所有元素相同
int[] heights5 = {2, 2, 2, 2};
// 期望输出:1
// 测试用例6:严格递增序列
int[] heights6 = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// 期望输出:9 (3×3方阵)
java复制// 测试用例7:大输入测试
int[] heights7 = new int[10000];
for (int i = 0; i < 10000; i++) {
heights7[i] = i + 1;
}
// 期望输出:10000 (100×100方阵)
在实际实现这道题目时,我最初尝试了直接寻找满足条件的子序列,但很快发现这样无法保证二维排列的约束条件。通过分析问题本质,意识到需要将问题转化为类似LIS的问题,但需要额外的排序预处理。
在优化过程中,发现直接应用标准的LIS优化技巧就能显著提升性能。特别需要注意的是,对于相同身高的处理必须非常小心,任何等于的情况都会破坏方阵的严格递增性质。
一个实用的调试技巧是:对于失败的测试用例,手工模拟算法执行过程,在纸上画出dp数组的变化,这样往往能快速定位问题所在。另外,编写完备的测试用例,特别是各种边界情况,对于确保算法正确性至关重要。