最長連続シーケンスのトップ 3
ソートされていない整数配列 nums を指定して、連続する数値の最長シーケンスの長さを見つけます (シーケンス要素は元の配列で連続している必要はありません)。 * * この問題を解決するには、時間計算量が O(n) のアルゴリズムを設計して実装してください。 * * * * 例 1: * * 入力: nums = [100,4,200,1,3,2] * 出力: 4 説明: 最長の連続する数値シーケンスは [1, 2, 3, 4] です。その長さは 4 です。
配列内の各数値 xxx を列挙し、それを開始点として考慮し、常に x+1,x+2,⋯x+1, x+2, \cdotsx+1,x+2,⋯ との一致を試みることを検討します。それは存在します。最長一致が x+yx+yx+y であると仮定すると、xxx から始まる最長の連続シーケンスは x,x+1,x+2,⋯ ,x+yx, x+1, x+2, \ となります。 cdots、x+yx,x+1,x+2,⋯,x+y、その長さは y+1y+1y+1 であるため、答えの列挙と更新を続けることができます。
照合プロセスの場合、配列を O(n)O(n)O(n) で走査して数値が存在するかどうかを確認する総当りの方法ですが、実際には、より効率的な方法は、ハッシュ テーブルを使用して数値を格納することです。配列を次のようにチェックします。数値が存在するかどうかは、O(1)O(1)O(1) の時間計算量に最適化できます。
このため、アルゴリズムの時間計算量は最悪の場合でも O(n2)O(n^2)O(n 2 ) に達します (つまり、外側の層は O(n)O(
n
を列挙する必要があります) )O(n) 回、内側の層には激しいマッチングが必要です (O(n)O(n)O(n) 回)。これでは質問の要件を満たすことができません。しかし、このプロセスを注意深く分析すると、不必要な列挙が多数実行されていることがわかります。 cdots 、 x+yx,x+1,x+2,⋯,x+y の連続シーケンスですが、x+1x+1x+1、x+2x+2x+2 または x+yx+yx+ から開始します。 y で照合を開始すると、得られる結果は列挙 xxx から始まる答えよりも優れていることは間違いないため、外側のループでこの状況が発生した場合はスキップできます。
では、スキップするかどうかはどのように判断すればよいのでしょうか?列挙したい数値 xxx には、配列内に先行する数値 x−1x-1x−1 が存在してはなりません。そうでない場合は、上記の分析に従って、x−1x-1x−1 から照合しようとします。ハッシュ テーブルに x-1x-1x-1 が存在する場合、それをスキップする必要があるかどうかを判断できます。
ハッシュテーブルを通じて実装
public class Top3 {
public static void main(String[] args) {
int[] num = {100,4,200,1,3,2};
int longgest = getLongestNum(num);
System.out.println(longgest);
}
/**
* 由于我们要枚举的数 xxx 一定是在数组中不存在前驱数 x−1 的,不然按照上面的分析我们会从 x−1x-1x−1 开始尝试匹配,因此我们每次在哈希表中检查是否存在 x−1x-1x−1 即能判断是否需要跳过了。
*
* @param intData
* @return
*/
private static int getLongestNum(int[] intData) {
Set<Integer> intSet = new HashSet();
for(int i:intData){
intSet.add(i);
}
int longgest = 0;
for(int j:intSet)
{
if(!intSet.contains(j-1)){
int curentData = j;
int longgetIndex = 1;
while (intSet.contains(curentData+1)){
longgetIndex++;
curentData++;
}
longgest = Math.max(longgest,longgetIndex);
}
}
return longgest;
}
}
top4 はゼロを移動します (デュアル ポインターの実装)
/** * 配列 nums を指定して、ゼロ以外の要素の相対的な順序を維持しながら、すべての 0 を配列の末尾に移動する関数を作成します。 * * 配列はコピーせずにその場で操作する必要があることに注意してください。 */
public class Top4 {
private static void moveData(int[] num){
/*我们创建两个指针 i 和 j,第一次遍历的时候指针 j 用来记录当前有多少 非0 元素。即遍历的时候每遇到一个 非0 元素就将其往数组左边挪,第一次遍历完后,j 指针的下标就指向了最后一个 非0 元素下标。
第二次遍历的时候,起始位置就从 j 开始到结束,将剩下的这段区域内的元素全部置为 0。
*/
if(num.length==0)
{
return;
}
int j = 0;
for(int i=0;i<num.length;i++){
if(num[i]!=0){
num[j]=num[i];
j++;
}
}
for(int i =j;i<num.length;i++){
num[i] = 0;
}
}
public static void main(String[] args) {
int[] num = {1,0,2,3,4,0,5,9,0,7,8,0};
moveData(num);
for(int i = 0;i<num.length;i++){
System.out.println(num[i]);
}
System.out.println("---------");
int[] num2 = {5,0,2,3,4,0,5,9,0,7,8,0};
moveDataTwo(num2);
for(int i = 0;i<num2.length;i++){
System.out.println(num2[i]);
}
}
private static void moveDataTwo(int[] num){
/*这里参考了快速排序的思想,快速排序首先要确定一个待分割的元素做中间点 x,然后把所有小于等于 x 的元素放到 x 的左边,大于 x 的元素放到其右边。
这里我们可以用 0 当做这个中间点,把不等于 0(注意题目没说不能有负数)的放到中间点的左边,等于 0 的放到其右边。
这的中间点就是 0 本身,所以实现起来比快速排序简单很多,我们使用两个指针 i 和 j,只要 nums[i]!=0,我们就交换 nums[i] 和 nums[j]
*/
if(num.length==0){
return;
}
int j=0;
for(int i=0;i<num.length;i++){
if(num[i]!=0){
int temp = num[i];
num[i] = num[j];
num[j++] = temp;
}
}
}
}