个人算法笔记总结

240 阅读3分钟

用于总结个人在刷算法题时遇到的问题,同时记录一些特定的算法模版

字典树

通常用于判断字典中有无出现对应的字符串,或加快查找效率。

模版:

class TrieNode {
	boolean isWord;							
	TrieNode[] children = new TrieNode[26];		

	public TrieNode(){}		
}
class TrieNode {		
	boolean isWord;								
	TrieNode[] children = new TrieNode[26];		
}

class Trie {

    TrieNode root;      

    public Trie() {
        root = new TrieNode();      
    }

    public void insert(String word) {
        TrieNode cur = root;                        
        for (int i = 0; i < word.length(); i++) {	
            int c = word.charAt(i) - 'a';           
            if (cur.children[c] == null) {
                cur.children[c] = new TrieNode();
            }
            cur = cur.children[c];
        }
        cur.isWord = true;                          
    }
public boolean search(String word) {
        TrieNode cur = root;

        for (int i = 0; i < word.length(); i++) {
            int c = word.charAt(i) - 'a';
            if (cur.children[c] == null) {
                return false;
            }
            cur = cur.children[c];
        }
        return cur.isWord;
    }
    public boolean startsWith(String word) {
        TrieNode cur = root;

        for (int i = 0; i < word.length(); i++) {
            int c = word.charAt(i) - 'a';
            if (cur.children[c] == null) {
                return false;
            }
            cur = cur.children[c];
        }
        return true;
    }

search方法变式:

public boolean search(String word) {
	return match(word, root, 0);
}

/* macth方法
// 基本思路是:根据word和start得到此时的字符,然后看该字符是否与此时的节点node配对————即node.children[c]有值(!=null)
// (其实start就相当于非递归写法中的for(i)的i),用来遍历word
*/
public boolean match(String word, TrieNode node, int start){		// 这个三个参数直接背下来,这是模板参数
    if(start == word.length()){								
        return node.isWord;					// (★) 
    }

    int c = word.charAt(start) - 'a';
    return node.children[c] != null && match(word, node.children[c], start + 1);

推荐文章:blog.csdn.net/m0_46202073…

前缀和

频繁查某个区间的累加和

模版:


int n = nums.length;
// 前缀和数组
int[] preSum = new int[n + 1];
preSum[0] = 0;
for (int i = 0; i < n; i++)
    preSum[i + 1] = preSum[i] + nums[i];

推荐文章:zhuanlan.zhihu.com/p/107778275

差分数组

频繁对数组某个区间的值进行整体的增或者减

模版:

int[] diff = new int[nums.length];

// 构造差分数组

diff[0] = nums[0];

for (int i = 1; i < nums.length; i++) {

diff[i] = nums[i] - nums[i - 1];

}

通过这个 diff 差分数组是可以反推出原始数组 nums 的,代码逻辑如下:

int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
    res[i] = res[i - 1] + diff[i];
}

这样构造差分数组 diff ,就可以快速进行区间增减的操作,如果你想对区间 nums[i..j] 的元素全部加 3,那么只需要让 diff[i] += 3,然后再让 diff[j+1] -= 3 即可。

推荐文章: labuladong.gitbook.io/algo/mu-lu-…

滑动窗口

题目问法大致有这几种:

  • 给两个字符串,一长一短,问其中短的是否在长的中满足一定的条件存在,例如:
  • 求长的的最短子串,该子串必须涵盖短的的所有字符
  • 短的的 anagram 在长的中出现的所有位置
  • ...
  • 给一个字符串或者数组,问这个字符串的子串或者子数组是否满足一定的条件,例如:
  • 含有少于 k 个不同字符的最长子串
  • 所有字符都只出现一次的最长子串
  • ...

除此之外,还有一些其他的问法,但是不变的是,这类题目脱离不开主串(主数组)和子串(子数组)的关系,要求的时间复杂度往往是 O(n) ,空间复杂度往往是常数级的。

public int slidingWindowTemplate(String[] a, ...) {
    // 输入参数有效性判断
    if (...) {
        ...
    }

    // 申请一个散列,用于记录窗口中具体元素的个数情况
    // 这里用数组的形式呈现,也可以考虑其他数据结构
    int[] hash = new int[...];

    // 预处理(可省), 一般情况是改变 hash
    ...

    // l 表示左指针
    // count 记录当前的条件,具体根据题目要求来定义
    // result 用来存放结果
    int l = 0, count = ..., result = ...;
    for (int r = 0; r < A.length; ++r) {
        // 更新新元素在散列中的数量
        hash[A[r]]--;

        // 根据窗口的变更结果来改变条件值
        if (hash[A[r]] == ...) {
            count++;
        }

        // 如果当前条件不满足,移动左指针直至条件满足为止
        while (count > K || ...) {
            ...
            if (...) {
                count--;
            }
            hash[A[l]]++;
            l++;
        }

        // 更新结果
        results = ...
    }

    return results;
}

推荐文章:zhuanlan.zhihu.com/p/69818691

拓扑排序

什么是拓扑排序?

拓扑排序:从给定的图的所有边中「提取出该图的某一个拓扑序列」的过程,拓扑序列是一条满足图中有向边前后关系的序列,任一有向边起点在序列中一定早于终点出现。如果图中有环,则无法提取出拓扑序列。所以拓扑排序的一个重要应用是在给定的有向图中判定是否存在环路。

作用:拓扑排序是找到图中入度为 0 的节点,以及仅由入度为 0 节点所指向的节点

因此可以得出,反向拓扑排序可以得到出度为0的节点。

过程:先找出入度为0的节点,然后剔除,这样它对应的下一个节点的入度数-1,以此循环。

// 示例[[1,2],[3],[3],[]] 表示节点0 出度到1,2 节点1 出度到3,节点2出度到3
public List<Integer> TopologicalSorting(int[][] nums) {
    int size = nums.length;
    // 记录每个节点出度的节点
    List<List<Integer>> list = new ArrayList<>();
    // 记录每个节点的入度
    int[] inCount = new int[size];

    for (int i = 0;i < size;i++) {
        List<Integer> tmp = new ArrayList();
        for (int j = 0;j < nums[i].length;j++) {
            inCount[nums[i][j]] ++;
            tmp.add(nums[i][j]);
        }
        list.add(tmp);
    }

    Deque<Integer> deque = new LinkedList<>();
    for (int i = 0;i < size;i++) {
        // 添加入度为0 的节点
        if (inCount[i] == 0) {
            deque.add(i);
        }
    }

    while (!deque.isEmpty()) {
        int top = deque.removeFirst();
        for (int node : list.get(top)) {
            inCount[node]--;
            if (inCount[node] == 0) {
                deque.addLast(node);
            }
        }
    }
    List<Integer> res = new ArrayList<>();
    for (int i = 0;i < size;i++) {
        if (inCount[i] == 0) {
            res.add(i);
        }
    }
    return res;
}

例题推荐:leetcode-cn.com/problems/fi…

并查集

适用于亲戚问题,如A和B有亲戚关系,B和C有亲戚关系,那么A和C就有亲戚关系

模版

class UnionFind {
    // 无关系的数量
    private int count;
    // 最终结果展示为两层的多叉树形结构
    private int[] parent;

    public UnionFind(int count) {
        this.count = count;
        parent = new int[count];
        for(int i = 0;i < count;i++) {
            parent[i] = i;
        }
    }

    public int find(int x) {
        int root = x;
        while(root != parent[root]) {
            root = parent[root];
        }
        // 调整成统一根节点
        while(x != parent[x]) {
            int p = x;
            parent[x] = root;
            x = parent[p];
        }
        return root;
    }
    // 合并
    public void union(int x,int y) {
        int xRoot = find(x);
        int yRoot = find(y);
        if(xRoot == yRoot) return;
        parent[xRoot] = yRoot;
        count--;
    }
    public int getCount() {
        return count;
    }
}

入门例题:547. 省份数量

进阶例题: 最长连续序列

tips

数组中循环--求余陷阱

示例:[1,3,6,2,4] 从节点0 开始,步长为1,会跳跃到节点1,此时步长为3,跳跃到节点4,步长为4跳跃到节点节点3......这样的关系用java如何表示?(数组中的步长也可以为负数)

错误的方法:

while(!visited[index]){
    // index当前位置,size数组的长度
    index = (nums[index] + index + size) % size;
    visited[index] = true;
}

在java中,当步长为-9时,当前坐标为2,size为3时,index的值为-1,下标越界。而在c中index值为2. 正确的写法:

 while(!visited[index]) {
    visited[index] = true;
    index = (((nums[index] + index) % size) + size) % size;
 }

1~9转字符串

char c = (char)(count + 48)

由已知随机数生成范围,获得新的随机数范围

rand7()生成1-7的随机数。 那么可以通过

(rand7() - 1) * 7 + rand7() 生成1-49范围的随机数

原理:(rand7() - 1) * 7 生成的是0、7、14...的离散数值,加上rand7用于填充中间空白的数值。