只出現一次的數字 [ LeetCode ]

給定一個非空整數數組,除了某個元素只出現一次之外,其他每一個元素均出現兩次。找出那個只出現了一次的元素。算法

說明:數組

你的算法應該具備線性時間複雜度。 你能夠不使用額外空間來實現嗎?app

示例 1:ide

輸入: [2,2,1]
輸出: 1

 

示例 2:測試

輸入: [4,1,2,1,2]
輸出: 4

以上是原題ui


 

OK,先找出題目中的重點要求:this

  一、線性時間複雜度:要求咱們的代碼時間複雜度最高爲O(n),不能有嵌套循環等。spa

  二、不使用額外空間:要求空間複雜度最高爲O(1)。code

除此以外,還有重要的信息:orm

  • 除了某個元素只出現一次之外,其他每一個元素均出現兩次

  這個條件很是關鍵,一開始本身審題不清楚沒注意到均出現兩次這個關鍵點,按照其餘元素出現屢次的狀況處理了,這樣致使思路受限不少。

開始解題:

方法一(比較法):

  思路:先對數組進行排序,而後對 nums[i] 和 nums[i + 1]進行比較,如相等,i += 2,繼續下一組比較,直到取到不相等的一組。

  注意:首先這個數組的長度確定是奇數(目標數字只出現一次,其餘全部數字出現兩次),因此若是上述步驟沒有找到不相等的一組數,那麼確定是數組的最後一個數字是單獨出現的。

  代碼以下:

 1     public static int singleNumber(int[] nums) {
 2         Arrays.sort(nums);  // 排序數組
 3         for (int i = 0; i < nums.length - 1; i += 2) {
 4             // 找到不相等的一組,直接返回
 5             if (nums[i] != nums[i + 1]) {
 6                 return nums[i];
 7             }
 8         }
 9         // 若是沒有找到不相等的一組數據,直接返回數組的最後一個數字
10         return nums[nums.length - 1];
11     }

 

 

方法二(去重法):

  思路:利用HashSet的特性,刪除重複的數組元素,最後剩下一個單獨的元素,返回便可。

  直接上代碼:

1     public static int singleNumber(int[] nums) {
2         Set<Integer> set = new HashSet<>();
3         for (int i = 0; i < nums.length; i++) {
4             if (!set.add(nums[i])) { // add成功返回true,若是set中已有相同數字,則add方法會返回false
5                 set.remove(nums[i]); // 刪除重複出現的數字
6             }
7         }
8         return set.iterator().next();9     }

 

方法三(求差法):

  思路:先對數組排序,顯而易見的,單獨出現一次的數據必然是出如今數組下標爲偶數的位置(下標從0開始),那麼全部奇數下標的元素之和減去偶數下標的元素之和,就是須要求得的結果。

  代碼以下:

1     public static int singleNumber(int[] nums) {
2         int num = 0;
3         Arrays.sort(nums);
4         for (int i = 0; i < nums.length; i++) {
5             // 偶數下標位置 num += nums[i],奇數下標位置 num -= nums[i]
6             num = i % 2 == 0 ? num + nums[i] : num - nums[i];
7         }
8         return num;
9     }

 

方法四(異或法)

  思路:根據異或運算的特色,相同的數字通過異或運算後結果爲0,除單獨出現一次的數字外,其餘數字都是出現兩次的,那麼這些數字通過異或運算後結果必定是0。而任何數字與0進行異或運算都是該數字自己。因此對數組全部元素進行異或運算,運算結果就是題目的答案。

  上代碼:

1     public static int singleNumber(int[] nums) {
2         int num = 0;
3         for (int i = 0; i < nums.length; i++) {
4             num = num ^ nums[i];
5         }
6         return num;
7     }

 

總結:

  其實嚴格來說,只有第四種方式是題目想要的解法,其餘三種方法都是有瑕疵的。

  方法1、方法三都用到了Arrays.sort(int[] a)的方法,咱們先來看JDK提供的數組排序方法:

1     public static void sort(int[] a) {
2         DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
3     }

  調用了DualPivotQuicksort類的靜態方法:

  1     /**
  2      * Sorts the specified range of the array using the given
  3      * workspace array slice if possible for merging
  4      *
  5      * @param a the array to be sorted
  6      * @param left the index of the first element, inclusive, to be sorted
  7      * @param right the index of the last element, inclusive, to be sorted
  8      * @param work a workspace array (slice)
  9      * @param workBase origin of usable space in work array
 10      * @param workLen usable size of work array
 11      */
 12     static void sort(int[] a, int left, int right,
 13                      int[] work, int workBase, int workLen) {
 14         // Use Quicksort on small arrays
 15         if (right - left < QUICKSORT_THRESHOLD) {
 16             sort(a, left, right, true);
 17             return;
 18         }
 19 
 20         /*
 21          * Index run[i] is the start of i-th run
 22          * (ascending or descending sequence).
 23          */
 24         int[] run = new int[MAX_RUN_COUNT + 1];
 25         int count = 0; run[0] = left;
 26 
 27         // Check if the array is nearly sorted
 28         for (int k = left; k < right; run[count] = k) {
 29             if (a[k] < a[k + 1]) { // ascending
 30                 while (++k <= right && a[k - 1] <= a[k]);
 31             } else if (a[k] > a[k + 1]) { // descending
 32                 while (++k <= right && a[k - 1] >= a[k]);
 33                 for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) {
 34                     int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
 35                 }
 36             } else { // equal
 37                 for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {
 38                     if (--m == 0) {
 39                         sort(a, left, right, true);
 40                         return;
 41                     }
 42                 }
 43             }
 44 
 45             /*
 46              * The array is not highly structured,
 47              * use Quicksort instead of merge sort.
 48              */
 49             if (++count == MAX_RUN_COUNT) {
 50                 sort(a, left, right, true);
 51                 return;
 52             }
 53         }
 54 
 55         // Check special cases
 56         // Implementation note: variable "right" is increased by 1.
 57         if (run[count] == right++) { // The last run contains one element
 58             run[++count] = right;
 59         } else if (count == 1) { // The array is already sorted
 60             return;
 61         }
 62 
 63         // Determine alternation base for merge
 64         byte odd = 0;
 65         for (int n = 1; (n <<= 1) < count; odd ^= 1);
 66 
 67         // Use or create temporary array b for merging
 68         int[] b;                 // temp array; alternates with a
 69         int ao, bo;              // array offsets from 'left'
 70         int blen = right - left; // space needed for b
 71         if (work == null || workLen < blen || workBase + blen > work.length) {
 72             work = new int[blen];
 73             workBase = 0;
 74         }
 75         if (odd == 0) {
 76             System.arraycopy(a, left, work, workBase, blen);
 77             b = a;
 78             bo = 0;
 79             a = work;
 80             ao = workBase - left;
 81         } else {
 82             b = work;
 83             ao = 0;
 84             bo = workBase - left;
 85         }
 86 
 87         // Merging
 88         for (int last; count > 1; count = last) {
 89             for (int k = (last = 0) + 2; k <= count; k += 2) {
 90                 int hi = run[k], mi = run[k - 1];
 91                 for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) {
 92                     if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) {
 93                         b[i + bo] = a[p++ + ao];
 94                     } else {
 95                         b[i + bo] = a[q++ + ao];
 96                     }
 97                 }
 98                 run[++last] = hi;
 99             }
100             if ((count & 1) != 0) {
101                 for (int i = right, lo = run[count - 1]; --i >= lo;
102                     b[i + bo] = a[i + ao]
103                 );
104                 run[++last] = right;
105             }
106             int[] t = a; a = b; b = t;
107             int o = ao; ao = bo; bo = o;
108         }
109     }
View Code

  代碼較長,想看的同窗點開看一下,能夠發現這個方法的時間複雜度是O(n3),不符合題目的要求線性時間複雜度

  方法二呢,是利用了HashSet集合不可重複的特性,一樣咱們來看HashSet的add方法:

 1     /**
 2      * Adds the specified element to this set if it is not already present.
 3      * More formally, adds the specified element <tt>e</tt> to this set if
 4      * this set contains no element <tt>e2</tt> such that
 5      * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
 6      * If this set already contains the element, the call leaves the set
 7      * unchanged and returns <tt>false</tt>.
 8      *
 9      * @param e element to be added to this set
10      * @return <tt>true</tt> if this set did not already contain the specified
11      * element
12      */
13     public boolean add(E e) {
14         return map.put(e, PRESENT)==null;
15     }

 

  其實HashSet的底層是經過HashMap來實現的,HashSet中的元素都是HashMap中的key,再來看HashMap的put方法:

 1     /**
 2      * Associates the specified value with the specified key in this map.
 3      * If the map previously contained a mapping for the key, the old
 4      * value is replaced.
 5      *
 6      * @param key key with which the specified value is to be associated
 7      * @param value value to be associated with the specified key
 8      * @return the previous value associated with <tt>key</tt>, or
 9      *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
10      *         (A <tt>null</tt> return can also indicate that the map
11      *         previously associated <tt>null</tt> with <tt>key</tt>.)
12      */
13     public V put(K key, V value) {
14         return putVal(hash(key), key, value, false, true);
15     }
16 
17     /**
18      * Implements Map.put and related methods
19      *
20      * @param hash hash for key
21      * @param key the key
22      * @param value the value to put
23      * @param onlyIfAbsent if true, don't change existing value
24      * @param evict if false, the table is in creation mode.
25      * @return previous value, or null if none
26      */
27     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
28                    boolean evict) {
29         Node<K,V>[] tab; Node<K,V> p; int n, i;
30         if ((tab = table) == null || (n = tab.length) == 0)
31             n = (tab = resize()).length;
32         if ((p = tab[i = (n - 1) & hash]) == null)
33             tab[i] = newNode(hash, key, value, null);
34         else {
35             Node<K,V> e; K k;
36             if (p.hash == hash &&
37                 ((k = p.key) == key || (key != null && key.equals(k))))
38                 e = p;
39             else if (p instanceof TreeNode)
40                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
41             else {
42                 for (int binCount = 0; ; ++binCount) {
43                     if ((e = p.next) == null) {
44                         p.next = newNode(hash, key, value, null);
45                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
46                             treeifyBin(tab, hash);
47                         break;
48                     }
49                     if (e.hash == hash &&
50                         ((k = e.key) == key || (key != null && key.equals(k))))
51                         break;
52                     p = e;
53                 }
54             }
55             if (e != null) { // existing mapping for key
56                 V oldValue = e.value;
57                 if (!onlyIfAbsent || oldValue == null)
58                     e.value = value;
59                 afterNodeAccess(e);
60                 return oldValue;
61             }
62         }
63         ++modCount;
64         if (++size > threshold)
65             resize();
66         afterNodeInsertion(evict);
67         return null;
68     }

  請注意:上面 putVal 方法中,調用的 resize() 、 putTreeVal() 等方法自己也是O(n2)的時間複雜度。不符合題目要求的線性時間複雜度

  因此嚴格來講,只有第四種方式符合題目要求,但咱們拓寬思路,能多用幾種還算不錯的方式實現需求,也是有百利而無一害的,你們也沒必要非要鑽這個牛角尖。

  另外,從效率上來說,第四種效率是最高的,通過測試高出前三種方式1-2個數量級。只是在普通的業務代碼中,用到異或運算的地方並很少,不太容易想到這種方式,這個就考驗你們的基礎功底了。

相關文章
相關標籤/搜索