目录
Java语言学习的其他内容 见Java基础知识总结(刷力扣必备)_青衫倚斜桥的博客-CSDN博客
刷题顺序见: 力扣
1、如何判断程序的复杂程度:时间和空间复杂度
如何判断程序的复杂程度:时间和空间复杂度_衣冠の禽兽的博客-CSDN博客_怎么看程序的时间复杂度
2、动态规划算法
2.1动态规划基本概念与思想
动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
注意动态规划必须要先初始化!
2.2 3种背包问题
sinat_yt的博客_CSDN博客-python,测试理论,无人机领域博主
背包问题(Java)动态规划_sinat_yt的博客-CSDN博客_java动态规划背包问题
2.2常见动态规划问题
2.2.1找零钱问题
有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,请返回有多少种方法可以凑成aim。
给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。
测试样例:
[1,2,4], aim=3
返回:2
解析:设dp[n][m]为使用前n中货币凑成的m的种数,那么就会有两种情况:
使用第n种货币:dp[n-1][m]+dp[n][m-peney[n]]
不用第n种货币:dp[n-1][m],为什么不使用第n种货币呢,因为penney[n]>m。
这样就可以求出当m>=penney[n]时 dp[n][m] = dp[n-1][m]+dp[n][m-peney[n]],否则,dp[n][m] = dp[n-1][m]
public static int Penny(int[] penny, int aim) {
int n=penny.length;
if(n==0||penny==null||aim<0){
return 0;
}
int[][] pd = new int[n][aim+1];
for(int i=0;i<n;i++){
pd[i][0] = 1;
}
for(int i=1;penny[0]*i<=aim;i++){
pd[0][penny[0]*i] = 1;
}
for(int i=1;i<n;i++){
for(int j=0;j<=aim;j++){
if(j>=penny[i]){
pd[i][j] = pd[i-1][j]+pd[i][j-penny[i]];
}else{
pd[i][j] = pd[i-1][j];
}
}
}
return pd[n-1][aim];
}
2.2.2走方格问题
有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
给定一个矩阵map,请返回最小路径和。
测试样例:
[[1,2,3],[1,1,1]]
返回:4
解析:设dp[n][m]为走到n*m位置的路径长度,那么显而易见dp[n][m] = min(dp[n-1][m]+map[i][j],dp[n][m-1]+map[i][j]);
public class MinimumPath {
public int getMin(int[][] map, int n, int m) {
// write code here
int[][] dp = new int[n][m];
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
dp[i][0]+=map[j][0];
}
}
for(int i=0;i<m;i++){
for(int j=0;j<=i;j++){
dp[0][i]+=map[0][j];
}
}
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
dp[i][j] = min(dp[i][j-1]+map[i][j],dp[i-1][j]+map[i][j]);
}
}
return dp[n-1][m-1];
}
public int min(int a,int b){
if(a>b){
return b;
}else{
return a;
}
}
}</span>
2.2.3最长公共序列数
给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最长公共子序列。
给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。保证两串长度均小于等于300。
测试样例:
"1A2C3D4B56",10,"B1D23CA45B6A",12
返回:6
解析:设dp[n][m] ,为A的前n个字符与B的前m个字符的公共序列长度,则当A[n]==B[m]的时候,dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]),否则,dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
<span style="font-size:18px;">import java.util.*;
public class LCS {
public int findLCS(String A, int n, String B, int m) {
// write code here
int[][] dp = new int[n][m];
char[] a = A.toCharArray();
char[] b = B.toCharArray();
//首先进行初始化
for(int i=0;i<n;i++){
if(a[i]==b[0]){
dp[i][0] = 1;
for(int j=i+1;j<n;j++){
dp[j][0] = 1;
}
break;
}
}
for(int i=0;i<m;i++){
if(a[0]==b[i]){
dp[0][i] = 1;
for(int j=i+1;j<m;j++){
dp[0][j] = 1;
}
break;
}
}
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
if(a[i]==b[j]){
dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]);
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[n-1][m-1];
}
public int max(int a,int b,int c){
int max = a;
if(b>max)
max=b;
if(c>max)
max = c;
return max;
}
}</span>
2.2.4 376. 摆动序列
class Solution {
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
if (n < 2) {
return n;
}
int[] up = new int[n];
int[] down = new int[n];
up[0] = down[0] = 1;
for (int i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
up[i] = Math.max(up[i - 1], down[i - 1] + 1);
down[i] = down[i - 1];
} else if (nums[i] < nums[i - 1]) {
up[i] = up[i - 1];
down[i] = Math.max(up[i - 1] + 1, down[i - 1]);
} else {
up[i] = up[i - 1];
down[i] = down[i - 1];
}
}
return Math.max(up[n - 1], down[n - 1]);
}
}
3、树与二叉树
3.1树与二叉树基本概念与思想
java数据结构与算法之树基本概念及二叉树(BinaryTree)的设计与实现
java数据结构与算法之树基本概念及二叉树(BinaryTree)的设计与实现_zejian_的博客-CSDN博客_java树
java数据结构与算法之平衡二叉树(AVL树)的设计与实现
java数据结构与算法之平衡二叉树(AVL树)的设计与实现_zejian_的博客-CSDN博客_java 平衡二叉树
3.1.1二叉树的前序中序后序遍历
前序遍历递归的流程示意图
测试用例用图来描述:
二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树,
根 -> 左 -> 右
递归+迭代解法:迭代运用了栈的思想
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
preorder(root, res);
return res;
}
//前序
public void preorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
res.add(root.val);
preorder(root.left, res);
preorder(root.right, res);
}
}
//迭代
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode node = root;
while (!stack.isEmpty() || node != null) {
while (node != null) {
res.add(node.val);
stack.push(node);
node = node.left;
}
node = stack.pop();
node = node.right;
}
return res;
}
}
左 -> 根 -> 右
递归+迭代解法:迭代运用了栈的思想
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
}
//迭代
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stk = new LinkedList<TreeNode>();
while (root != null || !stk.isEmpty()) {
while (root != null) {
stk.push(root);
root = root.left;
}
root = stk.pop();
res.add(root.val);
root = root.right;
}
return res;
}
}
左 -> 右 -> 根
先递归后迭代
//递归
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
postorder(root, res);
return res;
}
public void postorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
postorder(root.left, res);
postorder(root.right, res);
res.add(root.val);
}
}
//迭代
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode prev = null;
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (root.right == null || root.right == prev) {
res.add(root.val);
prev = root;
root = null;
} else {
stack.push(root);
root = root.right;
}
}
return res;
}
}
3.1.2 102. 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
class Solution {
List<List<Integer>> res=new ArrayList<List<Integer>>();
public void bianli(TreeNode root, int level){
if(level==res.size()){
res.add(new ArrayList<Integer>());
}
res.get(level).add(root.val);
if(root.left!=null){
bianli(root.left,level+1);
}
if(root.right!=null){
bianli(root.right,level+1);
}
}
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null){
return res;
}
bianli(root,0);
return res;
}
}
3.1.3 226. 翻转二叉树
翻转一棵二叉树。
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root==null ){
return null;
}
TreeNode left= invertTree(root.left);
TreeNode right= invertTree(root.right);
root.right=left;
root.left=right;
return root;
}
}
3.1.4 101. 对称二叉树
class Solution {
public boolean check(TreeNode leftnode,TreeNode rightnode) {
if(leftnode==null && rightnode==null) {
return true;
}
if(leftnode==null || rightnode==null){
return false;
}
if(leftnode.val!=rightnode.val){
return false;
}
return check(leftnode.left,rightnode.right)&&check(leftnode.right,rightnode.left);
}
public boolean isSymmetric(TreeNode root) {
return check(root,root);
}
}
3.15 平衡二叉树 AVL树
平衡二叉树的实现(java代码)_随性而活的风的博客-CSDN博客_二叉平衡树java实现
3.2树与二叉树经典例题
3.2.1 104. 二叉树的最大深度
按照递归三部曲,来看看如何来写。
-
确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。
int Depth(TreeNode root)
-
确定终止条件:如果为空节点的话,就返回0,表示高度为0。
if(root==null){ return 0; }
-
确定单层递归的逻辑:先求它的左子树的深度,再求的右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
int depthl=Depth(root.left); int depthr=Depth(root.right); return Math.max(depthl,depthr)+1;
class Solution { public int Depth(TreeNode root){ if(root==null){ return 0; } int depthl=Depth(root.left); int depthr=Depth(root.right); return Math.max(depthl,depthr)+1; } public int maxDepth(TreeNode root) { return Depth(root); } }
3.2.2 110. 平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
class Solution {
//计算每个节点的最大高度
public int height(TreeNode root){
if(root==null){
return 0;
}
int lheight=height(root.left);
int rheight=height(root.right);
return Math.max(lheight,rheight)+1;
}
//计算每个节点是否是平衡,如果有一个节点不平衡则返回false
public boolean check(TreeNode root){
if(root==null){
return true;
}
if(Math.abs(height(root.left)-height(root.right))>1){
return false;
}
return check(root.left)&&check(root.right);
}
public boolean isBalanced(TreeNode root) {
return check(root);
}
}
3.2.3 257. 二叉树的所有路径
给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> paths = new ArrayList<String>();
constructPaths(root, "", paths);
return paths;
}
public void constructPaths(TreeNode root, String path, List<String> paths) {
if (root != null) {
StringBuffer pathSB = new StringBuffer(path);
pathSB.append(Integer.toString(root.val));
if (root.left == null && root.right == null) { // 当前节点是叶子节点
paths.add(pathSB.toString()); // 把路径加入到答案中
} else {
pathSB.append("->"); // 当前节点不是叶子节点,继续递归遍历
constructPaths(root.left, pathSB.toString(), paths);
constructPaths(root.right, pathSB.toString(), paths);
}
}
}
}
4、回溯算法
4.1回溯基本概念与思想
回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了。
大家可以从图中看出「for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历」,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
4.2回溯经典例题
4.2.1 77. 组合
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。你可以按 任何顺序 返回答案。
class Solution {
List<List<Integer>> res= new ArrayList<List<Integer>>();
Deque<Integer> path=new LinkedList<Integer>();
//回溯,可以想象成一棵树
public void backtracking(int n, int k, int startindex){
if(path.size()==k){
res.add(new ArrayList<Integer>(path));
return;
}
for(int i=startindex;i<=n;i++){
//处理节点
path.addLast(i);
System.out.println("递归之前 => " + path);
backtracking(n,k,i+1);
path.removeLast(); 回溯,撤销处理的节点
System.out.println("递归之后 => " + path);
}
}
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return res;
}
}
4.2.2 17. 电话号码的字母组合
5、贪心算法
5.1 贪心基本概念与思想
贪心算法一般分为如下四步:
-
将问题分解为若干个子问题
-
找出适合的贪心策略
-
求解每一个子问题的最优解
-
将局部最优解堆叠成全局最优解
5.2 贪心经典例题
5.2.1 455. 分发饼干
排序+贪心
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i
,都有一个胃口值 g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j
,都有一个尺寸 s[j]
。如果 s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
class Solution {
public int findContentChildren(int[] g, int[] s) {
//贪心算法,尽可能完成胃口最小的
int res=0;
Arrays.sort(g);
Arrays.sort(s);
int leng=g.length;
int lens=s.length;
int i=0;
int j=0;
for(;i<leng&&j<lens;i++,j++){
//找到满足胃口的最小尺寸饼干
while(j<lens&&s[j]<g[i]){
j++;
}
if(j<lens){
res++;
}
}
return res;
}
}
5.2.2 452. 用最少数量的箭引爆气球
class Solution {
public int findMinArrowShots(int[][] points) {
if (points.length == 0) {
return 0;
}
//按照右边界从小到大排序
Arrays.sort(points,new Comparator<int []>(){
public int compare(int[] o1, int[] o2){
if(o1[1]>o2[1]){
return 1;
}
if(o1[1]<o2[1]){
return -1;
}
else{
return 0;
}
}
});
//第一支剑第一个的最右边
int pre=points[0][1];
int res=1;
for(int [] balloon:points){
if(balloon[0]>pre){
pre=balloon[1];
++res;
}
}
return res;
}
}
6、DFS/BFS:深度优先搜索 /广度优先搜索
6.1 DFS/BFS基本概念与思想
6.2 DFS/BFS经典例题
6.2.1 994. 腐烂的橘子
在给定的网格中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。
返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。
class Solution {
public int orangesRotting(int[][] grid) {
// 边界 长宽
int M = grid.length;
int N = grid[0].length;
Deque<int[]> queue = new LinkedList<>();
// count 表示新鲜橘子的数量
int count = 0;
// 遍历二维数组 找出所有的新鲜橘子和腐烂的橘子
for (int r = 0; r < M; r++) {
for (int c = 0; c < N; c++) {
// 新鲜橘子计数
if (grid[r][c] == 1) {
count++;
// 腐烂的橘子就放进队列
} else if (grid[r][c] == 2) {
// 缓存腐烂橘子的坐标
queue.add(new int[]{r, c});
}
}
}
// round 表示腐烂的轮数,或者分钟数
int round = 0;
// 如果有新鲜橘子 并且 队列不为空
// 直到上下左右都触及边界 或者 被感染的橘子已经遍历完
while (count > 0 && !queue.isEmpty()) {
// BFS 层级 + 1
round++;
// 拿到当前层级的腐烂橘子数量, 因为每个层级会更新队列
int n = queue.size();
// 遍历当前层级的队列
for (int i = 0; i < n; i++) {
// 踢出队列(拿出一个腐烂的橘子)
int[] orange = queue.poll();
// 恢复橘子坐标
int r = orange[0];
int c = orange[1];
// ↑ 上邻点 判断是否边界 并且 上方是否是健康的橘子
if (r-1 >= 0 && grid[r-1][c] == 1) {
// 感染它
grid[r-1][c] = 2;
// 好橘子 -1
count--;
// 把被感染的橘子放进队列 并缓存
queue.add(new int[]{r-1, c});
}
// ↓ 下邻点 同上
if (r+1 < M && grid[r+1][c] == 1) {
grid[r+1][c] = 2;
count--;
queue.add(new int[]{r+1, c});
}
// ← 左邻点 同上
if (c-1 >= 0 && grid[r][c-1] == 1) {
grid[r][c-1] = 2;
count--;
queue.add(new int[]{r, c-1});
}
// → 右邻点 同上
if (c+1 < N && grid[r][c+1] == 1) {
grid[r][c+1] = 2;
count--;
queue.add(new int[]{r, c+1});
}
}
}
// 如果此时还有健康的橘子
// 返回 -1
// 否则 返回层级
if (count > 0) {
return -1;
} else {
return round;
}
}
}