目录
1、二分查找
/**
* 二分查找,找到该值在数组中的下标,否则为-1
*/
public static int binarySerach(int[] array, int key) {
int left = 0;
int right = array.length - 1;
while (left <= right) { // 这里必须是 <=
int mid = left + (right - left) / 2; // 养成习惯 防止溢出
if (array[mid] == key) {
return mid;
} else if (array[mid] < key) { //这里是小于号,可以加上等于(加上等于左边界不断递增,就是查找最后一个等于key的元素)
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
2、回溯算法基本思想:
// 重复元素可以用if(i>0 &&nums[i]==nums[i-1]&&!used[i-1])进行剪枝
public void dfs(int k, int[] nums) {
if (k == nums.length) {
res.add(new ArrayList<>(tmp));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i])
continue;
tmp.add(nums[i]); //可用tmp.set(index,value)代替,后面就不应remove
used[i] = true;
dfs(k + 1, nums);
used[i] = false;
tmp.remove(tmp.size()-1); //回溯 还原状态
}
}
3.图的深度和广度优先遍历:
//DFS
{
int level = 0;
while (!deque.isEmpty()) {
size = deque.size();
while (size-- > 0) {
cur = deque.pop();
foreach 节点 in cur所有的相邻节点:
if 该节点不为空且没有被访问过:
deque.offer(该节点)
}
level++;
}
}
// BFS
//类似回溯
DFS(int deep,...){
if (找打解||走不下去了){
...
return;
}
DFS(int deep+1,....) // 枚举下一种情况
}
4、快速幂
//迭代版
int qpow(int a, int n) {
int ans = 1;
while (n > 0) {
if ((n & 1) == 1) //如果n的当前末位为1
ans *= a; //ans乘上当前的a
a *= a; //a自乘
n >>= 1; //n往右移一位
}
return ans;
}
//递归版
int qpow(int a, int n) {
if (n == 0) return 1;
if ((n & 1) == 1)
return a * qpow(a, n - 1);
else {
int tmp = qpow(a, n / 2); //奇数会自动转为int
return tmp * tmp;
}
}
5、常用排序算法
public static void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
public static void BubbleSort(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
//boolean flag = true; //优化 如果一趟没有做过交换,就直接结束。
for (int j = 0; j < nums.length - i - 1; j++) {
if (nums[j] > nums[j + 1]) {
swap(nums, j, j + 1);
// flag = false;
}
}
//if (flag) break;
}
}
public static void SelectSort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
int minIndex = i; //最小的数对应的下标
for (int j = i; j < nums.length; j++) {
if (nums[j] < nums[minIndex])
minIndex = j;
}
swap(nums, i, minIndex);
}
}
public static void InsertSort(int[] nums) {
for (int i = 1; i < nums.length; i++) { //从第2个数据开始插入
int temp = nums[i]; //记录要插入的数据
int j = i - 1;
while (j >= 0 && nums[j] > temp) { //从后向前移动,比他大的都向后移
nums[j + 1] = nums[j];
j--;
}
nums[j + 1] = temp;
}
}
public static void BuildMaxHeap(int[] nums) {//构建一个大顶堆,从最下面的非叶子节点开始向上遍历
for (int i = nums.length / 2 - 1; i >= 0; i--) { //注意,下标从0开始,i的子节点是2i+1,2i+2,反之
adjustHeap(nums, i, nums.length);
}
}
public static void adjustHeap(int[] arr, int parent, int length) {
int temp = arr[parent]; //临时保存父节点
int child = 2 * parent + 1; //左子节点的下标
while (child < length) {
if (child + 1 < length && arr[child] < arr[child + 1]) { //判断左子节点和右子节点的大小,若右边大,则把child定位到右边
child++;
}
if (arr[child] > temp) { //若child大于父节点,则交换位置,交换位置之后,不能保证当前的子节点是它子树的最大值,所以需要继续向下比较,把当前子节点设置为下次循环的父节点,同时,找到它的左子节点,继续下次循环
arr[parent] = arr[child];
parent = child;
child = 2 * parent + 1;
} else { //如果当前子节点小于等于父节点,则说明此时的父节点已经是最大值了,结束循环
break;
}
}
arr[parent] = temp; //把当前节点(parent可能已经改变)替换为最开始暂存的父节点值
}
public static void MergeSort(int[] nums, int low, int high) {
if (low >= high)
return;
int mid = low + (high - low) / 2;
MergeSort(nums, low, mid); // 递归排序前半部分
MergeSort(nums, mid + 1, high); // 递归排序后半部分
merge(nums, low, mid, high); // 归并
}
public static void merge(int[] nums, int low, int mid, int high) {
int i = low, j = mid + 1; //low为左半部分的起始inex
int[] temp = Arrays.copyOf(nums, nums.length); //复制一份
for (int index = low; index <= high; index++) {
if (i > mid) //左半部分i<low<mid,每次执行完i++,(mid-1完成后i=mid已经越界了)因此这里不能等于
nums[index] = temp[j++];
else if (j > high) //有半部分 mid+1 <j<high同理,
nums[index] = temp[i++];
else if (temp[i] < temp[j])
nums[index] = temp[i++];
else
nums[index] = temp[j++];
}
}
public static void QuickSort(int[] nums, int low, int high) {
if (low >= high)
return;
int index = partion(nums, low, high);
QuickSort(nums, low, index - 1);
QuickSort(nums, index + 1, high);
}
public static int partion(int[] nums, int low, int high) {
int pivot = nums[low];
while (low < high) {
while (low < high && nums[high] >= pivot) high--;
nums[low] = nums[high];
while (low < high && nums[low] <= pivot) low++; //从左往右扫描,找到第一个比基准值大的元素
nums[high] = nums[low];
}
nums[low] = pivot;
return low;
}
6、二叉树的遍历方式:
// 先序遍历,根左右,遍历把根push进stack,然后push 右子树,左子树。由于根在(根左右)前面,每次子遍历根都在前面,压进去就出来了,可忽略根的属性。由于栈的特性。左右子树压进去的顺序相反
public static void preOrderRecur(TreeNode head) {
if (head == null) return;
System.out.print(head.value + " ");//res.add(head.val) 访问
preOrderRecur(head.left);
preOrderRecur(head.right);
}
//--------------------------------------------------------
public void preorderTraversal(TreeNode p) {
if (p == null) return;
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(p);
while (!stack.isEmpty()) {
TreeNode tmp = stack.pop(); // poll()不会抛异常
System.out.print(tmp.val + " ");//res.add(tmp.val);
if (tmp.right != null) stack.push(tmp.right);
if (tmp.left != null) stack.push(tmp.left);
}
}
// 中序遍历,每次要一直向左子树压到底直到不能在压入,每次弹出一个加入结果集,如果有右子树,就把右子树当做当前节点,重复上述操作。
public static void inOrderIteration(TreeNode p) {
if (p == null) return;
Deque<TreeNode> stack = new LinkedList<>();
while (!stack.isEmpty() || p != null) { //栈不空,当前不空
while (p != null) { //左孩子非空,一直压栈
stack.push(p);
System.out.println(":push:" + p.val);
p = p.left;
}
p = stack.pop(); //弹出加到结果集合中,赋给右孩子,重复上一个while 一直压栈
res.add(p.val);//print
p = p.right;
}
}
// 后续遍历的第一种方式
// 后续遍历,是左右根的操作。倒过来就是根左右(利用栈的特性打的过来)。有没有很熟悉,前序是根右左,因此压栈顺序相反就行(前序是先压右子树,因此这里是先压左子树)
public static void postOrderIteration(TreeNode root) {
if (root == null) return res;
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode tmp = stack.pop();
res.add(0, tmp.val); //相当于头插法实现一次reverse//print
if (tmp.left != null)
stack.push(tmp.left);
if (tmp.right != null)
stack.push(tmp.right);
}
}
// 后续遍历的第二种方式
// 左右根 遍历的时候 第一次碰到根节点(任意的子过程中的根节点,可以视为任意节点)压栈,从左返回不管,从右返回弹出节点。
// 左右根 同中序 左根右 , 前面两个while 到底 ,看看栈顶的右边值是不是空,不为空看看是不是第一次走,第一次走把他压栈,不然就访问,记得置p值,一个是left一个是null
public static void PostOrderIteration2(TreeNode p) {
if (p == null) return;
TreeNode last = p;
Deque<TreeNode> stack = new LinkedList<>();
while (!stack.isEmpty() || p != null) {
while (p != null) { //一直压入直到左孩子为空
stack.push(p);
p = p.left;
}
p = stack.peek().right; //注意此时p的左孩子为空或者已经访问过
if (p != null && p != last) { //如果右孩子不为空且右孩子不是上一次访问过的节点 注意这里是左右孩子 不是p
stack.push(p);
p = p.left; //回归下一次循环
} else { //左右孩子皆为空
p = stack.pop(); //弹出
res.add(p.val); //访问一次加到结果中
last = p;
p = null; //把p置为空,为了弹栈,如果不置空,将重复压栈
}
}
}
// 层次遍历,队列加入root, while栈不空,while (size -- >0)
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) return new ArrayList<>();
List<List<Integer>> res = new ArrayList<List<Integer>>();
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while (!que.isEmpty()) {
List<Integer> tmp = new ArrayList<>();
// 关键之处,仔细体会
int size = que.size();
while (size-- > 0) {
TreeNode node = que.poll();
tmp.add(node.val);
if (node.left != null)
que.offer(node.left);
if (node.right != null)
que.offer(node.right);
}
res.add(tmp);
}
return res;
}