左神算法课程学习记录,常用的一些算法自己再实现一遍,为刷leetcode准备。
目录
- 归并排序
- 随机快排实现(降低最小划分值概率,节省划分变量)
- 堆排序,先建立大根堆,再进行排序
- 比较器的使用
- 桶排序(求排序后相邻数最大差值,时间复杂度O(N))
- KMP(求Str1是否包含str2,时间复杂度O(N))
- Manacher(求最长回文子串,时间复杂度O(N))
- 用栈实现队列(借助一个辅助栈)
- 用队列实现栈
- 转圈打印矩阵
归并排序
MergeSort
/**
* 归并排序
* @author lizhibiao
* @date 2019/6/2 17:27
*/
public class MergeSort
{
private static void mergeSort(int[] arr)
{
if (null == arr || arr.length <= 1)
{
return;
}
//注意这里右边界传入的数组长度减1,数组下标是从0开始
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int l, int r)
{
if (l == r)
{
return;
}
int mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
/**
* 合并过程
* @param arr 原数组
* @param l 起始
* @param mid 中间值
* @param r 末尾
*/
private static void merge(int[] arr, int l, int mid, int r)
{
//辅助数组
int[] help = new int[r - l + 1];
//help数组索引,从0开始
int i = 0;
//左半部分起始位置是传进来的l,需要和右半部分比较谁小,往help里放
int left = l;
//右边部分起始位置,需要和左半部分比较谁小,往help里放
int right = mid + 1;
while (left <= mid && right <= r)
{
//谁小谁往help里放,同时小的索引右移,help数组索引也右移
help[i++] = arr[left] < arr[right] ? arr[left++] : arr[right++];
}
while (left <= mid)
{
help[i++] = arr[left++];
}
while (right <= r)
{
help[i++] = arr[right++];
}
for (int j = 0; j < help.length; j++)
{
arr[l + j] = help[j];
}
}
public static void main(String[] args)
{
int[] arr = {2, 4, 3, 1};
mergeSort(arr);
for (int value : arr)
{
System.out.print(value);
}
}
}
随机快排实现(降低最小划分值概率,节省划分变量)
QuickSort
/**
* 随机快排
* @author lizhibiao
* @date 2019/6/4 19:36
*/
public class QuickSort
{
private static void quickSort(int[] arr)
{
if (null == arr || arr.length <= 1)
{
return;
}
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int l, int r)
{
if (l < r)
{
//从l到r范围内随机一个数放到数组末尾位置
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
int[] partition = partition(arr, l, r);
quickSort(arr, l, partition[0] - 1);
quickSort(arr, partition[1] + 1, r);
}
}
private static void swap(int[] arr, int i, int j)
{
int tempValue = arr[i];
arr[i] = arr[j];
arr[j] = tempValue;
}
/**
* 小于区、等于区、大于区 过程
* @param arr 数组
* @param l start
* @param r end
* @return 返回等于区的左边界和右边界下标
*/
private static int[] partition(int[] arr, int l, int r)
{
int less = l - 1;
int more = r;
while (l < more)
{
if (arr[l] < arr[r])
{
swap(arr, ++less, l++);
}
else if (arr[l] > arr[r])
{
swap(arr, l, --more);
}
else
{
l++;
}
}
//大于区第一个数和数组最后一个数交换(也就是划分值)
swap(arr, more, r);
//等于区的左边界和右边界下标
return new int[]{less + 1, more};
}
public static void main(String[] args)
{
int[] arr = {5, 6, 3, 8, 9, 6, 1, 8};
quickSort(arr);
for (int value : arr)
{
System.out.print(value);
}
}
}
堆排序,先建立大根堆,再进行排序
HeapSort
/**
* 堆排序,先建立大根堆,再进行排序
* @author lizhibiao
* @date 2019/6/12 11:42
*/
public class HeapSort
{
private static void heapSort(int[] arr)
{
if (null == arr || arr.length < 2)
{
return;
}
int size = arr.length;
for (int i = 0; i < size; i++)
{
heapInsert(arr, i);
}
//完全二叉树头节点和尾节点交换位置,对应到数组就是0位置和数组末尾位置
//数组长度减1,末尾值相当于有序了
swap(arr, 0, --size);
while (size > 0)
{
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
/**
* 建立大根堆,当前索引和父节点比较,大继续往上和父节点比较
* @param arr 数组
* @param i 当前索引
*/
private static void heapInsert(int[] arr, int i)
{
while (arr[i] > arr[(i - 1) / 2])
{
swap(arr, i, (i - 1) / 2);
i = (i - 1) / 2;
}
}
private static void swap(int[] arr, int i, int j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 如果树的头节点小于孩子节点,和大的孩子节点交互位置,往下沉,继续比较
* @param arr 数组
* @param index 当前头节点索引
* @param size 逻辑上的堆边界值
*/
private static void heapify(int[] arr, int index, int size)
{
//左孩子索引
int leftChild = index * 2 + 1;
while (leftChild < size)
{
//左孩子和右孩子比较大小,取大的孩子节点索引
//注意这个三目运算比较,不满足的情况一定是要等于左孩子节点,因为右孩子节点可能越界
int largeIndex = leftChild + 1 < size && arr[leftChild + 1] > arr[leftChild] ? leftChild + 1 : leftChild;
largeIndex = arr[largeIndex] > arr[index] ? largeIndex : index;
if (largeIndex == index)
{
break;
}
swap(arr, index, largeIndex);
index = largeIndex;
//更新新的头节点的左孩子节点
leftChild = index * 2 + 1;
}
}
public static void main(String[] args)
{
int[] arr = {8, 6, 7, 6, 3, 1, 2};
heapSort(arr);
for (int value : arr)
{
System.out.print(value);
}
}
}
比较器的使用
ComparatorExample
/**
* 比较器的使用
* @author lizhibiao
* @date 2019/6/16 15:20
*/
public class ComparatorExample
{
private static class Student
{
private String name;
private int age;
public Student(String name, int age)
{
this.name = name;
this.age = age;
}
}
/**
* 按学生年龄升序排序
*/
private static class MyAscendingComparator implements Comparator<Student>
{
@Override
public int compare(Student o1, Student o2)
{
return o1.age - o2.age;
}
}
/**
* 按学生年龄降序排序
*/
private static class MyDsendingComparator implements Comparator<Student>
{
@Override
public int compare(Student o1, Student o2)
{
return o2.age - o1.age;
}
}
public static void main(String[] args)
{
Student s1 = new Student("小菜", 20);
Student s2 = new Student("大鸟", 30);
Student[] arr = {s1, s2};
//升序
Arrays.sort(arr, new MyAscendingComparator());
sysMessage(arr);
System.out.println("===========分割线============");
//降序
Arrays.sort(arr, new MyDsendingComparator());
sysMessage(arr);
}
/**
* 输出信息
* @param arr 对象组
*/
private static void sysMessage(Student[] arr)
{
for (Student student : arr)
{
System.out.println("name:" + student.name + "" + "age:" + student.age);
}
}
}
桶排序(求排序后相邻数最大差值,时间复杂度O(N))
MaxGap
/**
* 桶排序
* @author lizhibiao
* @date 2019/7/8 21:30
*/
public class MaxGap
{
public static int maxGap(int[] nums)
{
if (nums == null || nums.length < 2)
{
return 0;
}
//注意这里的代码
int len = nums.length;
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
//找这个数组中的最大值和最小值, 这里进来的时候数组长度肯定大于等于2
for (int i = 0; i < len; i++)
{
//找最小值,所以min一开始要传入最大的数去比较。min为int最大值,
// 下一次比较max传进来的就是取nums[i]
min = Math.min(min, nums[i]);
//找最大值,所以,max一开始要传最小值进去比较,max最int最小值
max = Math.max(max, nums[i]);
}
//最大值和最小值相等,说明nums数组里值都相等,所以差值为0
if (min == max)
{
return 0;
}
//三个数组,表示i号桶是否为空和最大值、最小值
//hasNum数组用来记录每一个桶是否为空
boolean[] hasNum = new boolean[len + 1];
//maxs数组统计每个桶的最大值
int[] maxs = new int[len + 1];
//mins数组统计每个桶的最小值
int[] mins = new int[len + 1];
int bid = 0;
//建立所有桶信息
for (int i = 0; i < len; i++)
{
//每个数根据最大值和最小值算出你该进哪个桶
bid = bucket(nums[i], len, min, max);
//因为这个数,判断当前桶的最小值是否需要更新
mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i];
//因为这个数,判断当前桶的最大值是否需要更新
maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i];
//桶肯定不为空了
hasNum[bid] = true;
}
int res = 0;
//上一个桶的最大值
int lastMax = maxs[0];
int i = 1;
//下标从1开始
for (; i <= len; i++)
{
if (hasNum[i])
{
//每一桶的最小值都去找上一个桶的最大值
//取当前桶最小值减去上一通最大值,差值的最大值
res = Math.max(res, mins[i] - lastMax);
//更新上一个桶的最大值
lastMax = maxs[i];
}
}
return res;
}
/**
* 均分桶,然后根据公式算出你该进哪个桶
* (包括最大值和最小值也是根据这个公式选进第0号桶和最后一个桶)
* @param num 当前值,判断要进哪个桶
* @param len 数组的长度
* @param min 当前桶的最小值
* @param max 当前桶的最大值
* @return 放入几号桶
*/
public static int bucket(long num, long len, long min, long max)
{
return (int) ((num - min) * len / (max - min));
}
}
KMP(求Str1是否包含str2,时间复杂度O(N))
Kmp
/**
* KMP算法
* @author lizhibiao
* @date 2019/7/3 15:11
*/
public class Kmp
{
/**
* 字符串str1是否包含字符串str2
* @param str1
* @param str2
* @return 如果包含返回字符串str1匹配的起始位置,不包含返回-1
*/
private static int getIndexOf(String str1, String str2)
{
if (null == str1 || null == str2)
{
return -1;
}
if (str2.length() < 1 || str1.length() < str2.length())
{
return -1;
}
char[] char1 = str1.toCharArray();
char[] char2 = str2.toCharArray();
int[] nextArr = getNextArr(char2);
int first = 0;
int second = 0;
while (first < char1.length && second < char2.length)
{
if (char1[first] == char2[second])
{
first++;
second++;
}
else if (nextArr[second] == -1)
{
first++;
}
else
{
second = nextArr[second];
}
}
return second == char2.length ? first - second: -1;
}
/**
* next[]数组求解
* @param arr 传入的str2字符数组
* @return next[]数组
*/
private static int[] getNextArr(char[] arr)
{
if (arr.length <= 1)
{
return new int[]{ - 1};
}
int[] next = new int[arr.length];
next[0] = -1;
next[1] = 0;
int index = 2;
//每次跳的索引,刚好等于当前要求数的上一个数的最大前缀。
int jump = 0;
//从0开始,所以小于数组长度!
while (index < next.length)
{
if (arr[index - 1] == arr[jump])
{
next[index++] = ++jump;
}
else if (jump > 0)
{
//往前跳,数组是从0开始,所以刚好等于当前要求数的上一个数的最大前缀
jump = next[jump];
}
else
{
//没法再往前跳为了,说明当前缀长度为0,并且index加1继续下一个数的缀长度
next[index++] = 0;
}
}
return next;
}
public static void main(String[] args) {
String str = "abcabcababaccc";
String match = "ababa";
System.out.println(getIndexOf(str, match));
}
}
Manacher(求最长回文子串,时间复杂度O(N))
Manacher
/**
* @author lizhibiao
* @date 2019/8/25 14:24
*/
public class Manacher
{
public static char[] manacherString(String str)
{
char[] charArr = str.toCharArray();
char[] res = new char[str.length() * 2 + 1];
int index = 0;
for (int i = 0; i != res.length; i++) {
res[i] = (i & 1) == 0 ? '#' : charArr[index++];
}
return res;
}
public static int maxLcpsLength(String str)
{
if (str == null || str.length() == 0)
{
return 0;
}
//先处理成manacher字符数组也就是包含"#"的字符串
char[] charArr = manacherString(str);
//每一个数的回文半径长度记录数组
int[] pArr = new int[charArr.length];
//回文中心索引
int pC = -1;
//最右回文边界索引
int pR = -1;
int max = Integer.MIN_VALUE;
for (int i = 0; i != charArr.length; i++)
{
//i位置起码的回文半径是多少,不管是什么情况
//pR > i说明当前数在回文边界里面
// Math.min(pArr[2 * pC - i], pR - i), pArr[2 * pC - i]对称点i'的回文半径和pR到i的回文半径谁小就是不用验证的距离
// 这个其实就是:
// 情况2:如果i‘的回文半径在L和C之间,那么i的回文半径肯定和i’一样长,i回文半径等于pArr[2 * pC - i]
// 情况3:如果i‘的回文半径有部分在L和C之外,那么i的回文半径是i到R的距离,即pR - i
// 情况2和情况3两者之前取最小
// 然后就是情况4:i只需要判断从R的下一个数开始继续往两边扩,i的回文半径和i'的回文半径相等,所以随便取一个就可以
// 总结:Math.min(pArr[2 * pC - i], pR - i), pArr[2 * pC - i] 就是pR > i当前数在回文边界里面,最小的不用验证的距离
//pR <= i说明当前数不在回文边界里面那么需要往两边扩,最小的不用验证的回文半径长度就是自己本身所以就是1
//pArr[i] = pR > i ? Math.min(pArr[2 * pC - i], pR - i) : 1;
// 这个求得就是每个数起码的回文半径长度,现在只是为了code的短减少分支,整合在一起了,否则需要写4个分支
pArr[i] = pR > i ? Math.min(pArr[2 * pC - i], pR - i) : 1;
//继续判断能不能扩,能扩多远?
//判断是否越界i + pArr[i] < charArr.length && i - pArr[i] > -1
while (i + pArr[i] < charArr.length && i - pArr[i] > -1)
{
//如果没有越界,判断新的边界是否相等,如果相等回文边界加1
//当前数i的索引 + 当前数回文半径长度pArr[i] = 等于回文半径右边界的下一索引,也就是新的右边界
//当前数i的索引 - 当前数回文半径长度pArr[i] = 等于回文半径左边界的前一索引,也就是新的左边界
if (charArr[i + pArr[i]] == charArr[i - pArr[i]])
{
pArr[i]++;
}
else
{
break;
}
}
//如果当前数的最右回文半径边界大于当前记录的最右回文边界,需要更新最右回文边界值和回文中心索引
if (i + pArr[i] > pR)
{
pR = i + pArr[i];
pC = i;
}
//记录着整个全局的最大回文半径长度值,更新
max = Math.max(max, pArr[i]);
}
//#a#b#c#1#2#3#4#3#2#1#a#b#
//每一个数的回文半径长度记录包含了“#”符号,所以pArr[i] - 1刚好就是回文子串
//例如pArr[13] == 8, 即上面那个数字4的回文半径长度是8,回文半径里包含了“#”符号,减1刚好等于回文子串长度
return max - 1;
}
public static void main(String[] args)
{
String str1 = "abc1234321ab";
System.out.println(maxLcpsLength(str1));
}
}
用栈实现队列(借助一个辅助栈)
StackImplementQueue
/**
* 两个栈实现队列
* @author lizhibiao
* @date 2020/1/1 22:27
*/
public class StackImplementQueue
{
/**
* 数据栈
*/
private Stack<Integer> dataStack = new Stack<>();
/**
* 辅助栈
*/
private Stack<Integer> helpStack = new Stack<>();
/**
* 添加
* @param value 值
*/
public void add(int value)
{
dataStack.push(value);
}
/**
* 弹出
*/
public int pop()
{
if (dataStack.isEmpty() && helpStack.isEmpty())
{
throw new RuntimeException("Queue pop error!");
}
//只有辅助栈为空,那么就需要一次性把数据栈数据全部倒入
if (helpStack.isEmpty())
{
while (!dataStack.isEmpty())
{
Integer value = dataStack.pop();
helpStack.push(value);
}
}
return helpStack.pop();
}
public static void main(String[] args)
{
StackImplementQueue queue = new StackImplementQueue();
queue.add(1);
queue.add(2);
queue.add(3);
System.out.println("弹出的值" + queue.pop());
queue.add(4);
System.out.println("弹出的值" + queue.pop());
System.out.println("弹出的值" + queue.pop());
System.out.println("弹出的值" + queue.pop());
}
}
输出:
弹出的值1
弹出的值2
弹出的值3
弹出的值4
用队列实现栈
QueueImplementStack
/**
* 用队列实现栈
* @author lizhibiao
* @date 2020/1/16 20:45
*/
public class QueueImplementStack
{
/**
* 原数据队列
*/
private Queue<Integer> dataQueue = new LinkedList<>();
/**
* 辅助队列
*/
private Queue<Integer> helpQueue = new LinkedList<>();
/**
* 默认开始弹出数据的长度
*/
private static final int DEFAULT_POP_LENGTH = 1;
/**
* 压栈
* @param value 值
*/
public void push(int value)
{
dataQueue.add(value);
}
/**
* 出栈
* @return 弹出的值,没有返回-1
*/
public int pop()
{
if (dataQueue.isEmpty())
{
throw new RuntimeException("dataQueue is empty !");
}
while (dataQueue.size() != DEFAULT_POP_LENGTH)
{
helpQueue.add(dataQueue.poll());
}
int pollValue = dataQueue.poll();
swap();
return pollValue;
}
/**
* 交换队列引用
*/
private void swap()
{
Queue<Integer> tmp = helpQueue;
helpQueue = dataQueue;
dataQueue = tmp;
}
public static void main(String[] args)
{
QueueImplementStack stack = new QueueImplementStack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
转圈打印矩阵
PrintCircleMatrix
/**
* 转圈打印矩阵
*
* @author lizhibiao
* @date 2020/5/31 9:47
*/
public class PrintCircleMatrix
{
public static void main(String[] args)
{
//构建一个4行5列二维数组
int[][] arrData = buildArrData(4, 5);
System.out.println(Arrays.deepToString(arrData));
int startLineIndex = 0;
int startColumnIndex = 0;
int endLineIndex = arrData.length - 1;
int endColumnIndex = arrData[0].length - 1;
// printOneCircle(arrData, 0, 0, 0, 5);
// printOneCircle(arrData, 0, 0, 4, 0);
//一层一层打印
while (startLineIndex <= endLineIndex
|| startColumnIndex <= endColumnIndex)
{
printOneCircle(arrData, startLineIndex++, startColumnIndex++, endLineIndex--, endColumnIndex--);
}
}
/**
* 构建一个nLine行nColumn列的二维数组, 值累加
* @param nLine 行
* @param nColumn 列
* @return 返回数组
*/
private static int[][] buildArrData(int nLine, int nColumn)
{
int value = 1;
int[][] arr = new int[nLine][nColumn];
//行
int line = arr.length;
for (int i = 0; i < line; i++)
{
//列
int column = arr[i].length;
for (int j = 0; j < column; j++)
{
arr[i][j] = value++;
}
}
return arr;
}
/**
* 打印一圈的值
* @param arr 传入的数组
* @param startLineIndex 起始行索引
* @param startColumnIndex 起始列索引
* @param endLineIndex 终止行索引
* @param endColumnIndex 终止列索引
*/
private static void printOneCircle(int[][] arr, int startLineIndex, int startColumnIndex,
int endLineIndex, int endColumnIndex)
{
//只有一行
if (startLineIndex == endLineIndex)
{
for (int i = startColumnIndex; i < endColumnIndex; i++)
{
System.out.print(arr[startLineIndex][i] + " ");
}
return;
}
//只有一列
if (startColumnIndex == endColumnIndex)
{
for (int i = startLineIndex; i < endLineIndex; i++)
{
System.out.print(arr[i][startColumnIndex] + " ");
}
return;
}
//其它情况,打印圈
for (int i = startColumnIndex; i < endColumnIndex; i++)
{
System.out.print(arr[startLineIndex][i] + " ");
}
for (int i = startLineIndex; i < endLineIndex; i++)
{
System.out.print(arr[i][endColumnIndex] + " ");
}
for (int i = endColumnIndex; i > startColumnIndex; i--)
{
System.out.print(arr[endLineIndex][i] + " ");
}
for (int i = endLineIndex; i > startLineIndex; i--)
{
System.out.print(arr[i][startColumnIndex] + " ");
}
}
}