算法模板
二分搜索
模板
int binarySearch(int[] nums, int val) {
int n = nums.length;
int left = -1, right = n;
while (left + 1 != right) {
int mid = left + (right - left) / 2;
if (nums[mid] < val) left = mid;
else right = mid;
}
return left;
}
使用
1、数据大小查找,以判断第一个
≥
5
\geq5
≥5的元素位置、最后一个
<
5
<5
<5的元素位置、第一个
>
5
>5
>5的元素位置、最后一个
≤
5
\leq5
≤5的元素位置为例。
第一个
≥
5
\geq5
≥5的元素位置:判断条件if (nums[mid] < val) left = mid
;返回值return right;
最后一个
<
5
<5
<5的元素位置:判断条件if (nums[mid] < val) left = mid
;返回值return left;
第一个
>
5
>5
>5的元素位置:判断条件if (nums[mid] >= val) left = mid
;返回值return right;
最后一个
≤
5
\leq5
≤5的元素位置:判断条件if (nums[mid] <= val) left = mid
;返回值return left;
2、二分答案题。
理解
将数组按边界分为blue和red,找出符合blue/red的值。
详解
例题
leetcode35. 搜索插入位置、leetcode704. 二分查找、leetcode275. H 指数 II、二分答案题
快速排序
模板
void quickSort(int[] nums, int left, int right) {
if (left < right) {
int temp = nums[left];
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] > temp) --j;
if (i < j) nums[i++] = nums[j];
while (i < j && nums[i] < temp) ++i;
if (i < j) nums[j--] = nums[i];
}
nums[i] = temp;
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
}
}
topK
模板
/**
* @param l 左边界索引
* @param r 右边界索引
* @param k 第k大
*/
int topK(int[] nums, int l, int r, int k) {
if (l >= r) return nums[l];
int i = l, j = r;
int t = nums[l];
while (i < j) {
while (i < j && nums[j] > t) --j;
if (i < j) nums[i++] = nums[j];
while (i < j && nums[i] < t) ++i;
if (i < j) nums[j--] = nums[i];
}
nums[i] = t;
if ((i - l + 1) >= k) return topK(nums, l, i, k);
else return topK(nums, i + 1, r, k - (i - l + 1));
}
归并排序
void mergeSort(int[] nums, int l, int r) {
if (l >= r) return;
int mid = (r - l) / 2 + l;
// 分别排序
mergeSort(nums, l, mid);
mergeSort(nums, mid + 1, r);
// 合并排好序的数组
int[] temp = new int[r - l + 1];
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r) {
if (nums[i] < nums[j]) temp[k++] = nums[i++];
else temp[k++] = nums[j++];
}
while (i <= mid) temp[k++] = nums[i++];
while (j <= r) temp[k++] = nums[j++];
// 更新nums数组
for (i = l, j = 0; i <= r; i++, j++) nums[i] = temp[j];
}
快速幂
模板
/**
* @param a 底数
* @param b 指数
*/
long pow(long a, long b) {
if (a == 1 || b == 0) return 1;
if (b < 0) {
a = 1 / a;
b = -b;
}
long res = 1;
while (b > 0) {
if ((b & 1) == 1) res *= a; // 如果b为奇数,则需要额外乘一次
a *= a;
b >>= 1;
}
return res;
}
理解
1、对于
a
7
a^7
a7来说,其值等于
a
∗
a
∗
a
∗
a
∗
a
∗
a
∗
a
a*a*a*a*a*a*a
a∗a∗a∗a∗a∗a∗a,因此时间复杂度为
O
(
n
)
O(n)
O(n)。可以使用分治的思想降低复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn),即首先计算
a
2
=
a
∗
a
a^2=a*a
a2=a∗a,那么
a
4
=
a
2
∗
a
2
a^4=a^2*a^2
a4=a2∗a2,而
a
7
=
a
4
∗
a
2
∗
a
a^7=a^4*a^2*a
a7=a4∗a2∗a。特殊的,从上述例子可以看出,当当前指数
b
b
b为奇数时,下次带入计算的指数
b
′
=
⌊
b
/
2
⌋
b'=\lfloor b/2 \rfloor
b′=⌊b/2⌋,因此需要额外乘以一次底数。
2、对于需要 快速幂取模 运算时,由性质
(
x
∗
y
)
%
m
=
x
%
m
∗
y
%
m
(x * y) \% m = x \% m * y \% m
(x∗y)%m=x%m∗y%m 可得:
while (b > 0) {
if ((b & 1) == 1) res *= a % mod; // 如果b为奇数,则需要额外乘一次
a *= a % mod;
b >>= 1;
}
详解
例题
leetcode 50. Pow(x, n)、leetcode 100155. 双模幂运算
树状数组
模板
class Fenwick {
private int[] tree;
private int n;
Fenwick(int n) {
this.n = n;
tree = new int[n + 1];
}
private int lowbit(int x) {
return x & -x;
}
// 查询前缀和
public int query(int x) {
int ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) ans += tree[i];
return ans;
}
public void add(int x, int val) {
for (int i = x; i <= n; i += lowbit(i)) tree[i] += val;
}
}
使用
class Main {
int[] nums;
Fenwick fenwick = new Fenwick(nums.length);
void init() {
for (int i = 0; i < nums.length; i += fenwick.lowbit(i)) {
fenwick.add(i + 1, nums[i]);
}
}
void update(int x, int val) {
fenwick.add(x + 1, val - nums[x]);
nums[x] = val;
}
// [left, right]
int sumRange(int left, int right) {
return fenwick.query(right + 1) - fenwick.query(left);
}
}
理解
为什么树状数组下标必须从1开始?
因为lowbit(int x)
方法中,当x
等于0时没有意义,返回0,因此当执行i += lowbit(i)
时,i
没有变化,陷入死循环。
详解
习题
leetcode307. 区域和检索 - 数组可修改、leetcode1409. 查询带键的排列
跳表
模板
class SkipTable {
private final int maxLevel = 16;
private final Random random = new Random();
private Node head;
public SkipTable() {
head = new Node(-1, -1, maxLevel);
}
private int randomLevel() {
int level = 1;
while (level < maxLevel && random.nextInt() % 2 == 0) level++;
return level;
}
void insert(int key, int data) {
Node preElem = head;
Node[] pres = new Node[maxLevel];
boolean flag = false; // 不重复插入
for (int i = maxLevel - 1; i >= 0; i--) {
for (Node next = preElem.level[i]; next != null; next = preElem.level[i]) {
if (next.key >= key) {
if (next.key == key) {
next.data = data; // 更新data
flag = true;
}
break;
}
preElem = next;
}
pres[i] = preElem;
}
if (flag) return;
int level = randomLevel();
Node node = new Node(key, data, level);
for (int i = level - 1; i >= 0; i--) {
node.level[i] = pres[i].level[i];
pres[i].level[i] = node;
}
}
int query(int key) {
Node preElem = head;
for (int i = maxLevel - 1; i >= 0; i--) {
for (Node next = preElem.level[i]; next != null; next = preElem.level[i]) {
if (next.key >= key) {
if (next.key == key) return next.data;
break;
}
preElem = next;
}
}
return -1;
}
void delete(int key) {
Node preElem = head;
Node[] pres = new Node[maxLevel];
int level = -1;
for (int i = maxLevel - 1; i >= 0; i--) {
for (Node next = preElem.level[i]; next != null; next = preElem.level[i]) {
if (next.key >= key) {
if (next.key == key && level == -1) {
level = i;
}
break;
}
preElem = next;
pres[i] = preElem;
}
}
for (int i = level; i >= 0; i--) {
pres[i].level[i] = pres[i].level[i].level[i];
}
}
class Node {
int key, data;
Node[] level; //level数组存放node在每一层的next节点
public Node(int key, int data, int level) {
this.key = key;
this.data = data;
this.level = new Node[level];
}
}
}
字典树
模板
class Tire {
private final Tire[] children; // 可替换为HashMap
private boolean isEnd;
public Tire() {
children = new Tire[26];
isEnd = false;
}
public void insert(String s) {
char[] ca = s.toCharArray();
Tire node = this;
for (char c : ca) {
if (node.children[c - 'a'] == null) {
node.children[c - 'a'] = new Tire();
}
node = node.children[c - 'a'];
}
node.isEnd = true;
}
public boolean search(String s) {
char[] ca = s.toCharArray();
Tire node = this;
for (char c : ca) {
if (node.children[c - 'a'] == null) return false;
node = node.children[c - 'a'];
}
return isEnd;
}
public String filter(String text, String rp) {
if (text.isBlank()) return text;
StringBuilder sb = new StringBuilder();
Tire node = this;
int l = 0, r = 0;
while (l < text.length()) {
node = node.children[text.charAt(r) - 'a'];
if (node == null) { // 以l开头的字符串未匹配中模板
sb.append(text.charAt(l));
l++;
r = l;
node = this; // 匹配成功或失败都要回到字典树根节点
} else if (node.isEnd) { // 以l开头的字符串匹配中模板
sb.append(rp);
r++;
l = r;
node = this;
} else {
r++;
if (r >= text.length()) { // 以l开头的剩余字符串未匹配中
sb.append(text.charAt(l));
l++;
r = l;
node = this;
}
}
}
return sb.toString();
}
}
习题
LRU
模板
/**
* 时间复杂度为O(1)的LRUCache实现
*/
class LRU{
int capacity;
HashMap<Integer, Node> map;
Node dummy; // 哨兵节点,作为队头
public LRU(int capacity){
this.capacity = capacity;
map = new HashMap<>();
dummy = new Node(-1, -1);
dummy.next = dummy;
dummy.pre = dummy;
}
public int get(int key) { // 取值操作
Node node = map.get(key);
if (node == null) return -1;
// 将最新访问的节点放入队头
remove(node);
pushFront(node);
return node.val;
}
public void put(int key, int value) { // 存值操作
Node node = map.get(key);
if (node != null) {
// 有则更新value
node.val = value;
remove(node);
} else {
node = new Node(key, value);
map.put(key, node);
}
// 将新加入的节点放入队头
pushFront(node);
if (map.size() > capacity) {
// 当超出容量时,删除队尾节点
map.remove(dummy.pre.key);
remove(dummy.pre);
}
}
private void pushFront(Node node) { // 放入队头
node.next = dummy.next;
node.pre = dummy;
dummy.next = node;
node.next.pre = node;
}
private void remove(Node node) { //删除
node.pre.next = node.next;
node.next.pre = node.pre;
}
class Node{
int key, val;
Node pre, next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
}
小根堆
模板
class MinHeap {
private final int[] nums;
private int size = 0;
private final int maxSize;
public MinHeap(int maxSize) {
this.maxSize = maxSize;
nums = new int[maxSize + 1];
}
public void push(int val) {
if (size >= maxSize) return;
nums[++size] = val;
up(size);
}
public void pop() {
if (size == 0) return;
swap(1, size);
size--;
down(1);
}
public int top() {
return size == 0 ? -1 : nums[1];
}
private void down(int idx) {
if (idx > size) return;
int l = idx * 2, r = idx * 2 + 1;
int minIdx = idx;
if (l <= size && nums[l] < nums[minIdx]) minIdx = l;
if (r <= size && nums[r] < nums[minIdx]) minIdx = r;
if (minIdx != idx) {
swap(minIdx, idx);
down(minIdx);
}
}
private void up(int idx) {
while (idx / 2 > 0 && nums[idx] < nums[idx / 2]) {
swap(idx, idx / 2);
idx /= 2;
}
}
private void swap(int idx1, int idx2) {
int temp = nums[idx1];
nums[idx1] = nums[idx2];
nums[idx2] = temp;
}
}
限流算法
令牌桶算法
class TokenBucket {
// 令牌桶容量
private final int capacity;
// 令牌每秒增加数量
private final int rate;
// 桶中当前剩余数量
private int tokens;
// 上次拿令牌时间戳
private long lastTime;
public TokenBucket(int capacity, int rate) {
this.capacity = capacity;
this.rate = rate;
this.tokens = capacity;
lastTime = System.currentTimeMillis();
}
/**
* @param acquires 要获取的令牌数量
*/
public synchronized boolean tryAcquire(int acquires) {
long now = System.currentTimeMillis();
long gap = now - lastTime;
int newTokens = (int) (gap / 1000 * rate);
tokens = Math.min(capacity, tokens + newTokens);
lastTime = now;
if (acquires < tokens) {
tokens -= acquires;
return true;
} else {
return false;
}
}
}