劍指offer第二版面試題3:陣列中的重複數字(java)
面試題3:陣列中的重複數字
題目1:在一個長度為n的數組裡的所有數字都在0到n-1的範圍內。陣列中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複的次數。請找出陣列中任意一個重複的數字。例如如果輸入長度為7的陣列{2,3,1,0,2,5,3},那麼對應的輸出是重複的數字2或者3。
-
方法一:因為所有數字都在0-n-1,將陣列排序後遍歷一遍就能知道是哪個數字重複了。陣列排序時間複雜度為O(nlogn)。
-
方法二:將該陣列遍歷一遍存入雜湊表中,在遍歷元素時判斷,如果雜湊表中已經存在該元素,則該元素重複,如果不存在該元素,將該元素新增到集合中去。因為雜湊表的判斷是O(1)的,因此時間複雜度為O(n)(遍歷陣列),但是提升時間的代價是一個O(n)的雜湊表。
-
方法三:因為範圍在0到n-1的範圍內,因此如果將每個位置上放置對應的腳標上的話,如果該腳標上的元素和要放入的元素相同,則有重複數字。因為每個元素至多交換兩次就能到達應有的位置上,因此時間複雜度為O(n),空間複雜度為O(1)(因為不需要額外的空間)。
方法三的實現:(與書上大致一樣,但是缺少對輸入內容的判斷,答案採取的是for迴圈,裡面交換用的是while迴圈)
package com.ldl.forbuilding.controller; public class HugerSingletonTest { public static void main(String[] args) { int[] arr = {2,3,1,0,2,5,3}; System.out.println(findnumber(arr));; } public static int findnumber(int[] arr){ int i = 0; while (i<arr.length){ if (arr[i]!=i){ if(arr[arr[i]]==arr[i]){ return arr[i]; } int temp = arr[arr[i]]; arr[arr[i]] = arr[i]; arr[i] = temp; }else { i++; } } return 0; } }
答案:
public class Solution { public static boolean duplicate(int[] arr) { // 入參檢查 if (arr == null || arr.length == 0) { return false; } for (int i = 0; i < arr.length; i++) { if (arr[i] < 0 || arr[i] >= arr.length) { return false; } } // 遍歷陣列 for (int i = 0; i < arr.length; i++) { while (arr[i] != i) { if (arr[i] == arr[arr[i]]) { System.out.println(arr[i]); return true; } // 替換 int temp = arr[i]; arr[i] = arr[temp]; arr[temp] = temp; } } return false; } public static void main(String[] args) { int[] arr = {1, 1, 2, 4, 2, 5, 6}; boolean result = duplicate(arr); System.out.println(result); } }
題目2:在一個長度為n+1的數組裡的所有數字都在1~n的範圍內,所以陣列中至少有一個數字是重複的。請找出陣列中任意一個重複的數字,但是不能修改輸入的陣列。例如,如果輸入長度為8的陣列{2,3,5,4,3,2,6,7},那麼對應的輸出是重複的數字2或者3。
-
方法一:新建一個n+1大小的陣列,遍歷輸入陣列,如果該元素大小對應在新建陣列腳標位置上為空元素,則將該元素放入新陣列中,以此類推,當遍歷到的元素對應的新建陣列腳標上的元素不為空,說明出現重複元素。時間O(n),空間O(n)。
-
手寫
package com.ldl.forbuilding.controller; public class HugerSingletonTest { public static void main(String[] args) { int[] arr ={2,3,5,4,3,2,6,7}; System.out.println(findnumber(arr));; } public static boolean findnumber(int[] arr){ int[] temp = new int[arr.length]; for (int i = 0; i < arr.length; i++) { if(arr[i]==temp[arr[i]]){ System.out.println(arr[i]); return true; }else { temp[arr[i]]=arr[i]; } } return false; } }
-
答案
public class Solution { public static int getDuplication(int[] arr) { // 入參檢查 if (arr == null || arr.length == 0) { return -1; } for (int i = 0; i < arr.length; i++) { if (arr[i] < 1 || arr[i] >= arr.length) { return -1; } } int[] tempArr = new int[arr.length]; for (int i = 0; i < arr.length; i++) { if (arr[i] == tempArr[arr[i]]) { return arr[i]; } tempArr[arr[i]] = arr[i]; } return -1; } public static void main(String[] args) { int[] numbers = {2, 1, 5, 4, 3, 2, 6, 7}; System.out.println(getDuplication(numbers)); } }
假如面試官要求空間複雜度為O(1)時,我們要用到方法二。
-
-
方法二:通過二分查詢(折半查詢)的方式,判斷1m中的元素出現的次數,如果小於m次,則重複元素在m+1n中。將m+1~n再進行對半拆分,判斷元素出現次數。 舉例說明:長度為8的陣列,元素範圍為17。那麼14出現次數為5次(通過直接遍歷陣列,判斷每個元素是不是大於等於1且小於等於4),大於4,因此14中一定有重複元素。再拆分,12出現次數為2次(注意,這個時候2也是重複元素,但是該方法判斷不出來!),34出現了3次。因此34中一定有重複元素。再拆分,3出現了2次,4出現了一次,找到重複元素3。
-
手寫(看答案寫出來的。。。二分查詢忘乾淨了)
package com.ldl.forbuilding.controller; public class HugerSingletonTest { public static void main(String[] args) { int[] arr ={2,3,5,4,3,2,6,7}; System.out.println(findnumber(arr));; } public static int findnumber(int[] arr){ // 入參檢查 if (arr == null || arr.length == 0) { return -1; } for (int i = 0; i < arr.length; i++) { if (arr[i] < 1 || arr[i] >= arr.length) { return -1; } } int left = 1; int right = arr.length-1; while (left<=right){ int mid = (right-left)/2+left; int count = findcount(arr,left,mid); if(left==right){ if(count>1){ return left; }else { break; } } if(count>mid-left+1){ right=mid; }else { left=mid+1; } } return -1; } public static int findcount(int[] arr,int left,int right){ int count =0 ; for (int i = 0; i <arr.length ; i++) { if(arr[i]>=left&&arr[i]<=right){ ++count; } } return count; } }
二分查詢O(logn),遍歷長度為n的陣列O(n),因此輸入長度為n的陣列,那麼findcount函式將被呼叫O(logn)次,每次時間為O(n),因此總時間複雜度為O(nlogn),空間複雜度為O(1)。與之前的時間和空間都為O(n)相比是時間換空間。
-