给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

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

示例 2:

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

以上是原题


OK,先找出题目中的重点要求:

  1、线性时间复杂度:要求我们的代码时间复杂度最高为O(n),不能有嵌套循环等。

  2、不使用额外空间:要求空间复杂度最高为O(1)。

除此之外,还有重要的信息:

  • 除了某个元素只出现一次以外,其余每个元素均出现两次。

  这个条件非常关键,一开始自己审题不清楚没注意到均出现两次这个关键点,按照其他元素出现多次的情况处理了,这样导致思路受限很多。

开始解题:

方法一(比较法):

  思路:先对数组进行排序,然后对 nums[i] 和 nums[i + 1]进行比较,如相等,i += 2,继续下一组比较,直到取到不相等的一组。

  注意:首先这个数组的长度肯定是奇数(目标数字只出现一次,其他所有数字出现两次),所以如果上述步骤没有找到不相等的一组数,那么肯定是数组的最后一个数字是单独出现的。

  代码如下:

     public static int singleNumber(int[] nums) {
Arrays.sort(nums); // 排序数组
for (int i = 0; i < nums.length - 1; i += 2) {
// 找到不相等的一组,直接返回
if (nums[i] != nums[i + 1]) {
return nums[i];
}
}
// 如果没有找到不相等的一组数据,直接返回数组的最后一个数字
return nums[nums.length - 1];
}

方法二(去重法):

  思路:利用HashSet的特性,删除重复的数组元素,最后剩下一个单独的元素,返回即可。

  直接上代码:

     public static int singleNumber(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
if (!set.add(nums[i])) { // add成功返回true,如果set中已有相同数字,则add方法会返回false
set.remove(nums[i]); // 删除重复出现的数字
}
}
return set.iterator().next(); }

方法三(求差法):

  思路:先对数组排序,显而易见的,单独出现一次的数据必然是出现在数组下标为偶数的位置(下标从0开始),那么所有奇数下标的元素之和减去偶数下标的元素之和,就是需要求得的结果。

  代码如下:

     public static int singleNumber(int[] nums) {
int num = 0;
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 偶数下标位置 num += nums[i],奇数下标位置 num -= nums[i]
num = i % 2 == 0 ? num + nums[i] : num - nums[i];
}
return num;
}

方法四(异或法)

  思路:根据异或运算的特点,相同的数字经过异或运算后结果为0,除单独出现一次的数字外,其他数字都是出现两次的,那么这些数字经过异或运算后结果一定是0。而任何数字与0进行异或运算都是该数字本身。所以对数组所有元素进行异或运算,运算结果就是题目的答案。

  上代码:

     public static int singleNumber(int[] nums) {
int num = 0;
for (int i = 0; i < nums.length; i++) {
num = num ^ nums[i];
}
return num;
}

总结:

  其实严格来讲,只有第四种方式是题目想要的解法,其他三种方法都是有瑕疵的。

  方法一、方法三都用到了Arrays.sort(int[] a)的方法,我们先来看JDK提供的数组排序方法:

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

  调用了DualPivotQuicksort类的静态方法:

     /**
* Sorts the specified range of the array using the given
* workspace array slice if possible for merging
*
* @param a the array to be sorted
* @param left the index of the first element, inclusive, to be sorted
* @param right the index of the last element, inclusive, to be sorted
* @param work a workspace array (slice)
* @param workBase origin of usable space in work array
* @param workLen usable size of work array
*/
static void sort(int[] a, int left, int right,
int[] work, int workBase, int workLen) {
// Use Quicksort on small arrays
if (right - left < QUICKSORT_THRESHOLD) {
sort(a, left, right, true);
return;
} /*
* Index run[i] is the start of i-th run
* (ascending or descending sequence).
*/
int[] run = new int[MAX_RUN_COUNT + 1];
int count = 0; run[0] = left; // Check if the array is nearly sorted
for (int k = left; k < right; run[count] = k) {
if (a[k] < a[k + 1]) { // ascending
while (++k <= right && a[k - 1] <= a[k]);
} else if (a[k] > a[k + 1]) { // descending
while (++k <= right && a[k - 1] >= a[k]);
for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) {
int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
}
} else { // equal
for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {
if (--m == 0) {
sort(a, left, right, true);
return;
}
}
} /*
* The array is not highly structured,
* use Quicksort instead of merge sort.
*/
if (++count == MAX_RUN_COUNT) {
sort(a, left, right, true);
return;
}
} // Check special cases
// Implementation note: variable "right" is increased by 1.
if (run[count] == right++) { // The last run contains one element
run[++count] = right;
} else if (count == 1) { // The array is already sorted
return;
} // Determine alternation base for merge
byte odd = 0;
for (int n = 1; (n <<= 1) < count; odd ^= 1); // Use or create temporary array b for merging
int[] b; // temp array; alternates with a
int ao, bo; // array offsets from 'left'
int blen = right - left; // space needed for b
if (work == null || workLen < blen || workBase + blen > work.length) {
work = new int[blen];
workBase = 0;
}
if (odd == 0) {
System.arraycopy(a, left, work, workBase, blen);
b = a;
bo = 0;
a = work;
ao = workBase - left;
} else {
b = work;
ao = 0;
bo = workBase - left;
} // Merging
for (int last; count > 1; count = last) {
for (int k = (last = 0) + 2; k <= count; k += 2) {
int hi = run[k], mi = run[k - 1];
for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) {
if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) {
b[i + bo] = a[p++ + ao];
} else {
b[i + bo] = a[q++ + ao];
}
}
run[++last] = hi;
}
if ((count & 1) != 0) {
for (int i = right, lo = run[count - 1]; --i >= lo;
b[i + bo] = a[i + ao]
);
run[++last] = right;
}
int[] t = a; a = b; b = t;
int o = ao; ao = bo; bo = o;
}
}

  代码较长,想看的同学点开看一下,可以发现这个方法的时间复杂度是O(n3),不符合题目的要求线性时间复杂度。

  方法二呢,是利用了HashSet集合不可重复的特性,同样我们来看HashSet的add方法:

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

  其实HashSet的底层是通过HashMap来实现的,HashSet中的元素都是HashMap中的key,再来看HashMap的put方法:

     /**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} /**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

  请注意:上面 putVal 方法中,调用的 resize() 、 putTreeVal() 等方法本身也是O(n2)的时间复杂度。不符合题目要求的线性时间复杂度

  所以严格来说,只有第四种方式符合题目要求,但我们拓宽思路,能多用几种还算不错的方式实现需求,也是有百利而无一害的,大家也不必非要钻这个牛角尖。

  另外,从效率上来讲,第四种效率是最高的,经过测试高出前三种方式1-2个数量级。只是在普通的业务代码中,用到异或运算的地方并不多,不太容易想到这种方式,这个就考验大家的基础功底了。

  

只出现一次的数字 [ LeetCode ]的更多相关文章

  1. 136.只出现一次的数字 leetcode ^运算符 JavaScript解法

    leetcode上的一道题简单题 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用额外空间 ...

  2. Leetcode 137. 只出现一次的数字 II - 题解

    Leetcode 137. 只出现一次的数字 II - 题解 137. Single Number II 在线提交: https://leetcode.com/problems/single-numb ...

  3. 【LeetCode题解】136_只出现一次的数字

    目录 [LeetCode题解]136_只出现一次的数字 描述 方法一:列表操作 思路 Java 实现 Python 实现 方法二:哈希表 思路 Java 实现 Python 实现 方法三:数学运算 思 ...

  4. LeetCode 260. Single Number III(只出现一次的数字 III)

    LeetCode 260. Single Number III(只出现一次的数字 III)

  5. LeetCode 137. Single Number II(只出现一次的数字 II)

    LeetCode 137. Single Number II(只出现一次的数字 II)

  6. LeetCode 136. Single Number(只出现一次的数字)

    LeetCode 136. Single Number(只出现一次的数字)

  7. 【Leetcode】【简单】【136. 只出现一次的数字】【JavaScript】

    题目描述 136. 只出现一次的数字 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用额外 ...

  8. LeetCode初级算法--数组01:只出现一次的数字

    LeetCode初级算法--数组01:只出现一次的数字 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn. ...

  9. LeetCode:137. 只出现一次的数字 II

    LeetCode:137. 只出现一次的数字 II 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. ...

随机推荐

  1. Python基础灬文件常用操作

    文件常用操作 文件内建函数和方法 open() :打开文件 read():输入 readline():输入一行 seek():文件内移动 write():输出 close():关闭文件 写文件writ ...

  2. 译 - 高可用的mesos计算框架设计

    原文地址 http://mesos.apache.org/documentation/latest/high-availability-framework-guide/ 阅读建议:有写过或者看过Mes ...

  3. Windows下使用7-zip命令自动备份文件

    在上一篇博文中,介绍了使用WinRAR自动备份文件,后来改用了腾讯云服务器,上面默认没有安装WinRAR,只有7-zip,又不想在服务器上安装许多软件,就查了下7-zip的命令,贴出来备忘~ 系统环境 ...

  4. 作业 20181030-3互评Alpha版本

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2323 组名:可以低头,但没必要 组长:付佳 组员:张俊余  李文涛  孙 ...

  5. 欢迎来怼—第二次Scrum会议

    一.小组信息 队名:欢迎来怼小组成员队长:田继平成员:李圆圆,葛美义,王伟东,姜珊,邵朔,冉华小组照片 二.开会信息 时间:2017/10/14 18:30~18:47,总计17min.地点:东北师范 ...

  6. 《C》VS控制台应用

    源(c)文件:主要是源码,包括程序入口,函数的实现 头(h)文件:主要是定义的函数声明 资源(rc)文件:程序中用到的辅助资源,比如位图,图标资源 解决VS2015安装后stdio.h ucrtd.l ...

  7. Python数据挖掘学习路程--起步

    一.首先第一步我去了解了Python开发环境:Python(程序运行基础的解释器)+第三方类库(功能扩展)+编辑器(提高代码编辑效率) 编辑器有:Pycharm.Spyder.jupyter note ...

  8. oracle执行完shutdown immediate后登陆不上了怎么办

    在sqlplus 里登录后使用shutdown immediate 关闭数据库后若没有使用startup重启数据库就退出窗口则会出现下一次重启sqlplus窗口时无法登录的现象,解决方法如下 一.启动 ...

  9. WPF/MVVM快速指引

    简介 最近微软推出了UWA,又是一波新的C#+xaml学习热.好多小伙伴都对MVVM感觉很好奇,但是有些地方也有点难以理解.特意写了这边文章,希望对你有帮助. 这边文章会很长,所以我会用几个例子的形式 ...

  10. 使用docker国内镜像解决方案

    1:蜂巢镜像 https://c.163yun.com/hub#/m/library/ 例如: docker pull hub.c.163.com/library/nginx:1.8 再次执行dock ...