目录
实现思路
代码展示
代码讲解
实现思路
思路一
思路二
代码展示
两元素之和实现思路
数组中两元素之和(简称两数之和)到底讲的是什么东西呢?它讲的是,在一个数组中,寻找两个元素的和,该和等于目标数字,并返回两元素索引。比如数组{1,4,2,3},我们的目标数字是7(寻找7)。那很显然,4+3=7,那么就返回{1,3},这分别是元素4和3在数组中的索引。
我们现在知道了它是什么东西,那该如何实现它呢?我们考虑使用最简单直接的方法:暴力组合。该方法的思想是,把数组里面所有元素都组合一遍,看看哪个和等于目标数字。比如在上面这个数组中我们尝试:1+4等于7么?不等于,就尝试下一个组合。1+2!=7,1+3!=7,4+2!=7,4+3==7。于是返回4,3在数组中的索引。如果找遍了数组都没有找到正确的组合,就说明目标数字根本不以两数之和的形式存在于数组中,于是返回0和0,表示没组合到。
注意,这里是组合,不是排列,如果在代码中写的是排列的话,虽然对结果没影响,但是程序要花更多无用功,浪费了电脑资源。比如,如果使用排列的方式的话,以上过程在代码中就会变成:1+4!=7,1+2!=7,1+3!=7,4+1!=7,4+2!=7,4+3==7。足足多用了一次判断!不要看这里只有一次,当数组变大之后,冗余的判断会呈倍数增长。因此为了避免这种浪费,我们使用组合。
代码展示
package advance.algorithm;
public class SumOfTwo {
public int sum = 0;
public int[] twoSum(int[] arr,int target) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j < arr.length; j++) {
this.sum = arr[i] + arr[j];//尝试两数组合
if (this.sum == target) {
int[] result={i,j};
return result;//返回这两个数的索引
}
}
}return new int[] {0,0};//配对失败的返回值
}
//测试
public static void main(String[] args) {
SumOfTwo obj=new SumOfTwo();
int[] arr = { 1, 3, 5, 7 };//原始数组
int[] R=obj.twoSum(arr,12);
for(int e:R)//循环打印数组R里的元素
System.out.print(String.format("index %d; ", e));
}
}
代码讲解
我们说说如何才能让程序执行“组合”而不是排列。在上段代码中,我们看到内嵌的for循环中,循环变量j范围是从i+1到arr.length。我们这次用上段代码中的数组arr={1,3,5,7}作例子。我们的目标数字是12。
如果循环变量j的范围是从0到arr.length,那么这就是排列。过程是这样的:arr[0]+arr[1]!=12, arr[0]+arr[2]!=12, arr[0]+arr[3]!=12, arr[1]+arr[0]!=12, arr[1]+arr[1]!=12, arr[1]+arr[2]!=12, arr[1]+arr[3]!=12, arr[2]+arr[0]!=12, arr[2]+arr[1]!=12, arr[2]+arr[2]!=12, arr[2]+arr[3]==12。
足足有五次冗余!不过如果按照范围从i+1-arr.length的话,就没有这五个冗余判断,大大节省了计算机算力。
寻找单个元素实现思路
这个算法是什么呢?这个算法可以在一个数组中寻找到落单的数。比如这里有数组{4,1,4,6,1},显然,这里落单的数是6,因此该算法最后返回值为6
这里提供了两种实现思路,第一种比较代码量比较大,但是结构很稳固。第二种你没有听错,只有一行!这是纯数学思路,通过异或的特性来完成计算。
思路一
我们试着理解第一种思路。首先我们把数组里面的元素从小到大依次排列,这样相同的数字就会紧挨在一起。于是我们基本的想法就有了:如果两个相邻的数字相等,计数器就加1,表示他不是一个落单的数字。如果两个数不相等,我们就先看计数器是否为0,如果为0,就说明这个数是落单的数,我们返回它就好了;如果不为0,说明这个数的上个数和他相等,因此我们就把计数器重置为0,开始下一个计数。
我们用上面数组演示一下这个思路。在数组从小到大排序后,数组变成了:arr={1,1,4,4,6}。arr[0]==arr[1], cnt+=1; arr[1]!=arr[2], cnt!=0, so cnt=0. arr[2]==arr[3], cnt+=1; arr[3]!=arr[4], cnt!=0, so cnt=0。
这里我们遇到了一个小问题。arr[4]之后就没有元素了,无法继续进行比较。因此,我们要考虑另外一种情况,即落单的数字在数组末尾的情况。我们设立一个判断,如果一个数不等于他的下一个数,且下一个数处在数组的最大索引,那我们就直接返回下一个数,不需要再去进行下一次比较。
当然另还有一种情况。如果数组中只有一个元素,我们就无法使用上述方法,所以我们要在这个方法之前新建一个判断:如果数组长度等于1,那么就直接返回数组中唯一的元素。
思路二
我们现在看一下第二种方法,异或逻辑运算(exclusive or)。什么是异或呢?那我们要先了解一下与其相关的逻辑运算符,他们是:和(and),或(or)以及非(not),都属于布尔代数的范畴,也就是说我们接下来的演示用到的是二进制数。我们用1表示True,0表示False。
和:1 and 1=1, 1 and 0=0, 0 and 0=0
或:1 or 1=1, 1 or 0=1, 0 or 0=0
非:not 1=0, not 0=1(一元运算符)
异或:1 xor 1=0, 1 xor 0=1, 0 xor 0= 0
异或也叫半加运算,它和或逻辑运算符比较相似,不过就如他的英文名一样,只有在一个1(True)一个0(False)的情况下才会返回1(True),其他情况都返回0(False)。
很多读者到这里可能就不太理解:数组中的数都是十进制的啊,这个是True和False,好像八竿子打不着关系呢。true和false可以表示为1,0,他们可以表示所有二进制数,而二进制数又能转化成十进制数,所以异或处理的数的类型并不重要,因为他们可以互相转换,同理,我们也可以使用代数来演示逻辑运算。我们现在来看一下异或满足的运算律。
交换律:a xor b= b xor a
归零律:a xor a=0
恒等律:a xor 0=a
结合律:a xor (b xor c)= (a xor b) xor c
自反律:a xor b xor a=b(可以根据交换律,归零律和恒等律推出)
其实对于以上运算律大家可以自己代数进去,都是可以成立的。利用以上运算律,我们就可以弄懂那一行代码了。我们还是拿数组{4,1,4,6,1}做例子。根据自反律,我们知道前面三个元素异或运算的结果是1,那么我们再作一次自反,这样我们就得到6。当然,这些运算律只是便于我们人类计算和理解而已,计算机实际上不会用这些,它就是一个一个数去老老实实地去算。
代码展示
package advance.algorithm;
import java.util.Arrays;
public class SingleNumber {
public static int solution1(int[] arr) {//more robust than solution2 but much more complicated
Arrays.sort(arr);
int len=arr.length;
if(len==1)
return arr[0];
int cnt=0;
for(int i=0;i<len;i++) {
if (arr[i]==arr[i+1])
cnt++;
else if(arr[i]!=arr[i+1]) {
if(cnt==0) {
return arr[i];
}else if(i+1==len-1) {
return arr[i+1];
}else {
cnt=0;
}
}
}return 0;
}
public static int solution2(int[] arr) {
int single=0;for(int e:arr)single^=e;return single;
}
public static void main(String[] args) {
int[] arr= {2,2,1,4,4,1,5};
System.out.println(solution1(arr));
//System.out.print(solution2(arr));
}
}