leetcode分类
1. DFS
岛屿与湖水
https://labuladong.gitee.io/algo/1/9/
2.标准回溯问题
3. 二分法
有序则二分
面试题 10.05. 稀疏数组搜索
4. 动态规划
5. 前缀树
6. 路径
7. 数位中1的个数
技巧
大小写转化
原地交换两数
class Solution {
public int[] swapNumbers(int[] numbers) {
numbers[1] = numbers[0]^numbers[1];
numbers[0] = numbers[0]^numbers[1];
numbers[1] = numbers[0]^numbers[1];
return numbers;
}
}
判断是否二次幂
一般有两种做法:方法一的复杂度为 O(logn),方法二为 O(1)。
①第一种是通过不断的相除,看能否除净,由于除法精度问题,一般采用乘法再比较。
boolean check(long x) {
//方法一
long cur = 1;
while (cur < x) {
cur = cur * 2;
}
return cur == x;
}
②与HashMap中静态方法tableSizeFor()类似,找到大于等于此数的第一个2的几次幂,与本数相比较。
java源码:
/**
* Returns a power of two table size for the given desired capacity.
* See Hackers Delight, sec 3.2
*/
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
判断二次幂代码:
long getVal(long x) {
long n = x - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return n < 0 ? 1 : n + 1;
}
为什么要x-1?
为了减少开辟的空间,若原来传进来的是2的幂,能够减少一位的空间。
KMP:最长相等前后缀数组
- 找出next矩阵
- 匹配两个串
class Solution {
// KMP 算法
// ss: 原串(string) pp: 匹配串(pattern)
public int strStr(String ss, String pp) {
if (pp.isEmpty()) return 0;
// 分别读取原串和匹配串的长度
int n = ss.length(), m = pp.length();
// 原串和匹配串前面都加空格,使其下标从 1 开始
ss = " " + ss;
pp = " " + pp;
char[] s = ss.toCharArray();
char[] p = pp.toCharArray();
// 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
int[] next = new int[m + 1];
// 构造过程 i = 2,j = 0 开始,i 小于等于匹配串长度 【构造 i 从 2 开始】
for (int i = 2, j = 0; i <= m; i++) {
// 匹配不成功的话,j = next(j)
while (j > 0 && p[i] != p[j + 1]) j = next[j];
// 匹配成功的话,先让 j++
if (p[i] == p[j + 1]) j++;
// 更新 next[i],结束本次循环,i++
next[i] = j;
}
// 匹配过程,i = 1,j = 0 开始,i 小于等于原串长度 【匹配 i 从 1 开始】
for (int i = 1, j = 0; i <= n; i++) {
// 匹配不成功 j = next(j)
while (j > 0 && s[i] != p[j + 1]) j = next[j];
// 匹配成功的话,先让 j++,结束本次循环后 i++
if (s[i] == p[j + 1]) j++;
// 整一段匹配成功,直接返回下标
if (j == m) return i - m;
}
return -1;
}
}
二叉树DFS与BFS
LeetCode
class Solution {
Set<Integer> set = new HashSet<>();
public int findSecondMinimumValue(TreeNode root) {
dfs(root);
if (set.size() < 2) return -1;
int first = Integer.MAX_VALUE, second = Integer.MAX_VALUE;
for (int i : set) {
if (i <= first) {
second = first;
first = i;
} else if (i <= second) {
second = i;
}
}
return second;
}
void dfs(TreeNode root) {
if (root == null) return;
set.add(root.val);
dfs(root.left);
dfs(root.right);
}
}
class Solution {
Set<Integer> set = new HashSet<>();
public int findSecondMinimumValue(TreeNode root) {
bfs(root);
if (set.size() < 2) return -1;
int first = Integer.MAX_VALUE, second = Integer.MAX_VALUE;
for (int i : set) {
if (i <= first) {
second = first;
first = i;
} else if (i <= second) {
second = i;
}
}
return second;
}
void bfs(TreeNode root) {
Deque<TreeNode> d = new ArrayDeque<>();
d.addLast(root);
while (!d.isEmpty()) {
TreeNode poll = d.pollFirst();
set.add(poll.val);
if (poll.left != null) d.addLast(poll.left);
if (poll.right != null) d.addLast(poll.right);
}
}
}
二叉树前序中序后序遍历(递归和迭代)
迭代法:
- 前序迭代:我-左-右
找一个栈,将此根节点入栈,只要这栈不为空,就弹出此节点,然后依次压入其右节点、左节点,由于栈的特殊性,弹出的结果为:左。右。再循环 - 后续迭代:左右我
思考。将整个反转,得到:我右左,这是前序的一种,只需将前序的迭代的循环中,压栈顺序改一下,改成先压左再压右,弹出的结果为:右,左。这样产生的结果为:我-右-左,再反转!Collections.reverse(result)
- 中序迭代:左我右
依次入栈所有左节点,必须先将当前节点推到最左下方的子节点。随后弹栈,弹出一个记一个,再将当前节点记为当前节点的右节点。
// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null){
stack.push(node.right);
}
if (node.left != null){
stack.push(node.left);
}
}
return result;
}
}
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()){
if (cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null){
stack.push(node.left);
}
if (node.right != null){
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}
}
递归法:
// 前序遍历·递归·LC144_二叉树的前序遍历
class Solution {
ArrayList<Integer> preOrderReverse(TreeNode root) {
ArrayList<Integer> result = new ArrayList<Integer>();
preOrder(root, result);
return result;
}
void preOrder(TreeNode root, ArrayList<Integer> result) {
if (root == null) {
return;
}
result.add(root.val); // 注意这一句
preOrder(root.left, result);
preOrder(root.right, result);
}
}
// 中序遍历·递归·LC94_二叉树的中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inorder(root, res);
return res;
}
void inorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
inorder(root.left, list);
list.add(root.val); // 注意这一句
inorder(root.right, list);
}
}
// 后序遍历·递归·LC145_二叉树的后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorder(root, res);
return res;
}
void postorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
postorder(root.left, list);
postorder(root.right, list);
list.add(root.val); // 注意这一句
}
}
stream取最大值最小值或者总和
Arrays.stream(dp).sum();
Arrays.stream(dp).min().getAsInt();
快排
nlogn
/**
* 快速排序
* @param array
*/
public static void quickSort(int[] array) {
int len;
if(array == null
|| (len = array.length) == 0
|| len == 1) {
return ;
}
sort(array, 0, len - 1);
}
/**
* 快排核心算法,递归实现
* @param array
* @param left
* @param right
*/
public static void sort(int[] array, int left, int right) {
if(left > right) {
return;
}
// base中存放基准数
int base = array[left];
int i = left, j = right;
while(i != j) {
// 顺序很重要,先从右边开始往左找,直到找到比base值小的数
while(array[j] >= base && i < j) {
j--;
}
// 再从左往右边找,直到找到比base值大的数
while(array[i] <= base && i < j) {
i++;
}
// 上面的循环结束表示找到了位置或者(i>=j)了,交换两个数在数组中的位置
if(i < j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
// 将基准数放到中间的位置(基准数归位)
array[left] = array[i];
array[i] = base;
// 递归,继续向基准的左右两边执行和上面同样的操作
// i的索引处为上面已确定好的基准值的位置,无需再处理
sort(array, left, i - 1);
sort(array, i + 1, right);
}
注意顺序很重要,如果先从左往右找就会找到错误结果!
为啥错误:
每次排序的时候,因为是ij处的元素与base元素进行交换,一定要保障的是我们ij处的元素严格小于base,故我们必须先判断右侧指针!
list转int【】
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
int[] data = {4, 5, 3, 6, 2, 5, 1};
// int[] 转 List<Integer>
List<Integer> list1 = Arrays.stream(data).boxed().collect(Collectors.toList());
// Arrays.stream(arr) 可以替换成IntStream.of(arr)。
// 1.使用Arrays.stream将int[]转换成IntStream。
// 2.使用IntStream中的boxed()装箱。将IntStream转换成Stream<Integer>。
// 3.使用Stream的collect(),将Stream<T>转换成List<T>,因此正是List<Integer>。
// int[] 转 Integer[]
Integer[] integers1 = Arrays.stream(data).boxed().toArray(Integer[]::new);
// 前两步同上,此时是Stream<Integer>。
// 然后使用Stream的toArray,传入IntFunction<A[]> generator。
// 这样就可以返回Integer数组。
// 不然默认是Object[]。
// List<Integer> 转 Integer[]
Integer[] integers2 = list1.toArray(new Integer[0]);
// 调用toArray。传入参数T[] a。这种用法是目前推荐的。
// List<String>转String[]也同理。
// List<Integer> 转 int[]
int[] arr1 = list1.stream().mapToInt(Integer::valueOf).toArray();
// 想要转换成int[]类型,就得先转成IntStream。
// 这里就通过mapToInt()把Stream<Integer>调用Integer::valueOf来转成IntStream
// 而IntStream中默认toArray()转成int[]。
// Integer[] 转 int[]
int[] arr2 = Arrays.stream(integers1).mapToInt(Integer::valueOf).toArray();
// 思路同上。先将Integer[]转成Stream<Integer>,再转成IntStream。
// Integer[] 转 List<Integer>
List<Integer> list2 = Arrays.asList(integers1);
// 最简单的方式。String[]转List<String>也同理。
// 同理
String[] strings1 = {"a", "b", "c"};
// String[] 转 List<String>
List<String> list3 = Arrays.asList(strings1);
// List<String> 转 String[]
String[] strings2 = list3.toArray(new String[0]);
}
}
二分查找
https://www.cnblogs.com/mxj961116/p/11945444.html
在一个升序序列中找左侧或者右侧终点
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length;
//找右侧最终点,左闭右开
while(left<right){
int mid = left+(right-left)/2;
if(nums[mid]> target){
right = mid;
}else if(nums[mid]<target){
left = mid+1;
}else if(nums[mid]==target){
left = mid+1;
}
}
int bound1 = left-1;
//找左侧节点头,左闭右开
left = 0;
right = nums.length;
while(left<right){
int mid = left+(right-left)/2;
if(nums[mid]> target){
right = mid;
}else if(nums[mid]<target){
left = mid+1;
}else if(nums[mid]==target){
right = mid;
}
}
int bound2 = right;
return bound1-bound2+1 ;
}
}
树:DFS
public static void DFS(TreeNode head){//树的深搜使用回溯
if (head==null)return;
check();//满足条件的check();
if (head.left!=null){
DFS(head.left);
}if (head.right!=null){
DFS(head.right);
}
}
树:BFS
public static void BFS(TreeNode head){//树的宽搜
if (head==null)return;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(head);
while (!queue.isEmpty()){
TreeNode m=queue.poll();
//System.out.print(m.val+" ");
//dosomething();
if (m.left!=null){
queue.offer(m.left);
}if (m.right!=null){
queue.offer(m.right);
}
queue.poll();
}
}
图算法:DFS
从起始顶点开始,递归访问其所有邻近节点。比如A节点是其第一个邻近节点,而B节点又是A的一个邻近节点,则DFS访问A节点后再访问B节点,如果B节点有未访问的邻近节点的话将继续访问其邻近节点,否则继续访问A的未访问邻近节点,当所有从A节点出去的路径都访问完之后,继续递归访问除A以外未被访问的邻近节点。
public static void DFS(int v){//图的深搜
visited[v]=true;
for (int i = 0; i < a[0].length; i++) {
if (check()&&visited[i]==false){
DFS(i);//回溯
}
}
}
图算法:BFS
即由一点逐级往下搜索,以图为例,由顶点v1可以延伸到v2、v3,再由v2可以延伸到v1、v4、v5,被搜索过的顶点不用再次搜索,直到整个图的节点被搜完一遍。
public static void BFS(int v){//图的宽搜
Queue<Integer> queue=new LinkedList<>();
Boolean[] visited=new Boolean[a.length*a[0].length];//已被访问节点
visited[v]=true;
queue.add(v);
int []prev=new int[a.length*a[0].length];
int l=0;
prev[l]=0;
while (queue.size()!=0){
int v2=queue.poll();
for (int i = 0; i <a[0].length ; i++) {//从v2点开始往下搜
if (visited[i]==false&&check()){//结点满足条件的check();
queue.add(i);
prev[++l]=i;
}
visited[i]=true;
}
}
}
最短路径
图算法:弗洛伊德算法
lc题目举例:https://leetcode-cn.com/problems/course-schedule-iv/solution/floyed-suan-fa-by-15228207-7r9k/
简单:就是三重遍历,最外层放中间节点,里面两层为i与j,最中间if判断,如果中继的路径小于加和,就更新。
https://leetcode-cn.com/problems/course-schedule-iv/
图算法:迪杰斯特拉算法
用于计算从某个源点到其他所有节点的最短路径。
图算法:贝尔曼-福特算法
弗洛伊德和迪杰斯特拉算法都不能处理求含负权边的图。
图算法:拓扑排序
内容:【广度优先遍历】+【贪心算法】在有向图中
应用场景:任务调度,一个需要是另一个的前提条件。
作用:输出一个拓扑序+判断是否存在环路。
典型题目课程表II:https://leetcode-cn.com/problems/course-schedule-ii/solution/tuo-bu-pai-xu-shen-du-you-xian-bian-li-python-dai-/
最小生成树
将一个有权图中的 所有顶点 都连接起来,并保证连接的边的 总权重最小,即最小生成树,最小生成树不唯一。
Prim算法
详解:https://mp.weixin.qq.com/s/bvi0wGdbtB4nkYye0yzmqg
举例:
package com.company.十种算法.prim;
import java.util.Arrays;
/**
* Author : zfk
* Data : 16:37
* prim算法 - 》 最小生成树
*/
public class PrimAlgorithm {
//用INF表示两个顶点不能连通
private static final int INF = 65535;
public static void main(String[] args) {
char[] data = new char[]{'A','B','C','D','E','F','G'};
int verxs = data.length;
//邻接矩阵
int[][] weight = new int[][]{
{INF,5,7,INF,INF,INF,2},
{5,INF,INF,9,INF,INF,3},
{7,INF,INF,INF,8,INF,INF},
{INF,9,INF,INF,INF,4,INF},
{INF,INF,8,INF,INF,5,4},
{INF,INF,INF,4,5,INF,6},
{2,3,INF,INF,4,6,INF}
};
//创建MGraph对象
MGraph mGraph = new MGraph(verxs);
//创建MinTree
MinTree minTree = new MinTree();
minTree.createGraph(mGraph,verxs,data,weight);
minTree.showGraph(mGraph);
minTree.prim(mGraph,0);
}
}
//创建最小生成树
class MinTree{
/**
* 创建图的邻接矩阵
* @param graph 图对象
* @param verxs 顶点个数
* @param data 顶点的值
* @param weight 图的邻接矩阵
*/
public void createGraph(MGraph graph,int verxs,char[] data,int[][] weight){
int i ,j;
for (i = 0;i < verxs;i++){
graph.data[i] = data[i];
for (j = 0;j < verxs;j++){
graph.weight[i][j] = weight[i][j];
}
}
}
public void showGraph(MGraph graph){
for (int[] link : graph.weight){
System.out.println(Arrays.toString(link));
}
}
/**
* 编写prim算法,得到最小生成树
* @param mGraph 图
* @param v 生成树的起点
*/
public void prim(MGraph mGraph,int v){
//标记已被访问的顶点,初始都为0
int[] visited = new int[mGraph.verxs];
//把当前节点标记为已访问
visited[v] =1;
//用h1和h2记录两个顶点的下标
int h1 = -1 , h2 = -1;
//存储最小权
int minWeight = 10000;
for (int k =1;k < mGraph.verxs;k++){
//i表示访问过的节点,j表示没有访问过的节点
for (int i = 0;i < mGraph.verxs;i++){
for (int j =0;j < mGraph.verxs;j++){
//当<i,j>的边权值小于最小值,存储两顶点,即找到最小边
if (visited[i] == 1 && visited[j] == 0 &&
mGraph.weight[i][j] < minWeight){
minWeight = mGraph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
//找到1条边最小
System.out.println("边<"+mGraph.data[h1]+","+mGraph.data[h2]+"> 权值:"+minWeight);
//标记已访问
visited[h2] =1;
//重新设置为最大值 10000
minWeight = 10000;
}
}
}
//图
class MGraph{
//结点个数
int verxs;
//存放结点数据
char[] data;
//邻接矩阵:存放边
int[][] weight;
public MGraph(int verxs){
this.verxs = verxs;
data = new char[verxs];
weight = new int[verxs][verxs];
}
}
kruskal算法
可以设置一个终点集合ends(终点为最大的顶点,<A,G>的终点为G),最小生成树每加入一个边,就将树中的每个顶点在最小生成树中的终点更新,如果加入的两个顶点相同,说明构成了回路。
大顶堆小顶堆
第k大的数,小顶堆(堆内都是大于堆顶的数,依次放入k个,如果此数小于堆顶,一定不能是第k大的数)
第k小的数,大顶堆(堆顶是最大数,存小值k个,如果此数大于堆顶,一定不能是第k小的数)
ACM模式IO
https://blog.csdn.net/qq_42403042/article/details/107533785
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
// int[] num;
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
String sr = bf.readLine();
int num[] = new int[Integer.parseInt(sr)];
while (bf.ready()) {
String[] str = bf.readLine().split(" ");
num = new int[str.length];
for (int i = 0; i < num.length; i++) {
num[i] = Integer.parseInt(str[i]);
}
System.out.println(Arrays.toString(num));
}
//写代码逻辑
}