【算法之美】常见算法速成篇-例题+解法特点分析(一道顶一万道)

这篇博客深入探讨了数据结构中的优先队列、StringBuilder/StringBuffer、栈、HashSet等,并举例说明了它们在实际问题中的应用。此外,还详细介绍了二叉树、线性查找和二分查找的原理及实现。最后,文章涵盖了回溯、深度优先搜索(DFS)、广度优先搜索(BFS)以及并查集的概念和实例。
摘要由CSDN通过智能技术生成

🔰数据结构

优先队列(堆)

直接给你排好

// 新建小根堆
PriorityQueue<Type> pq = new PriorityQueue<Type>();

🎲StringBuilder/StringBuffer(可变字符串)

Builder 速度快

例💡

在这里插入图片描述

public class Solution {
    public String replaceSpace(String s) {
        if (s == null) {
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (char c : s.toCharArray()){
            if (c == ' ') {
                stringBuilder.append("%20");

            }
            else {
                stringBuilder.append(c);
            }
        }
        return stringBuilder.toString();

    }
}

🎲Stack(栈)

例💡

在这里插入图片描述

class Solution {
    public int[] reversePrint(ListNode head) {
        Stack stack = new Stack<>();
        ListNode nowNode = head;
        while (nowNode != null) {
            stack.push(nowNode.val);
            nowNode = nowNode.next;
        }
//        尤为注意,不要节省代码,要在遍历前将size 拿出来 否则stack.pop() stack.size()会变化
        int size  = stack.size();
        int[] res = new int[size];
        for (int i = 0; i < size; i++) {
            res[i] = (int) stack.pop();
        }
        return res;

    }
}

🎲HashSet

例💡

在这里插入图片描述

public class Solution {
    public int findRepeatNumber(int[] nums) {
//        看到不重复,想到hashSet
        HashSet unDiffrent = new HashSet();
        for (int num: nums) {
            if (unDiffrent.contains(num)) {
                return num;
            }
            unDiffrent.add(num);
        }
//        没有重复和nums为null
        return -1;
    }

}

二叉树

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;
    }
}

🎲线性查找

特点🙉

矩阵,自左向右,自上而下递增,可以根据左下或右上,动态判断方向,减少时间复杂度

关键

只要存在边界内-找条加减对角线

例💡

在这里插入图片描述

public class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
//      边界条件  重要!!! 输入null,[] [[],[],[],[]] 这几种情况
        if (matrix == null||matrix.length == 0||matrix[0].length == 0) {
            return false;
        }
        int x = matrix[0].length-1,y=0;
//        到左到达边界时停止
        while (x>=0&&y<matrix.length){
            System.out.println(x+","+y);
            int now_number = matrix[y][x];
            if (now_number == target) {
                return true;
            }
            else if (target<now_number){
//                左移
                x--;
            }
//            下移动
            else {
                y++;
            }
        }
        return false;
    }
}

🎲二分查找

💪关键

找到中值-校验-移动左右索引

例💡

在这里插入图片描述

class Solution {
	/**
	 * 
	 * @param nums 被查找数组
	 * @param targht 查找值
	 * @return
	 */
	public int binarySearch(int[]nums,int targht) {
//		边界条件 
		if (nums == null) {
			return -1;
		}
//		对其排序
//		Arrays.sort(nums);
//		左索引,右索引
		int left = 0,right=nums.length-1;
		while (left<=right) {
//			获取中位位置 = 当前位置右移距离/2
			int mid = left + (right-left + 1)/2;
			if (nums[mid] == targht) {
				return mid;
			}
//			比中位值大
			else if (nums[mid] < targht) {
//				左索引放到中位右边
				left = mid+1;
				
			}
			else {
//				右索引放中位左边
				right = mid-1;
			}
		}
		
		return -1;
	}

}
}

在这里插入图片描述

public class Solution {
    public int findRadius(int[] houses, int[] heaters) {
//    	初始化最小半径
        int ans = 0;
//        对供暖器排序
        Arrays.sort(heaters);
//        遍历所有房屋
        for (int house : houses) {
//        	传入所有供暖期位置和当前房子位置,获取最靠近该房子的左供暖器位置
            int i = binarySearch(heaters, house);
//            右供暖器位置
            int j = i + 1;
//            左供暖器位置不为-1(-1为左边没有任何供暖器) 求得左距离
            int leftDistance = i < 0 ? Integer.MAX_VALUE : house - heaters[i];
//           右供暖器位置不为最右那个供暖器(右边没有任何供暖器) 求得右距离
            int rightDistance = j >= heaters.length ? Integer.MAX_VALUE : heaters[j] - house;
//            去左距离与右距离最小为半径
            int curDistance = Math.min(leftDistance, rightDistance);
//            比对更新最大半径
            ans = Math.max(ans, curDistance);
        }
        return ans;
    }
/**
 * 【核心】
 * @param nums 所有供暖器
 * @param target 当前房屋
 * @return 左索引
 */
    public int binarySearch(int[] nums, int target) {
//    	左索引,右索引
        int left = 0, right = nums.length - 1;
//        当前房屋在所有供暖器最左边 返回-1
        if (nums[left] > target) {
            return -1;
        }
//        直到左索引到供暖器最右边,结束循环 
//        目的:将左右缩小到最靠近当前房屋的位置
        while (left < right) {
//        	当前供暖器位置与最右位置的中位
            int mid = (right - left + 1) / 2 + left;
//          如果中位在当前房屋右边
            if (nums[mid] > target) {
//            	右位置向左移
                right = mid - 1;
            } else {
//            	左位置向右移动
                left = mid;
            }
        }
        return left;
    }
}

🎲回溯

概念

回溯算法是对树形或者图形结构执行一次深度优先遍历,实际上类似枚举的搜索尝试过程,在遍历的过程中寻找问题的解。
深度优先遍历有个特点:当发现已不满足求解条件时,就返回,尝试别的路径。此时对象类型变量就需要重置成为和之前一样,称为「状态重置」

关键

区分变于不变量-史前做好初始化-组合一探一退格-全局index位置先行

例💡

在这里插入图片描述

public class Solution {
//	映射
	Map<Character, String> number2char = new HashMap() {{
	put('2',"abc");
	put('3',"def");
	put('4',"ghi");
	put('5',"jkl");
	put('6',"mno");
	put('7',"pqrs");
	put('8',"tuv");
	put('9',"wxyz");
	}};
//	存放结果
	ArrayList<String> res = new ArrayList<String>();
//  当前组合
	StringBuffer combination = new StringBuffer();
//  输入进来的号码
	String digits;
//	回溯算法
	public void backtrack(int index) {
//      index:当前字母位置
//		递归结束条件:组合长度等于输入数字长度
		if(combination.length() == digits.length()) {
			res.add(combination.toString());
			return;
		}else {
//			通过map查出当前号码所有字母
			String nowChars = number2char.get(digits.toCharArray()[index]);
//			组合里加字母
			for (char  nowChar: nowChars.toCharArray()) {
				combination.append(nowChar);
//				递归
				backtrack(index+1);
//				退格(为了下一种情况)
				combination.deleteCharAt(combination.length()-1);
			}
		} 
	}

//	执行层
	public List<String> letterCombinations(String digits) {
//		边界条件
		if (digits == null || digits.isEmpty()) return res;
		this.digits = digits;
		backtrack(0);
		return res;
	}
}

🎲dfs

关键

失败情况写在前(到达边界+不满足)- 衔接成功少不了-上下左右递归或满足

例💡

在这里插入图片描述

public class Solution {
    /**
     * 深度优先搜索
     * @param board 矩阵
     * @param words 字符数组
     * @param i 矩阵行位
     * @param j 矩阵列位
     * @param k 字符位置
     * @return
     */
    public boolean dfs(char[][] board,char[]words,int i,int j,int k){
//        递归结束条件
//        失败 超出边界/当前点不等于当前字符
        if(i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] != words[k]) return false;
//        成功 对应完所有 字符
        if (k == words.length-1) return true;
//        特殊字符标记当前位置,避免重复
        board[i][j] = '\0';
//        递归 下右上左
        boolean result = dfs(board, words, i+1, j, k+1)||dfs(board, words, i, j+1, k+1)||dfs(board, words, i-1, j, k+1)||dfs(board, words, i, j-1, k+1);
//        还原
        board[i][j] = words[k];
        return result;

    }
    public boolean exist(char[][] board, String word) {
//        将word转字符数组
        char[] words = word.toCharArray();
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
//                将每一个点作为开始进行搜素 当前节点不行则看看下一个节点,而不是结束
                if (dfs(board,words,i,j,0)) return true;
            }

        }
        return false;
    }
}

bfs

特点

  • 从根结点开始,沿着树的宽度遍历树的结点。如果所有结点均被访问,则算法中止
  • 主要解决 路径,矩阵等问题,跟上下左右有关系的问题
  • 列表队列+while循环

关键

初始化个多队列(节点,值)-while判空做循环-终止语句写在前-成功失败来嵌套-上下左右探一探-能够走通加队列

在这里插入图片描述

public class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
//        边界阻挡
        if (root == null) {
            return false;
        }
//        创建当前节点队列
        Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
//        创建存储当前路径和队列
        Queue<Integer> valueQueue = new LinkedList<Integer>();
//        offer 约等于 add 不用担心大小限制
        nodeQueue.offer(root);
        valueQueue.offer(root.val);
        while (!nodeQueue.isEmpty()) {
//            成功
            TreeNode nowNode = nodeQueue.poll();
            int temp = valueQueue.poll();
//            到达根节点
            if (nowNode.left == null&&nowNode.right == null){
                if (temp == targetSum) {
                    return true;
                }
//            节省内存,避免后面的运算
                continue;
            }
            //右子树
            if (nowNode.right == null){
                nodeQueue.offer(nowNode.right);
                valueQueue.offer(nowNode.right.val+temp);
            }
            //左子树
            if(nowNode.left == null) {
                nodeQueue.offer(nowNode.left);
                valueQueue.offer(nowNode.left.val+temp);
            }
        }
        return false;

    }
}

🎲并查集

特点🙉

用来解决:连通分量的问题
并查集跟树有些类似,只不过她跟树是相反的。在树这个数据结构里面,每个节点会记录它的子节点。在并查集里,每个节点会记录它的父节点。
如果节点相互连通,即在同一棵树上,祖先节点相同
第一个节点添加到并查集,父节点为空

概念

  • 并(Union),代表合并(认贼作父)
public void merge(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        
        if (rootX != rootY){
            father.put(rootX,rootY);
        }
    }
  • 查(Find),代表查找(寻祖)
public int find(int x) {
        int root = x;
        
        while(father.get(root) != null){
            root = father.get(root);
        }
         
        return root;
    }

在此基础上,可做优化(压缩),发现是底层节点,移至二级节点,方便下次找祖宗,我称为"返祖"

public int find(int x) {
        int root = x;
        while(father.get(root) != null){
            root = father.get(root);
        }
        // 返祖
        while(x != root){
        	// 获取查找的节点的麻麻
            int original_father = father.get(x);
            // 将查找的节点和找到的祖宗添加到并查集(返租成功)
            father.put(x,root);
            // x 指向它的麻麻,让其麻麻进入下一轮返祖试炼
            x = original_father;
        }
        return root;
    }
  • 集(Set),代表这是一个以字典为基础的数据结构,它的基本功能是合并集合中的元素,查找集合中的元素
class UnionFind {
	// 此map用来记录该节点的父节点
    private Map<Integer,Integer> father;
}

综合结构

class UnionFind {
	// 集
    private Map<Integer,Integer> father;
    public UnionFind() {
        father = new HashMap<Integer,Integer>();
    }
    // 添加
    public void add(int x) {
        if (!father.containsKey(x)) {
            father.put(x, null);
        }
    }
    // 并
    public void merge(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        
        if (rootX != rootY){
            father.put(rootX,rootY);
        }
    }
    // 查
    public int find(int x) {
        int root = x;
        
        while(father.get(root) != null){
            root = father.get(root);
        }
        
        while(x != root){
            int original_father = father.get(x);
            father.put(x,root);
            x = original_father;
        }
        
        return root;
    }
    // 判断两个节点是否处于同一个连通分量(看祖宗是否相同)
    public boolean isConnected(int x, int y) {
        return find(x) == find(y);
    }
} 

例💡

在这里插入图片描述

package shengFenShuLiang;

import java.util.HashMap;
import java.util.Map;

class UnionFind{
//	集
	public Map<Integer, Integer> father;
//	省份数量
	public int shengFenNumbr;
	public UnionFind() {
		father = new HashMap<Integer, Integer>();
		shengFenNumbr = 0;
	}
//	添加
	public void add(int node) {
		if(!father.containsKey(node)) {
			father.put(node,null);
		}
//		省份数量+1
		++shengFenNumbr;
	}
//	并
	public void marge(int node1,int node2) {
//		找其祖宗
		int node1Root = find(node1);
		int node2Root = find(node2);
//		两大族群联合(族长大型认爹仪式)
		if (node1Root != node2Root){
			father.put(node1Root, node2Root);
			--shengFenNumbr;
		}
		
	}
	
//	查
	public int find(int node) {
//		初始化自己为祖节点
		int root = node;
//		结束条件: 父节点为null
		while(father.get(root) != null) {
			root = father.get(root);
		}
//		返祖
		if(node!=root) {
//			找到自己麻麻(不忘本)
			int maMa = father.get(node);
//			返祖
			father.put(node, root);
//			麻麻进入下一轮返祖计划
			node = maMa;
		}
		
		return root;
	}
//	判断是否连通
	public boolean isConnected(int node1,int node2) {
		return find(node1) == find(node2);
		
	}
//	获取省份数量
	public int getShengFenShuNumber() {
		return shengFenNumbr;
	}
	
}
public class Solution {
	public int findCircleNum(int[][] isConnected) {
//		实例化一个并查集
		UnionFind unionFind = new UnionFind();
//		遍历所有关系
		for(int i = 0;i < isConnected.length; i++) {
//			将i添加到并查集
			unionFind.add(i);
//			并关系
			for(int j = 0; j < isConnected[i].length; j++) {
				if (isConnected[i][j] == 1){
//					合并
					unionFind.marge(i, j);
				}
				
			}
		}
		return unionFind.getShengFenShuNumber();
	}
}

双指针

关键

  • 处理的数据结构是数组
  • 解决低空间复杂度问题 双指针空间复杂度O(1)

类型

同向(快慢、交换)

左阴右阳,一阴一阳协同完成任务,缺一不可,阴主后方守,阳主探索攻

例-交换

在这里插入图片描述

public class Solution {
    public int removeDuplicates(int[] nums) {
//        边界条件 边界条件 输入如果是数组,考虑null,空数组
        if (nums==null)return 0;
//        创建同向双指针
        int p = 0,q = 1;
        while (q < nums.length) {
            if (nums[q] != nums[p]) {
//                交换
                nums[p+1] = nums[q];
                p++;
            }
            q++;

        }
        return p+1;
    }
}
例-快慢
例-快慢(同并归)

反向

例(同贪心)

在这里插入图片描述

public class Solution {
    public int numRescueBoats(int[] people, int limit) {
        if(people==null||limit<1)return 0;
//        排序
        Arrays.sort(people);
//        左右指针
        int left = 0,right=people.length-1;1
//        船数
        int boatNum = 0;
//        相撞时结束 = 为什么,考虑到奇数个人
        while (left <= right) {
            // 体重重的一个人能坐上船
            if (people[right]<=limit){
                ++boatNum;
            }
            if (people[left]+people[right]<=limit) {
//                先自加再赋值
                ++left;
            }
            --right;
        }
        return boatNum;
    }
}

贪心

关键

  • 大多是处理数组问题最优组合
  • 先对数组排序
  • 追求局部最优解
  • 使用贪心算法解决的问题必须具备「无后效性」,即某个状态以前的过程不会影响以后的状态,只与当前状态有关,这也是它与动态规划的区别

关键

先看有无后效性-有序排列先安排-小换大,大换小

在这里插入图片描述

public class Solution {
    public int numRescueBoats(int[] people, int limit) {
        if(people==null||limit<1)return 0;
//        排序
        Arrays.sort(people);
//        左右指针
        int left = 0,right=people.length-1;1
//        船数
        int boatNum = 0;
//        相撞时结束 = 为什么,考虑到奇数个人
        while (left <= right) {
            // 体重重的一个人能坐上船
            if (people[right]<=limit){
                ++boatNum;
            }
            if (people[left]+people[right]<=limit) {
//                先自加再赋值
                ++left;
            }
            --right;
        }
        return boatNum;
    }
}

在这里插入图片描述
此题有两种局部最优解决思路:

  1. 优先满足当前饱腹感最小的,小对小,对不上,换大一点的饼干
  2. 优先满足当前饱腹感最大的,大对大,对不上,换小一点的孩子
    这里选取第二种,代码量相对较少
public class Solution {
    /**
     * for循环+计数器模式
     * @param g 孩子数组
     * @param s 饼干数组
     * @return
     */
    public int findContentChildren(int[] g, int[] s) {//s:饼干
//        排序
        Arrays.sort(g);
        Arrays.sort(s);
//        满足孩子数
        int count = 0;
//        饼干起点
        int start = s.length - 1;//饼干数量

        for(int index = g.length - 1; index >= 0; index--){
//        start>=0 限制饼干分完     
            if(start>=0&&g[index] <= s[start]){
                count++;
                start--;
            }
        }
        return count;
    }
}

动态规划

  • 自顶向下即记忆化递归,自底向上就是递推
  • 求解方案数,最小数,最大数这种不需要具体方案的问题
  • 当前最佳数量受上一级数量影响

关键

总结与前相关性(方程,当前最优何得来)- 滑动窗口少不了-在内在外只为更新最优秀的状态
在这里插入图片描述

class Solution {
    public int climbStairs(int n) {
        int p = 0, q = 0, r = 1;
        for (int i = 1; i <= n; ++i) {
            p = q; 
            q = r; 
            r = p + q;
        }
        return r;
    }
}

在这里插入图片描述

public class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

递归

  • 函数自调
  • 一个简单的基本案例,能够不使用递归来产生答案的终止方案。
  • 一组规则,可将所有其他情况拆分到基本案例。

在这里插入图片描述

public class Solution {
    /**
     * 
     * @param root 当前根节点
     * @param targetSum 当前路径剩余要求的和
     * @return
     */
    public boolean hasPathSum(TreeNode root, int targetSum) {
//        边界
        if (root == null) {
            return false;
        }
//        非递归的结束条件 到根停止
        if (root.left == null&&root.right == null) {
            return targetSum == root.val;
        }
//        左子树,右子树任意满足
        return hasPathSum(root.left, targetSum-root.val) || hasPathSum(root.right,targetSum - root.val);

    }
}

归并排序

精髓

快慢指针-循环得到慢指针指向中点位置(注意奇偶)- 左右迭代-构建新链表-比较大小换到下-判空确实优选择

在这里插入图片描述

public class Solution {
    public ListNode sortList(ListNode head) {
//        边界条件
//        1.作为输入时校验
//        2.递归的结束条件head.next == null就是没有下一个节点即二分到只剩下一个节点结束
        if (head == null || head.next == null)
            return head;
//        创建快慢两个指针,分别指向头节点的下一个节点和头节点
        ListNode fast = head.next, slow = head;
//        直到快指针指向右侧边界外且快指针的下一个节点为空停止
//        fast.next != null 防止总数为偶数时,快指针指向最后一个节点,
//        任何一个条件不满足,循环都到结束
        while (fast != null && fast.next != null) {

//            慢指针指向慢指针的下一个节点
            slow = slow.next;
//            快指针指向快指针的下下一个节点

            fast = fast.next.next;
//            等于两个指针向→做1步长和2步长的滑动窗口
//            slow一下走一步,fast一下走两步
//            走得快的走完,走得慢的正好到中点
        }
//      从中心阻断 此时 head为左段头节点,tmp为右段头节点
        ListNode tmp = slow.next;
        slow.next = null;
//        递归
        ListNode left = sortList(head);
        ListNode right = sortList(tmp);
//        创建值为0的节点
        ListNode h = new ListNode(0);
//        res指针指向这个节点
        ListNode res = h;
//        左右任何一个为空,循环结束
        while (left != null && right != null) {
//            都不为空
//            在h节点后按大小排列拼接left/right
            if (left.val < right.val) {
                h.next = left;
//                left指针指向自己的下一个元素?
                left = left.next;
            } else {
                h.next = right;
                right = right.next;
            }
//            h 指向h的下一个节点,即right或
            h = h.next;
        }
//        判断左节点是否为空(两种原因造成:1.left = left.next;2.只有左节点)
//        不为空指向左反之则右,防止h->left->left 前面做了左节点后滑 不论如何上方的哪种情况都会正确
//        变成如下局面 h->left / h->right / h->left1->left2 / h->left1->right
//        这句代码是精髓,K神的厉害之处,节省了很多代码和判断语句
        h.next = left != null ? left : right;
//        返回 h的下一个节点 如果当前为顶层,则把0节点后后排序好的值返回用户
//        如果是底层 则将 left1->right 返回到上级作为left/right 合并排序
        return res.next;
    }
}

最短路径

在这里插入图片描述

Floyd

public class Main {
	static int[][] graph = new int[2050][2050];
	static final int INF = 0x3f3f3f3f;
	
	private static void floyd() {
		for (int k = 1; k <= 2021; k++) {
			for (int i = 1; i <= 2021; i++) {
				for (int j = 1; j <= 2021; j++) {
					if (i != j && graph[i][j] > graph[i][k] + graph[k][j]) {
						graph[i][j] = graph[i][k] + graph[k][j];
					}
				}
			}
		}
	}
	
	private static int gcd(int a, int b) {
		return b == 0 ? a : gcd(b, a % b);
	}
	
	public static void main(String[] args) {
		for (int i = 1; i <= 2021; i++) {
			for (int j = 1; j <= 2021; j++) {
				graph[i][j] = INF;
			}
		}
		
		for (int i = 1; i <= 2021; i++) {
			int st = Math.max(i - 21, 1);
			for (int j = st; j <= i; j++) {
				int div = gcd(j, i);
				int lcm = i * j / div;
				graph[i][j] = lcm;
				graph[j][i] = lcm;
			}
		}
		
		floyd();
		
		System.out.println(graph[1][2021]); 
	}
	
}


Dijkstra

广度优先搜素
与广度优先搜素的区别是使用的队列为堆(优先队列)

public class Main {
    static class Edge {
//        指向谁,长度
        int to, length;
        Edge(int _to, int _length) {
            to = _to;
            length = _length;
        }
    }
    static List<Edge>[] graph;
    static final int INF = 0x3f3f3f3f;

    /**
     *
     * @param st 起点
     * @param ed 终点
     * @return
     */
    private static int dijkstra(int st, int ed) {
        // 新建小根堆
        PriorityQueue<Edge> pq = new PriorityQueue<>((a, b) ->
                Integer.compare(a.length, b.length));
//        距离
        int[] dist = new int[2050];
//        距离都填充正无穷大
        Arrays.fill(dist, INF);
//        开始距离初始化为0
        dist[st] = 0;
        // to 为点的编号,length 为当前路径长度
        pq.offer(new Edge(st, dist[st]));
        while (!pq.isEmpty()) {
//            弹出对队首(即最小)元素 并获取其连接节点
            int from = pq.poll().to;
//            遍历 它能指向的节点信息
            for (Edge next : graph[from]) {
//                获取指向节点编号,距离
                int to = next.to, len = next.length;
//                第一次:因为 初始化填充为无穷大,所以肯定指向其第一个遍历元素,这里会找到一个最短路径放到dist数组里,但堆里会放比其距离大的几个元素去探索
//                不过由于堆的特性,距离最小的考前,但都会计算
                if (dist[to] > dist[from] + len) {
                    dist[to] = dist[from] + len;
                    pq.offer(new Edge(to, dist[to]));
                }
            }
        }

        return dist[ed];
    }

    private static int gcd(int a, int b) {
//        a=21 b=1
//        如果b=0则返回a 反之递归
        return b == 0 ? a : gcd(b, a % b);
    }

    public static void main(String[] args) {

        graph = new List[2050];

        // 构建邻接表
        for (int i = 1; i <= 2021; i++) {
//             i=1 st = 1;i = 20,st=1;i>21 st=i
            int st = Math.max(i - 21, 1);
//            如果 i <= 21不进入循环 反之循环对应次
            for (int j = st; j <= i; j++) {
//              求最小公倍数  21,1
                int div = gcd(j, i);
                int lcm = i * j / div;
//                为 i与j.创建存储关系的数组
                if (graph[i] == null) {
                    graph[i] = new ArrayList<>();
                }
                if (graph[j] == null) {
                    graph[j] = new ArrayList<>();
                }
//                将 j 节点 与 最小公倍数 传入Edge
                graph[i].add(new Edge(j, lcm));
                graph[j].add(new Edge(i, lcm));
            }
        }

        System.out.println(dijkstra(1, 2021)); // 10266837
    }

}

SPFA

边界条件

  • 输入是数组 ,考虑 nums==null ,空数组 nums.length == 0
  • 输入是矩阵,考虑 null,nums.length == 0,nums[i].length == 0

数学

  • 最大公因数与最小公倍数
public class Test {
    /**
     * 欧几里得 最大公约数
     * @param a
     * @param b
     * @return
     */
    public static int gcd(int a, int b){
        return b == 0?a : gcd(b,a%b);
    }

    /**
     * 最小公倍数
     * @param a
     * @param b
     * @return
     */
    public static int lcm(int a, int b){
        return a*b/gcd(a,b);
    }
    public static void main(String[] args) {
        int a = 15,b=20;
        System.out.println("最大公约数:"+gcd(a,b));
        System.out.println("最小公倍数:"+lcm(a,b));
    }
}
  • 阶乘
public class Test {
    /**
     * 阶乘
     * @param num
     * @return
     */
    public static int recursion(int num){
        int sum=1;
        if(num==1){
            return 1;//根据条件,跳出循环
        }else{sum=num * recursion(num-1);//运用递归计算
            return sum;
        }

    }
    public static void main(String[] args) {
        int a = 2;
        System.out.println(recursion(4));
    }
}
  • 对数
float b = Math.log(a)
  • 无穷大
//整数
int INF = 0x3f3f3f3f;
//小数
double max=Double.POSITIVE_INFINITY;//无穷大
double maxx=Double.NEGATIVE_INFINITY;//无穷小
  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值