1.算法认识复杂度对数期二分法与异或运算
-
数组的查找是O(1),因为他是直接计算地址偏移量
-
左移<< :num << n, 相当于 num 乘以2的 n 次方
-
有符号右移>> : 左边补上符号位 (负的就填1 正的就填0)。num >> n, 相当于 num 除以2的 n 次方
-
无符号右移>>> : 空位都补零
-
异或运算:记为混略进位两数相加。 110 ^ 111 = 001
- 性质1:0 ^ 0 = 0 n ^ n =0 n ^ 0 = n
- 性质2:异或运算满足交换律和结合律(一批数,不管顺序,异或的结果都一样) 题目1:如何不用额外变量交换两个数(两个数不能指向同一个地址)
a = a ^ b; b = a ^ b; //此时a = a ^ b b = a ^ b ^ b = a ^ 0 = a a = a ^ b; // a = a ^ b ^ b = a ^ 0 = a题目2:一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数
int eor = 0; for (int i = 0; i < arr.length; i++){ //跟数组中所有元素异或。出现偶数次的数,他们异或结果为0,(出现奇数次的数 -1)结果为0。最后就是0 ^ 出现奇数次的数 = 出现奇数次的数 eor = eor ^ arr[i]; } System.out.println(eor);题目3:怎么把一个int类型的数n,提取最右侧的1出来(例如:这个数的二机制是 00110000100 ,结果应该是100)
n & ((~n) + 1) // ============================== n = 00110000100 ~n = 11001111011 ~n+1 = 11001111100 n&((~n)+1) = 00000000100消除n最右侧的1 : n & (n - 1)
题目4:一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数
2.算法链表结构栈队列递归行为哈希表
- 反转单链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;//前一指针
while(head != null){
//1.记录next指针
ListNode next = head.next;
//2.改变指针指向
head.next = pre;
//3.head和pre都挪一位
pre = head;
head = next;
}
//注意:不是返回head。反转后head指向null,pre由指向null变为新的head。因此while中条件就是head!=null
return pre;
}
}
- 删除链表中指定元素
Node removeValues(Node head,int num){
while(head !=null){
if(head.value != num){
break;
}
}
//head来到第一个不需要删除的位置,作为一个新的头部
Node cur = head;
Node pre = head;
while(cur != null){
if(cur.value == num){
pre.next = cur.next;
}else{
pre = cur;
}
cur = cur.next;
}
return head;
}
- 实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能 用两个栈,一个存数据,一个存当前最小值。输入数据:3 4 2 2 7 。左侧数据栈右侧最小值栈
- 用栈结构实现队列,用队列结构实现栈
- 哈希表
HashMap- 使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为 O(1),但是常数时间比较大
- 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用是这个东西的大小。如果不是基础类型,内部按引用传递,内存占用是8字节
- 有序表
TreeMap- 有序表在使用时,时间复杂度都是O(logN)
3.算法归并排序与快速排序
- 归并排序
private void merge(int[] arr, int l, int h, int mid) {
int[] help = new int[h - l + 1];
//将数组arrCopy分为左右两部分[l ... mid]和[mid+1 ... h]进行归并
int left = l; //左侧起点
int right = mid + 1;//右侧起点
// left遍历左边,right遍历右边。原则是哪个更小就把哪个拷贝到help数组里,然后left或者right加1。
// 跳出循环的条件是:一侧遍历结束,也就是left或者right越界了。假如left越界,说明left遍历完了,将右侧剩下数据直接拷贝过来即可
int i = 0;
//left,right都没有越界
while (left <= mid && right <= h) {
help[i++] = arr[left] > arr[right] ? arr[right++] : arr[left++];
}
//left没有越界,right越界
while (left <= mid) {
help[i++] = arr[left++];
}
//left越界,right没有越界
while (right <= h) {
help[i++] = arr[right++];
}
//将归并结束,也就是l-->h的所有排好序数据。放到arr中
for (i = 0; i < help.length; i++) {
arr[i + l] = help[i];
}
}
//递归的归并排序。调用跟斐波拉契数一样,是一个树形结构
void mergeSort(int[] arr, int l, int h) {
if (arr == null || arr.length < 2 || l == h) {
return;
}
int mid = l + ((h - l) >> 1);//也可写成(l+h)/2,但有溢出风险
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, h);
merge(arr, l, h, mid);
}
//====================================非递归的归并排序======================================
void mergeSort2(int[] arr, int l, int h) {
if (arr == null || arr.length < 2 || l == h) {
return;
}
//1:将第0和1个数归并,第2和3个数归并...... mergeSize=2:将index为[0,1]]和[2,3]两个数组归并...... mergeSize=3:将index为[0,1,2]]和[3,4,5]两个数组归并
int mergeSize = 1;//要归并的两个数组长度
while (mergeSize < h) {
int group = 2 * mergeSize;
for (int i = 0; i < arr.length; i = i + group) {
int left = i;
int mid = left + mergeSize - 1;
if (mid >= arr.length) {
//每次归并的两个数组长度为mergeSize。如果剩下数的个数小于mergeSize。那就已经有序了,不用管(都凑不齐两个数组)
break;
}
int right = Math.min(mid + mergeSize, arr.length - 1);
//将[left,left+mergeSize]和[left+mergeSize+1,right]两个数组归并
merge(arr, left, right, mid);
}
if (group > arr.length) {
break;
}
mergeSize = mergeSize << 1;
}
}
//==========================非递归的归并排序,跟上面非递归排序效果一样=============================
void mergeSort3(int[] arr, int l, int h) {
int mergeSize = 1;
while (mergeSize < arr.length) {
int left = 0;
while (left < arr.length) {
int mid = left + mergeSize - 1;// 0 2
if (mid >= arr.length) {
break;
}
int right = Math.min(mid + mergeSize, arr.length - 1);
merge(arr, left, right, mid);
left = right + 1;
}
if (mergeSize > arr.length / 2) {
break;
}
mergeSize = mergeSize << 1;
}
}
归并排序的时间复杂度为nlogn,而选择,插入,冒泡排序都是n^2。以选择排序为例,每次循环只选择一位最小值,浪费了比较行为。而归并排序,假如要排序的数是[1 0 3 2],第一次循环结束变成[0 1 2 3 ],让部分有序了。所以归并排序会快点
求:一个数组中的某个数,右侧有多少个数比他大,或者多少个数比他小,可以用归并
- 求一个数组的小和 在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。 例子:
[1,3,4,2,5] 1左边比1小的数:没有
3左边比3小的数:1
4左边比4小的数:1、3
2左边比2小的数:1
5左边比5小的数:1、3、4、 2
所以数组的小和为1+1+3+1+1+3+4+2=16
解题思路:右侧比1大的数有4位,所以1会加4次。同理,3会加2次,4会加一次,2会加一次,所以小和为14 + 32 +4 +2 =16。对数组进行归并排序,每次merge的时候,只要左侧数比右侧数小,就加上左侧数
private int merge(int[] arr, int l, int h, int mid) {
int[] help = new int[h - l + 1];
//将数组arrCopy分为左右两部分[l ... mid]和[mid+1 ... h]进行归并
int left = l; //左侧起点
int right = mid + 1;//右侧起点
// left遍历左边,right遍历右边。原则是哪个更小就把哪个拷贝到help数组里,然后left或者right加1。
// 跳出循环的条件是:一侧遍历结束,也就是left或者right越界了。假如left越界,说明left遍历完了,将右侧剩下数据直接拷贝过来即可
int i = 0;
//小和
int res = 0;
//left,right都没有越界
while (left <= mid && right <= h) {
//h - right + 1 右侧当前位置到最后有多少个数比此时左侧数大
res += arr[left] < arr[right] ? (h - right + 1) * arr[left] : 0;
help[i++] = arr[left] >= arr[right] ? arr[right++] : arr[left++];
}
//left没有越界,right越界
while (left <= mid) {
help[i++] = arr[left++];
}
//left越界,right没有越界
while (right <= h) {
help[i++] = arr[right++];
}
//将归并结束,也就是l-->h的所有排好序数据。放到arr中
for (i = 0; i < help.length; i++) {
arr[i + l] = help[i];
}
return res;
}
int minAdd(int[] arr, int l, int h) {
if (l == h) return 0;
int mid = l + ((h - l) >> 1);
return minAdd(arr, l, mid) + minAdd(arr, mid + 1, h) + merge(arr, l, h, mid);
}
代码中跟归并排序不一样的地方就是merge方法中
- 给定一个数组,左侧下标l和右侧下标h,给定数字num,要求将数组大于num的数排在左侧,大于num的数排在右侧,等于num的数排在中间,不要求有序
int[] sort(int[] arr, int l, int h, int num) {
if (l == h) {
return new int[]{l, h};
} else if (l > h) {
return new int[]{-1, -1};
}
//小于区域和大于区域初始没有值
int lowArea = l - 1;
int highArea = h + 1;
int index = l;
while (index < highArea) {
if (arr[index] < num) {
//小于区域右扩
swap(arr, index++, ++lowArea);
} else if (arr[index] > num) {
//大于区域左扩
swap(arr, index, --highArea);
} else {
//相等时候循环正常走index++,不做处理
index++;
}
}
return new int[]{lowArea, highArea};
}
void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//===================测试代码================
val array = intArrayOf(3, 4, 1, 2, 4, 2, 5, 2)
Test().sort(array, 0, array.size - 1, 4)
for ((index, element) in array.withIndex()) {
println("$index --> $element")
}
- 快速排序 基于上面的问题进而得出快速排序
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
//返回值:最后基准数的位置
public static int partition(int[] arr, int L, int R) {
if (L > R) {
return -1;
}
if (L == R) {
return L;
}
int lessEqual = L - 1;//小于等于区域初始值
int index = L;
while (index < R) {
if (arr[index] <= arr[R]) {
//小于等于基准值,小于区域右扩
swap(arr, index, ++lessEqual);
}
index++;
}
//循环结束后划分为[小于等于区域,大于区域,基准值]
//将小于区域下一位跟基准值交换。这样也就划分为[小于等于区域,基准值,大于区域]
swap(arr, ++lessEqual, R);
//返回基准值所在位置
return lessEqual;
}
public static int[] netherlandsFlag(int[] arr, int L, int R) {
if (L > R) {
return new int[] { -1, -1 };
}
if (L == R) {
return new int[] { L, R };
}
//以最右侧数为基准值,剩下的划分为三个区域,[小于基准值,等于基准值,大于基准值,基准值]
//将基准值和大于区域第一个数交换
int less = L - 1;
int more = R;//不能R+1 ,否则为R的数据(基准值会被参与交换)
int index = L;
while (index < more) {
if (arr[index] == arr[R]) {
//小于区域右扩
index++;
} else if (arr[index] < arr[R]) {
//大于区域左扩
swap(arr, index++, ++less);
} else {
//相等时候循环正常走index++,不做处理
swap(arr, index, --more);
}
}
//将基准值和大于区域第一个数交换。这样也就划分为[小于基准值,等于基准值,大于基准值]
swap(arr, more, R);
return new int[] { less + 1, more };
}
//1.0版本
public static void quickSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process1(arr, 0, arr.length - 1);
}
public static void process1(int[] arr, int L, int R) {
if (L >= R) {
return;
}
// 1.0版本,M是基准数的位置,小于基准数在L --> M-1 大于等于基准数在 M+1 --> R
int M = partition(arr, L, R);
process1(arr, L, M - 1);
process1(arr, M + 1, R);
}
//2.0版本
public static void quickSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process2(arr, 0, arr.length - 1);
}
public static void process2(int[] arr, int L, int R) {
if (L >= R) {
return;
}
//2.0版本,在1.0版本基础上(partition智能返回基准值所在位置),等于基准数的区域不再参与循环(netherlandsFlag可以返回)
int[] equalArea = netherlandsFlag(arr, L, R);
process1(arr, L, equalArea[0] - 1);
process1(arr, equalArea[1] + 1, R);
}
//3.0版本
public static void quickSort3(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process3(arr, 0, arr.length - 1);
}
public static void process3(int[] arr, int L, int R) {
if (L >= R) {
return;
}
//3.0版本,在2.0版本基础上,随机选取一个数跟最右侧数交换
swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
int[] equalArea = netherlandsFlag(arr, L, R);
process1(arr, L, equalArea[0] - 1);
process1(arr, equalArea[1] + 1, R);
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
//产生随机数
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
int[] arr3 = copyArray(arr1);
//测试三个快排结果
quickSort1(arr1);
quickSort2(arr2);
quickSort3(arr3);
if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Oops!");
}
因为最坏的情况时间复杂度为O(N^2) (每次sort结束,返回的都在最右侧)。所以随意选择一个数与最右侧数交换,这样N足够大的时候,时间复杂度为O(N*logN)
4.堆与比较器
堆结构就是用数组实现的完全二叉树结构
完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
如果既不是大根堆,也不是小根堆,那就不是堆
- 大根堆
public class BigHeap {
private int[] arr;
private int limit;
private int heapSize;
public BigHeap(int limit) {
arr = new int[limit];
this.limit = limit;
heapSize = 0;
}
public boolean isEmpty() {
return heapSize == 0;
}
public void push(int value) {
if (heapSize >= limit) {
throw new RuntimeException("超出限制");
}
int index = heapSize++;
arr[index] = value;
if (heapSize <= 1) {
return;
}
//最后一个结点上浮
while (arr[index] > arr[(index - 1) / 2]) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public int pop() {
if (heapSize == 0) {
throw new RuntimeException("空堆");
}
if (heapSize == 1) {
return arr[--heapSize];
}
int result = arr[0];
swap(0, --heapSize);
sink(0);
return result;
}
//大根堆下沉
private void sink(int index) {
int left = (index * 2 + 1);
while (left < heapSize) {
//有右孩子,并且右孩子更大
int maxChildIndex = (left + 1) < heapSize && arr[left] < arr[left + 1] ? (left + 1) : left;
//当前节点和他的左右孩子,选出更大的
maxChildIndex = (arr[index] > arr[maxChildIndex]) ? index : maxChildIndex;
if (index == maxChildIndex) {
//当前已经最大,结束循环
break;
}
//与更大的孩子节点交换,等于父节点下沉到下一层
swap(index, maxChildIndex);
left = maxChildIndex * 2 + 1;
}
}
private void swap(int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//==============测试代码======================
val heap = BigHeap(100)
heap.push(3)
heap.push(7)
heap.push(9)
heap.push(2)
heap.push(3)
heap.push(8)
while (!heap.isEmpty){
println(heap.pop())
}
- 堆排序
public class HeapSort {
public static void sort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
//先建立大根堆
//从上往下建立大根堆,每次新节点在最下面,然后上浮
// O(N*logN)
//for (int index = 0; index < arr.length; index++) {// O(N)
// swim(arr, index);// O(logN)
//}
//从下往上建大根堆,每次新节点在上面,然后下沉(相对于从上往下建立大根堆,此办法效率更高)
for (int index = arr.length - 1; index >= 0; index--) {
sink(arr, index, arr.length);
}
int heapSize = arr.length;
//再依次将堆中第一个元素(最大的元素)移到数组最后面去。假如数组长度为5,index为0和index为4交换,调整0-3的数。
// index为0和index为3交换,调整0-2的数...最后数组的数就从小到大排列了
while (heapSize > 0) {
swap(arr, 0, --heapSize);
sink(arr, 0, heapSize);
}
}
//节点上浮
private static void swim(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
//大根堆下沉
private static void sink(int[] arr, int index, int heapSize) {
int left = (index * 2 + 1);
while (left < heapSize) {
//有右孩子,并且右孩子更大
int maxChildIndex = (left + 1) < heapSize && arr[left] < arr[left + 1] ? (left + 1) : left;
//当前节点和他的左右孩子,选出更大的
maxChildIndex = (arr[index] > arr[maxChildIndex]) ? index : maxChildIndex;
if (index == maxChildIndex) {
//当前已经最大,结束循环
break;
}
//与更大的孩子节点交换,等于父节点下沉到下一层
swap(arr, index, maxChildIndex);
left = maxChildIndex * 2 + 1;
}
}
}
- 比较器Comparator
//返回正数:obj2排在前面 返回负数:obj1排在前面
int compare(Object obj1, Object obj2)