一、数学
1.1 素数
素数:只有1和本身两个因子。
2、3、5、7、11、13、17...
1) 单个判断
普通判断算法 O ( n ) O(\sqrt{n}) O(n)
public boolean isPrime(int n) {
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
稍快速的判断算法 O ( 1 2 n ) O({1\over2}\sqrt{n}) O(21n)
素数中只有一个2是偶数,其余都是奇数,这些奇素数不可能有偶因子
public boolean isPrime2(int n) {
if (n <= 1) return false;
if (n == 2) return true;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
2) 区间判断
循环套单个判断 O ( 1 2 n 3 2 ) O({1\over2}n^{3\over2}) O(21n23)
public List<Integer> getRangePrime(int start, int end) {
List<Integer> ans = new ArrayList<>();
for (int i = start; i <= end; i++)
if (isPrime(i)) ans.add(i);
return ans;
}
public boolean isPrime(int n) {
if (n <= 1) return false;
if (n == 2) return true;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
埃式筛
O
(
n
l
o
g
(
l
o
g
(
n
)
)
)
O(nlog(log(n)))
O(nlog(log(n)))
l
o
g
l
o
g
n
loglogn
loglogn上升极慢,上述复杂度几乎可以认为是线性
table
数组的值表示该下标对应的数字是否是素数
- 初始化
table
全部为true
i
遍历[2, sqrt(n)]
- 如果
table[i]
为true
,则j
遍历[i * i, n]
,全部table[j]
为false
,j
遍历步长为i
public int[] checkPrime(int n) { // 考察[2, n]区间的素数情况
boolean[] table = new boolean[n + 1];
Arrays.fill(table, true); // 初始化认为全是素数
for (int i = 2; i * i <= n; i++) { // 从2开始,遍历到sqrt(n)
if (table[i]) { // 如果当前是素数
// 就把从 i*i 开始,i 的所有倍数都设置为 false。
for (int j = i * i; j <= n; j += i) {
table[j] = false;
}
}
}
List<Integer> ans = new ArrayList<>();
for (int i = 2; i <= n; i++) {
if (table[i]) ans.add(i);
}
return ans.stream().mapToInt(i -> i).toArray();
}
1.2 最大公约数 & 最小公倍数
1) 最大公约数
循环写法
public static int getGCD(int a, int b) {
if (a < 0 || b < 0) {
return -1; // 数学上不考虑负数的约数
}
if (b == 0) {
return a;
}
while (a % b != 0) {
int temp = a % b;
a = b;
b = temp;
}
return b;
}
递归写法
public static int getGCD(int a, int b) {
if (a < 0 || b < 0) {
return -1; // 数学上不考虑负数的约数
}
if (b == 0) {
return a;
}
return a % b == 0 ? b : getGCD(b, a % b);
}
2) 最小公倍数
a和b的最小公倍数 = a ∗ b / g c d ( a , b ) a *b/gcd(a,b) a∗b/gcd(a,b)
二、排序
2.1 快排
public void quickSort(int[] nums, int left, int right) {
if (left >= right) return;
int mid = partition(nums, left, right);
quickSort(left, mid - 1);
quickSort(mid + 1, right);
}
private int partition(int[] nums, int left, int right) {
int r = (int)(Math.random() * (right - left + 1) + left);
int temp = nums[left];
nums[left] = nums[r];
nums[r] = temp;
temp = nums[left];
while (left < right) {
while (left < right && nums[right] < temp) right--;
nums[left] = nums[right];
while (left < right && nums[left] >= temp) left++;
nums[right] = nums[left];
}
nums[left] = temp;
return left;
}
2.2 归并
public int[] sortArray(int[] nums) {
mergeSort(nums, 0, nums.length - 1);
return nums;
}
private void mergeSort(int[] nums, int left, int right) {
if (left >= right) return;
int mid = left + ((right - left) >> 1);
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
merge(nums, left, mid, mid + 1, right);
}
private void merge(int[] nums, int l1, int r1, int l2, int r2) {
int[] temp = new int[nums.length];
int start = l1, cnt = 0;
while (l1 <= r1 || l2 <= r2) {
if (l1 <= r1 && l2 <= r2 && nums[l1] < nums[l2] || l2 > r2)
temp[cnt++] = nums[l1++];
else temp[cnt++] = nums[l2++];
}
System.arraycopy(temp, 0, nums, start, cnt);
}
2.3 堆排
public int[] heapSort(int[] a) {
int[] nums = new int[a.length + 1];
System.arraycopy(a, 0, nums, 1, a.length);
int tail = a.length;
for (int i = tail >> 1; i >= 1; i--) adjust(i, nums, tail);
for (int i = 0; i < a.length; i++) {
a[i] = nums[1];
nums[1] = nums[tail--];
adjust(1, nums, tail);
}
return a;
}
private void adjust(int i, int[] nums, int tail) {
int j = i << 1;
while (j <= tail) {
if (j + 1 <= tail && nums[j + 1] < nums[j]) j++;
if (nums[i] > nums[j]) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
i = j;
j = i << 1;
}
}
三、树
3.1 可更改数组的区间和
1)线段树
class NumArray {
private int[] nums;
private Node root;
public NumArray(int[] nums) {
this.nums = nums;
root = buildTree(0, nums.length - 1);
}
public void update(int index, int val) {
updateTreeNode(index, val, root);
}
public int sumRange(int left, int right) {
return queryRangeTree(left, right, root);
}
/* Segment Tree starts from here */
class Node {
int start, end, sum;
Node left, right;
public Node(int start, int end, int sum) {
this.start = start;
this.end = end;
this.sum = sum;
}
}
private Node buildTree(int start, int end) {
if (start == end) return new Node(start, end, nums[start]);
int mid = start + ((end - start) >> 1);
Node left = buildTree(start, mid);
Node right = buildTree(mid + 1, end);
Node node = new Node(start, end, left.sum + right.sum);
node.left = left;
node.right = right;
return node;
}
private void updateTreeNode(int idx, int val, Node root) {
if (root.start == root.end) {
root.sum = val;
return;
}
int mid = root.start + ((root.end - root.start) >> 1);
if (idx <= mid) updateTreeNode(idx, val, root.left);
else updateTreeNode(idx, val, root.right);
root.sum = root.left.sum + root.right.sum;
}
private int queryRangeTree(int start, int end, Node root) {
if (start == root.start && end == root.end) return root.sum;
int mid = root.start + ((root.end - root.start) >> 1);
if (end <= mid) return queryRangeTree(start, end, root.left);
else if (start > mid) return queryRangeTree(start, end, root.right);
else return queryRangeTree(start, mid, root.left) + queryRangeTree(mid + 1, end, root.right);
}
}
2)树状数组
class NumArray {
FenWickTree tree;
private int[] nums;
public NumArray(int[] nums) {
this.nums = nums;
tree = new FenWickTree(nums);
}
public void update(int index, int val) {
tree.updateTree(index + 1, val - nums[index]);
nums[index] = val;
}
public int sumRange(int left, int right) {
return tree.queryRangeFromHead(right + 1) - tree.queryRangeFromHead(left);
}
/** 树状数组开始 */
class FenWickTree {
private int[] tree;
public FenWickTree(int[] nums) {
tree = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
updateTree(i + 1, nums[i]); // 注意,FenwickTree的下标相比于原始数组右偏移一位
}
}
public void updateTree(int index, int delta) { // 注意,更新是基于增量的
while (index < tree.length) {
tree[index] += delta;
index += lowbit(index);
}
}
public int queryRangeFromHead(int index) { // 注意,查询是查询的从头到index的区间和
int ans = 0;
while (index > 0) {
ans += tree[index];
index -= lowbit(index);
}
return ans;
}
private int lowbit(int x) {
return x & -x;
}
}
}
四、链表
4.1 k个一组翻转链表
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 因为要有连接操作,所以要有pre和虚拟节点
ListNode dummy = new ListNode(), pre = dummy, groupStart = head;
dummy.next = head;
while (groupStart != null) {
ListNode[] res = reverseK(groupStart, k);
pre.next = res[0];
pre = res[1];
groupStart = res[1].next;
}
return dummy.next;
}
private ListNode[] reverseK(ListNode head, int k) {
// 翻转链表,先找到最后的非法节点
ListNode invalid = head;
int cnt = 0;
while (cnt++ < k && invalid != null) invalid = invalid.next; // invalid 向后走k个位置
// 如果cnt没有到预计最大值 k + 1,那么说明invalid == null中途退出,这样的话剩余不到k个节点
if (cnt < k + 1) return new ListNode[] {head, new ListNode()}; // 注意这边返回的链表尾非真实的链表尾,而是新建的,但是因为他的next是null,所以下次的start是null,退出循环了就,所以不影响
// 经典反转链表模板 ⭐️
ListNode pre = invalid, cur = head;
while (cur != invalid) {
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return new ListNode[] {pre, head};
}
}