Algorithm Examples(算法示例)
1、 遍历字符串的连续子串
public static List<String> subStrList(String text) {
if (text == null || text.isEmpty()) {
return Collections.emptyList();
}
List<String> result = new ArrayList<>();
for (int start = 0; start < text.length(); start++) {
for (int end = start + 1; end <= text.length(); end++) {
result.add(text.substring(start, end));
}
}
return result;
}
2、 在字符串空隙中补充字符(也可遍历全部子串)
public static List<String> charPadding(String str, char pad) {
List<String> list = new ArrayList<>();
int n = str.length();
if (n <= 1) { // 处理边界情况:空字符串或单字符
list.add(str);
return list;
}
int gaps = n - 1; // 空隙数量 = 字符数 - 1
int total = 1 << gaps; // 总组合数:2^(gaps),位运算等价于2^gaps
// 遍历所有组合 (0 到 total-1),每次给mask+1即可将mask从00000000依次填充到11111111
for (int mask = 0; mask < total; mask++) {
StringBuilder sb = new StringBuilder();
// 遍历每个字符位置
for (int i = 0; i < n; i++) {
sb.append(str.charAt(i)); // 添加当前字符
//在当前字符后添加"+"的判断条件:
// 1. 不是最后一个字符
// 2. 当前位掩码为1(表示在此空隙添加+号),使用位运算检查第i位:(mask >> i) & 1
if (i < gaps && ((mask >> i) & 1) == 1) {
sb.append(pad);
}
}
list.add(sb.toString());
}
return list;
}
3、 判断一个整数是否是质数
/**
* 基础实现(试除法)
*/
public static boolean isPrimeBasic(int n) {
if (n <= 1) return false; // 1 和负数不是质数
if (n == 2) return true; // 2 是质数
if (n % 2 == 0) return false; // 排除偶数
// 检查奇数因子
for (int i = 3; i <= Math.sqrt(n); i += 2) {
if (n % i == 0) {
return false;
}
}
return true;
}
/**
* 优化版本(6k±1 定理)
*/
public static boolean isPrimeOptimized(int n) {
if (n <= 1) return false; // 1 和负数不是质数
if (n <= 3) return true; // 2,3 是质数
if (n % 2 == 0 || n % 3 == 0) return false; // 排除能被2或3整除的数
// 检查 6k±1 形式的因子
int sqrt = (int) Math.sqrt(n);
for (int i = 5; i <= sqrt; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) {
return false;
}
}
return true;
}
/**
* 大数优化(Miller-Rabin 概率测试),适用于超过 Integer.MAX_VALUE 的大数
*/
public static boolean isPrimeBigNumber(long n) {
return BigInteger.valueOf(n).isProbablePrime(10); // 10次测试,错误率<1/2^20
}
4、 分解质因数
public static List<Integer> factorize(int n) {
if (n == 0 || n == 1) {
return Collections.singletonList(n);
}
List<Integer> factors = new ArrayList<>();
int num = Math.abs(n);
if (n < 0) { // 处理符号
factors.add(-1);
}
while (num % 2 == 0) { // 处理偶数因子
factors.add(2);
num /= 2;
}
for (int i = 3; i * i <= num; i += 2) { // 处理奇数因子
while (num % i == 0) {
factors.add(i);
num /= i;
}
}
if (num > 2) { // 处理最后剩余的质数
factors.add(num);
}
return factors;
}
5、 匹配子字符串(基础算法)
public static int countOccurrences(String string, String subStr) {
if (string == null || subStr == null || subStr.isEmpty() || string.length() < subStr.length()) {
// 处理特殊情况
return 0;
}
int count = 0;
int strLen = string.length();
int subLen = subStr.length();
for (int i = 0; i <= strLen - subLen; i++) { // 遍历所有可能的起始位置
boolean match = true;
for (int j = 0; j < subLen; j++) { // 检查当前起始位置是否匹配整个subStr
if (string.charAt(i + j) != subStr.charAt(j)) {
match = false;
break;
}
}
if (match) {
count++;
}
}
return count;
}
6、 匹配子字符串(KMP算法)
public static int kmpCount(String string, String subStr) {
if (string == null || subStr == null || subStr.isEmpty()) return 0;
int count = 0;
int[] lps = computeLPS(subStr); // 计算部分匹配表
int i = 0, j = 0;
while (i < string.length()) {
if (string.charAt(i) == subStr.charAt(j)) {
i++;
j++;
if (j == subStr.length()) {
count++;
j = lps[j - 1]; // 回退到最长公共前后缀的位置
}
} else {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return count;
}
// 生成部分匹配表
private static int[] computeLPS(String pattern) {
int[] lps = new int[pattern.length()];
int len = 0; // 当前最长的公共前后缀长度
int i = 1;
while (i < pattern.length()) {
if (pattern.charAt(i) == pattern.charAt(len)) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
7、 一维动态规划(斐波那契数列,跳台阶)
public class Solution1 {
private int[] dp = new int[40];
public int jumpFloor (int number) {
if (number < 2) {
return 1;
}
dp[0] = dp[1] = 1;
for (int i = 2; i <= number; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[number];
}
}
public class Solution2 {
public int jumpFloor (int number) {
if (number < 2) {
return 1;
}
int d_1 = 1, d_2 = 1, dp = 0;
for (int i = 2; i <= number; i++) {
dp = d_1 + d_2;
d_2 = d_1;
d_1 = dp;
}
return dp;
}
}
8、 贪心算法(主持人调度)
- 首先建立两个数组分别存储开始时间(记为start)和结束时间(记为end)。
- 然后分别对start和end数组进行排序。
- 接着遍历start数组,判断当前开始时间是否大于等于最小的结束时间,如果是,则说明当前主持人就可以搞定(对应当前最小的结束时间的那个活动);如果否,则需要新增一个主持人,并将end数组下标后移(表示对应的活动已经有人主持)。
public class Solution {
public int minmumNumberOfHost (int n, int[][] startEnd) {
//初始化两个数组,分别记录开始时间和结束时间
int[] start = new int[n], end = new int[n];
//将活动的开始和结束时间赋值道start和end数组
for (int i = 0; i < n; i++) {
start[i] = startEnd[i][0];
end[i] = startEnd[i][1];
}
//按从小到大的顺序对start和end数组排序
Arrays.sort(start);
Arrays.sort(end);
int res = 0, idx = 0;
for (int i = 0; i < n; i++) {
if (start[i] >= end[idx]) {
idx++; //如果大于等于当前最小的结束时间,说明当前主持人可以搞定
} else {
res++; //否则,需要新增主持人
}
}
return res;
}
}
9、 递归(括号生成)
给出n对括号,请编写一个函数来生成所有的由n对括号组成的合法组合。\ 例如,给出n=3,解集为:"((()))", "(()())", "(())()", "()()()", "()(())"
public class Solution {
public ArrayList<String> generateParenthesis (int n) {
ArrayList<String> list = new ArrayList<>();
recursion(n, 0, 0, "", list);
return list;
}
public void recursion(int n, int l, int r, String temp, ArrayList<String> list) {
if (l == n && r == n) {
list.add(temp);
return;
}
if (l < n) { // 使用一次左括号
recursion(n, l + 1, r, temp + "(", list);
}
if (r < n && l > r) { // 使用一次左括号,且使用右括号个数必须少于左括号
recursion(n, l, r + 1, temp + ")", list);
}
}
}
10、 兑换零钱
给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。如果无解,请返回-1。
- step 1:可以用dp[i]dp[i]表示要凑出i元钱需要最小的货币数。
- step 2:一开始都设置为最大值aim+1aim+1,因此货币最小1元,即货币数不会超过aimaim.
- step 3:初始化dp[0]=0dp[0]=0。
- step 4:后续遍历1元到aim元,枚举每种面值的货币都可能组成的情况,取每次的最小值即可,转移方程为dp[i]=min(dp[i],dp[i−arr[j]]+1)dp[i]=min(dp[i],dp[i−arr[j]]+1).
- step 5:最后比较dp[aim]dp[aim]的值是否超过aim,如果超过说明无解,否则返回即可。
public class Solution {
public int minMoney (int[] arr, int aim) {
// Arrays.sort(arr);
if (aim < 0) {
return -1;
}
if (aim == 0) {
return 0;
}
//dp[i]表示凑齐i元最少需要多少货币数
int[] dp = new int[aim + 1];
Arrays.fill(dp, aim + 1);
dp[0] = 0;
for (int i = 1; i <= aim; i++) { // 遍历1-aim元
for (int j = 0; j < arr.length; j++) { // 每种面值的货币都要枚举
if (arr[j] <= i) { // 如果面值不超过要凑的钱才能用
dp[i] = Math.min(dp[i], dp[i - arr[j]] + 1); // 维护最小值
}
}
}
// 如果最终答案大于aim代表无解
return dp[aim] > aim ? -1 : dp[aim];
}
}
11、 最大公约数和最小公倍数
/**
* 计算两个数的最大公约数 - 递归版本
* @param a 第一个数
* @param b 第二个数
* @return 最大公约数
*/
public static long gcd(long a, long b) {
// 处理负数
a = Math.abs(a);
b = Math.abs(b);
// 基础情况
if (b == 0) return a;
// 递归调用
return gcd(b, a % b);
}
/**
* 计算两个数的最大公约数 - 迭代版本
* @param a 第一个数
* @param b 第二个数
* @return 最大公约数
*/
public static long gcdIterative(long a, long b) {
a = Math.abs(a);
b = Math.abs(b);
while (b != 0) {
long temp = b;
b = a % b;
a = temp;
}
return a;
}
/**
* 计算两个数的最小公倍数
* @param a 第一个数
* @param b 第二个数
* @return 最小公倍数
*/
public static long lcm(long a, long b) {
if (a == 0 || b == 0) return 0;
// LCM(a,b) = |a*b| / GCD(a,b)
return Math.abs(a * b) / gcd(a, b);
}
/**
* 安全的LCM计算,避免溢出
*/
public static long lcmSafe(long a, long b) {
if (a == 0 || b == 0) return 0;
a = Math.abs(a);
b = Math.abs(b);
long gcdValue = gcd(a, b);
// 先除后乘,避免溢出
return (a / gcdValue) * b;
}