【算法習題】數組中未出現的最小正整數


題目:

給定一個無序整型數組arr,找到數組中未出現的最小正整數。要求時間復雜度為O(N)空間復雜度為O(1)

例如:

arr=[-1,2,3,4]。返回1。

arr=[1,2,3,4]。返回5。

 

=========================================================

分析:

這道題要理解最小正整數的意思,最小的正整數就是1,所以考察的方法就是在數組中找1,然后找2,依次找下去...。直到第一個沒有找到的數,這個數就是未出現的最小的正整數。但是這樣的時間復雜度很大,達到了O(n2)。

 

先看一個時空復雜度均為O(n)的方案,思路如下:

         新建一個和原數組大小一致的新數組,通過遍歷原數組將其中每個元素e(忽略掉小於1或大於數組長度的元素)填充到新數組中[e-1]位置上。之后遍歷新數組就可找到目標,這個遍歷可能會遇到兩種情況,一般情況下,上一步的操作總有被忽略的元素,每忽略一個數,新數組中就會少填充一個正整數,如{-1,1,2,5,6}>>{1,2,0,0,5},這種情況要找的數就是第一個值為0的元素的下標+1極端情況下,上一步的操作沒有被忽略的元素,如{3,2,1,5,4}>>{1,2,3,4,5},這種情況要找的數就是length+1;

         為什么開辟的新數組大小要和原數組大小一致?這是為了確保在極端情況下能夠容納下由原數組中元素組成的從1開始的最長連續整數序列。

         為什么要忽略掉大於數組長度的元素?這是因為如果存在這樣的數X,剩下的小於length個元素不可能組成1~length的連續整數序列,則X更不可能在連續序列中,就沒必要維護它了。

相應的代碼實現如下:

 1 @org.junit.Test
 2 public void test() {
 3     System.out.println("結果:" + func1(new int[] { -1, 5, 1, 6, 2 }));
 4     System.out.println("結果:" + func1(new int[] { 3, 2, 1, 5, 4 }));
 5 }/* out:
 6  * [-1, 5, 1, 6, 2] >> 
 7  * [1, 2, 0, 0, 5]
 8  * 結果:3
 9  * [3, 2, 1, 5, 4] >> 
10  * [1, 2, 3, 4, 5]
11  * 結果:6
12  */
13 
14 public int func1(int[] arr) {
15     int[] newArr = new int[arr.length];
16     for (int e : arr) {
17         if (e < 1 || e > arr.length) {
18             continue;
19         }
20         newArr[e - 1] = e;
21     }
22     System.out.println(Arrays.toString(arr) + " >> ");
23     System.out.println(Arrays.toString(newArr));
24 
25     for (int i = 0; i < newArr.length; i++) {
26         if (newArr[i] == 0) {
27             return i + 1;
28         }
29     }
30     return arr.length + 1;
31 }
View Code

 

再看改進方案,減小空間復雜度為O(1),代碼如下:

 1 @org.junit.Test
 2 public void test2() {
 3     System.out.println(funcFinal(new int[]{-1,5,1,6,2}));
 4     System.out.println(funcFinal(new int[]{3,2,1,5,4}));
 5 }/* out:
 6  * 原數組:[-1, 5, 1, 6, 2]
 7  * 處理后:[1, 2, 1, 6, 2]
 8  * 結果:3
 9  * 原數組:[3, 2, 1, 5, 4]
10  * 處理后:[1, 2, 3, 4, 5]
11  * 結果:6
12  */
13     
14 public int funcFinal(int[] arr) {
15     System.out.println("原數組:" + Arrays.toString(arr));
16     /* 
17      * right是一個邊界值,表示用數組中元素組成的從1開始的連續整數序列中可能的最大值(初始等於數組長度)。
18      * 處理數組過程中如果遇到比right大的數,就表示該數不合法,應該被丟掉(代碼中還處理了其它表示數不合法的情況)。
19      * >> 隨着數組元素被處理,每遇到一個不合法的元素,就應將right減1。
20      */
21     int right = arr.length;
22     /*
23      * 索引left(初始為0),left將數組分成兩部分。
24      * [0,left)是處理完成的部分,其中每個元素都滿足a[i]=i+1;
25      * [left,right]是待處理部分。
26      * >> 隨着數組元素被處理,left會逐漸向右移動。
27      */
28     int left = 0; 
29     
30     while (left + 1 <= right) { // 正在處理的元素的值(left+1) <= 邊界值
31         // 分支1、arr[left]在理想的位置
32         // 則處理完成部分長度加1,然后繼續處理未完成部分的下一個待處理元素
33         if (arr[left] == left + 1) { 
34             left++; 
35         } 
36         // 分支2、arr[left]是不合法的數據
37         // 則先將right減1,然后丟掉不合法的數並將待處理部分最后一個元素填充到left位置繼續處理
38         else if (arr[left] < left + 1 || arr[left] > right) {
39             right--;
40             arr[left] = arr[right];
41         } 
42         // 分支3、arr[left]合法,但是沒有在理想的位置上
43         // 則需要交換arr[left]與其理想位置上元素,然后繼續處理交換后left位置處的元素
44         // 求理想位置p的索引:p+1 = arr[left] >> p = arr[left]-1
45         else { 
46             // 如果要交換的兩個元素相同,也算當前處理的元素arr[left]不合法,進行與分支2一樣的處理
47             if(arr[left] == arr[arr[left] - 1]) {
48                 right--;
49                 arr[left] = arr[right];
50             } else {
51                 swap(arr, left, arr[left] - 1);
52             }
53         }
54     }
55     System.out.println("處理后:" + Arrays.toString(arr));
56     return left + 1;
57 }
58 private void swap(int[] a, int i, int j) {
59     int temp = a[i];
60     a[i] = a[j];
61     a[j] = temp;
62 }
View Code

 


  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM