一、异或
简言之,”异或“是一种逻辑运算,两个数字的二进制位对应位置相同时为0,不同时为1。
满足交换律和结合律:
A ^ B = B ^ A
A ^ B ^ C = A ^ (B ^ C)
且0和任何运算元异或值为该运算元的值,两个相同运算元异或值为0.
0 ^ N = N
N * N = 0
以一段代码为例:
int a = 甲
int b = 乙
a = a ^ b // 步骤1
b = a ^ b // 步骤2
a = a ^ b // 步骤3
先揭晓答案,a最终结果为乙,b最终结果为甲。答对的朋友请跳过,答错的朋友请细细看来~
其中,经过步骤一后,a的值为甲^乙;b的值不变,为乙。
经过步骤二后,a的值不变,为甲^乙;b的值为(甲^乙)^乙,即甲^(乙^乙),即甲^0,即b的值为甲。
经过步骤三后,b的值不变,为甲;a的值为(甲^乙)^甲,即甲^甲^乙,即0^乙,即乙。
综上,最后运算结果为a = 乙,b = 甲。
左神讲了一道题真的还是挺经典的面试可能会问到的题。
(1)只有一种数出现了奇数次,找到数组中出现了奇数次的数。(同理:力扣136道)
假设存在数组a,找出数组a中出现了奇数次的数。
解决思路:使用异或。
A= [ a, b, c, d…… ]
int eor = 0
使eor和数组A中每个数都异或一遍,最终得到的结果即出现了奇数次的数。eor = a^b^c^d……
是不是很妙呢?开心消消乐,出现偶数次的都被消掉了~
(2)有两种不同数出现了奇数次,怎么找到这两种数?
假设数组中a和b出现了奇数次,a不等于b。int eor = 0
eor和数组中所有值异或,最终eor = a^b,那么怎么知道a和b的值呢?
因为a和b不同,那么eor某一位上一定是1,假设eor第八位是1,那么可以把数组分为两类:第八位上是1的数和第八位上是0的数。新设置一个变量eor',让eor'去异或第八位上是1的数,即可得到a或b中的一个,最后再用eor’去异或eor,得到另一个数。
(一个数)与(这个数取反+1)就可以提取出这个数最右侧的1。
二、插入排序
插入排序比冒泡排序和选择排序会好一点
代码实现如下:
public static void insertionSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
//0~0是有序的 0~i想要有序
for(int i = 1; i < arr.length; i++){
for( int j = i - 1; j >= 0 && arr[j] > arr[j+1]; j--){
swap(arr, j, j + 1);
}
}
}
三、二分法
说到二分法,大部分朋友应该很熟悉了:
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length-1, mid;
while(left<=right){
mid = (left + right)/2;
if(target == nums[mid]){
return mid;
}
else if(target < nums[mid]){
right = mid - 1;
}
else{
left = mid + 1;
}
}
return -1;
}
}
先讲一个非常经典的题,有其他方法,这里讲的是使用二分法来做。
在排序数组中查找元素的第一个位置和最后一个位置。(力扣第34题)
给定一个按照升序排列的整数数组nums,和一个目标值target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值,返回[-1,-1]。
eg:[5, 7, 7, 8, 8, 9, 10] target=9 =====> 返回值[3, 4]
当然可以从数组左右遍历,遇到target值结束遍历,则左边遍历结束的位置为第一个位置,右边遍历结束的位置为最后一个位置。这个解法很简单,可以自己下去实现一下。
那么主要说说怎么用二分法来解决:
解题思路,找到两个位置,一个位置左侧区间的数字都小于target,一个位置右侧区间的数字都大于target。所以一个是找target所在的第一个位置,一个是找target所在的最后一个位置。那么怎么找到target出现的第一个位置呢?
1、target==nums[mid]的时候,target出现的第一个位置可能在mid,也可能在mid左边;
2、target<nums[mid]的时候,第一个位置在mid左边,那么使得right = mid;
3、target>nums[mid]的时候,第一个位置在mid右边,那么使得left = mid + 1;
待补充......
局部最小值问题:
在一个数组arr中,arr无序,但是任意两个相邻的数不相等,求任意一个局部最小数。(局部最小就是比左右两边的数字都要小,eg数组[7,5,6],5就是局部最小数。)
四、归并排序
相较于选择排序、冒泡排序经过多次比较只能得到一个值的位置,归并排序中比较的行为没有浪费,变成了整体有序的部分,然后跟下一个更大的部分merge!(经典)
详细讲解一下:
1)归并排序整体就是一个简单递归,左边排好序、右边排好序、让其整体有序;
2)让其整体有序的过程用了排外序方法;
3)利用master公式来求解时间复杂度;
master公式为:
T(N) = a * T(N/b) + O(N^d)
4)归并排序的实质
时间复杂度O(N*logN),额外空间复杂度O(N)。
又讲到一道算法题,小和问题。
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
eg:1,3,4,2,5 那么小和为:0+1+(3+1)+1+(1+3+2+4)= 16
简单的做法是,使用两个for循环,第一个for循环从右至左遍历,第二个for循环从左至右遍历,寻找比第一个for循环指的数字小的并且加入到sum中,以此类推。
下面讲解一下使用归并排序来解决这个问题:
归并排序的核心,先部分产生小和,再整体merge小和。整体过程如下图所示,我们把找左边比他小的数变为找右边有多少个数字比他大。
经过步骤1,我们知道1右边有一个3比他大,所以这里有1个1;步骤2、3我们知道(1,3)右边4比他们大,所以这里有1个1,1个3;步骤4我们知道2右边6比他大,所以这里记录1个2;同理经过步骤5我们这里记录,2个1,1个3,1个4;
最终结果为 1+1+3+2+2*1+3+4=16
我最开始这里也不太理解,后来懂了!因为有排序所以就能知道右边有多少个数字比他大了!