算法集2

229 阅读9分钟

顶部

算法:深度搜索

场景

待补充...

题目

733.图像渲染

经典题,基础题

class Solution {
    int[] dx = {1, 0, 0, -1};
    int[] dy = {0, 1, -1, 0};
    public int[][] floodFill(int[][] image, int sr, int sc, int color) {
        // DFS
        int curColor = image[sr][sc];
        if(curColor != color){
            // 1、dfs
            dfs(image, sr, sc, curColor, color);
        }
        return image;
    }
    public void dfs(int[][] image, int x, int y, int curColor, int color){
        // 2、满足条件才能递归
        if(image[x][y] == curColor){
            image[x][y] = color;
            // 3、上下左右遍历
            for(int i = 0; i < 4; i++){
                int mx = x + dx[i], my = y + dy[i];
                // 4、不超出边界才能继续dfs
                if(mx >= 0 && mx < image.length && my >= 0 && my < image[0].length){
                    image[x][y] = color;
                    dfs(image, mx, my, curColor, color);
                }
            }
        }
    }
}

79. 单词搜索

思路:既是dfs也是回溯,好题~!,熟练使用visited数组

class Solution {
    public boolean exist(char[][] board, String word) {
        boolean[][] visited = new boolean[board.length][board[0].length];
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (dfs(board, word, visited, i, j, 0)) 
                    return true;
            }
        }
        return false;
    }
    public boolean dfs(char[][] board, String word, boolean[][] visited, int i, int j, int wordIdx) {
        // 注意要判断最后一个字符是不是相等
        if (wordIdx == word.length() - 1) 
            return board[i][j] == word.charAt(wordIdx);;

         if (board[i][j] == word.charAt(wordIdx)){
             visited[i][j] = true;
            int[][] direactions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
            
            for (int[] dir: direactions) {
                int newi = i + dir[0];
                int newj = j + dir[1];
                if (newi >= 0 && newi < board.length && newj >= 0 && newj < board[0].length && !visited[newi][newj]) {  
                    if(dfs(board, word, visited, newi, newj, wordIdx + 1)) {
                        return true;
                    }
                }
            }
            // 注意是周围都找不到,自身的visited才标为false
            visited[i][j] = false;
        }
        return false;
    }
}

岛屿(网格)题模板

    void dfs(int[][] grid, int r, int c) {
        // 判断 base case
        if (!inArea(grid, r, c)) {
            return;
        }
        // 如果这个格子不是岛屿,直接返回
        if (grid[r][c] != 1) {
            return;
        }
        grid[r][c] = 2; // 将格子标记为「已遍历过」

        // 访问上、下、左、右四个相邻结点
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }

    // 判断坐标 (r, c) 是否在网格中
    boolean inArea(int[][] grid, int r, int c) {
        return 0 <= r && r < grid.length 
                    && 0 <= c && c < grid[0].length;
    }

200.岛屿数量

参考题解:leetcode.cn/problems/nu…

class Solution {
    public int numIslands(char[][] grid) {
        if(grid == null || grid.length == 0)
            return 0;
        int count = 0;
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j < grid[0].length; j++){
                if(grid[i][j] == '1'){
                    count++;
                    dfs(grid, i, j);
                }  
            }
        }
        return count;
    }

    void dfs(char[][] grid, int r, int c){
        if (!inArea(grid, r, c)) {
            return;
        }

        if (grid[r][c] != '1') {
            return;
        }

        grid[r][c] = '2';

        dfs(grid, r - 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r + 1, c);
        dfs(grid, r, c + 1);
    }

    boolean inArea(char[][] grid, int r, int c) {
        return 0 <= r && r < grid.length 
        	&& 0 <= c && c < grid[0].length;
    } 
}

463.岛屿的周长

参考题解:leetcode.cn/problems/is…

class Solution {
        public int islandPerimeter(int[][] grid) {
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[0].length; c++) {
                if (grid[r][c] == 1) {
                    // 题目限制只有一个岛屿,计算一个即可
                    return dfs(grid, r, c);
                }
            }
        }
        return 0;
    }
    int dfs(int[][] grid, int r, int c) {
         // 从一个岛屿方格走向网格边界,周长加 1
        if (!inArea(grid, r, c)) { 
            return; 
        }
        // 从一个岛屿方格走向水域方格,周长加 1
        if (grid[r][c] == 0) {
            return 1;
        }
        if (grid[r][c] != 1) {
            return 0;
        }
        grid[r][c] = 2;
        return dfs(grid, r - 1, c)
            + dfs(grid, r + 1, c)
            + dfs(grid, r, c - 1)
            + dfs(grid, r, c + 1);
    }
    boolean inArea(char[][] grid, int r, int c) {
        return 0 <= r && r < grid.length 
        	&& 0 <= c && c < grid[0].length;
    } 

}

695.岛屿的最大面积

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
    int res = 0;
    for (int r = 0; r < grid.length; r++) {
        for (int c = 0; c < grid[0].length; c++) {
            if (grid[r][c] == 1) {
                int area = dfs(grid, r, c);
                res = Math.max(res, area);
            }
        }
    }
    return res;
}

    int dfs(int[][] grid, int r, int c) {
        if (!inArea(grid, r, c)) {
            return 0;
        }
        if (grid[r][c] != 1) {
            return 0;
        }
        grid[r][c] = 2;
        
        return 1 
            + dfs(grid, r - 1, c)
            + dfs(grid, r + 1, c)
            + dfs(grid, r, c - 1)
            + dfs(grid, r, c + 1);
    }

    boolean inArea(int[][] grid, int r, int c) {
        return 0 <= r && r < grid.length 
                && 0 <= c && c < grid[0].length;
    }
}

207.课程表

此题涉及图的构建(邻接表)、拓扑排序、深度搜索,经典题目

class Solution {
        // 构造一个邻接表
        List<List<Integer>> graph = new ArrayList<>();
        boolean hasCircle = false;
        // 3种搜索状态, 0-未搜索;1-正在搜索;2-已搜索过
        int[] visited;

    public boolean canFinish(int numCourses, int[][] prerequisites) { 
        visited = new int[numCourses];

        // 注意:初始化邻接表
        for(int i = 0; i < numCourses; i++){
            graph.add(new ArrayList<Integer>());
        }
        
        // 入度
        for(int[] info : prerequisites){
            graph.get(info[1]).add(info[0]);
        }
        // 注意:不是prerequisites的长度,而是numCourses长度
        for(int i = 0; i < numCourses; i++){
            if(visited[i] == 0)
                // 因为邻接表有i行,课程也对应i,所以从i开始
                dfs(i);
        }
        return !hasCircle;
    }
    void dfs(int i){
        // 正在搜索
        visited[i] = 1;
        // 递归单行邻接表
        for(int k : graph.get(i)){
            if(visited[k] == 0){
                dfs(k);
            }else if(visited[k] == 1){
                hasCircle = true;
            }
                
        }
        // 「未搜索」:我们还没有搜索到这个节点;
        // 「搜索中」:我们搜索过这个节点,但还没有回溯到该节点,即该节点还没有入栈,还有相邻的节点没有搜索完成);
        // 「已完成」:我们搜索过并且回溯过这个节点,即该节点已经入栈,并且所有该节点的相邻节点都出现在栈的更底部的位置,满足拓扑排序的要求。
        visited[i] = 2;
    }
}

算法:广度搜索

场景

待补充...

题目

733.图像渲染

经典题,基础题

class Solution {
    int[] dx = {-1, 0, 0, 1};
    int[] dy = {0, -1, 1, 0};
    public int[][] floodFill(int[][] image, int sr, int sc, int color) {
        // BFS
        int curColor = image[sr][sc];
        if(curColor == color){
            return image;
        }

        int n = image.length;
        int m = image[0].length;
        // 虽然添加的是二维数组,但泛型可以写一维数组
        Queue<int []> queue = new LinkedList<>();
        // 添加二维的写法
        queue.offer(new int[]{sr, sc});
        // 1、先加入根节点
        image[sr][sc] = color;
        while(!queue.isEmpty()){
            // 2、弹出根节点
            int[] cell = queue.poll();
            int x = cell[0], y = cell[1];
            for(int i = 0; i < 4; i++){
                int mx = x + dx[i], my = y + dy[i];
                if(mx >= 0 && mx < n && my >= 0 && my < m && image[mx][my] == curColor){
                    // 3、将满足条件的节点添加到队列
                    queue.offer(new int[]{mx, my});
                    image[mx][my] = color;
                }
            }
        }
        return image;
    }
}

102. 二叉树的层序遍历

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {  
        List<List<Integer>> res = new LinkedList<List<Integer>>();

        //要用LinkedList
        Queue<TreeNode> q = new LinkedList<TreeNode>();
        //要放在res后面
        if(root == null)
            return res;
        q.offer(root);
        while(!q.isEmpty()){
            int count = q.size();
            //每次一层遍历完都要清空list,放在外面不能在里面
            List<Integer> list = new ArrayList<>(); 
            // 注意:里面还有一层循环,将当前队列中的所有节点向四周扩散
            while(count > 0){
                //取出节点          
                TreeNode node = q.poll();
                //将节点的值存如list
                list.add(node.val);
                //左端点加入队列
                if(node.left != null)
                    q.offer(node.left);
                //右端点加入队列
                if(node.right != null)
                    q.offer(node.right);
                count--;
            }
            //将当层节点的值的List添加到res
            res.add(list);
                
        }
        return res;
    }
}

100.相同的树

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null)
            return true;
        Queue<TreeNode> que = new LinkedList<>();
        que.offer(p);
        que.offer(q);
        while(!que.isEmpty()){
            TreeNode node1 = que.poll();
            TreeNode node2 = que.poll();
            
            // 注意:都为null的跳过这轮
            if(node1 == null && node2 == null)
                continue;
            if(node1 == null || node2 == null || node1.val != node2.val)
                return false;

            que.offer(node1.left);
            que.offer(node2.left);

            que.offer(node1.right);
            que.offer(node2.right);
        }
        return true;
        
    }
}

116.填充每个节点的下一个右侧节点指针

class Solution {
    public Node connect(Node root) {
        if(root == null)
            return null;
        Queue<Node> que = new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty()){
            int size = que.size();
            for(int i = 0; i < size; i++){
                Node node = que.poll();

                if(i < size-1)
                    node.next = que.peek();

                if(node.left != null)
                    que.offer(node.left);
                if(node.right != null)
                    que.offer(node.right);
            }

        }
        // 注意:返回root根节点即可
        return root;
    }
}

103.二叉树的锯齿形层序遍历

思路:借助双端队列实现,注意多了循环

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if(root == null)
            return res;
        Queue<TreeNode> que = new LinkedList<>();
        que.offer(root);
        boolean isLeft = true;
        while(!que.isEmpty()){
            Deque<Integer> deque = new LinkedList<>();
            int size = que.size();
            for(int i = 0; i < size; i++){
                TreeNode cur = que.poll();
                
                if(isLeft)
                    deque.offerLast(cur.val);
                else
                    deque.offerFirst(cur.val);

                if(cur.left != null)
                    que.offer(cur.left);
                
                if(cur.right != null)
                    que.offer(cur.right);
            } 
            res.add(new LinkedList<Integer>(deque));
            isLeft = !isLeft;
        }
        return res;
    }
}

130.被围绕的区域

class Solution {
    int[] dx = {1, -1 , 0, 0};
    int[] dy = {0, 0, 1, -1};

    public void solve(char[][] board) {
        // 控制上下左右查找
        int n = board.length;
        int m = board[0].length;
        Queue<int []> que = new LinkedList<int []>();

        // 处理边界,把边界O全都放在队列
        for(int i = 0; i < n; i++){
            if(board[i][0] == 'O'){
                que.offer(new int[]{i, 0});
                board[i][0] = 'A';
            }      
            if(board[i][m-1] == 'O'){
                que.offer(new int[]{i, m-1});
                board[i][m-1] = 'A';
            }    
        }

        for(int i = 1; i < m-1; i++){
            if(board[0][i] == 'O'){
                que.offer(new int[]{0, i});
                board[0][i] = 'A';
            } 
            if(board[n-1][i] == 'O'){
                que.offer(new int[]{n-1, i});
                board[n-1][i] = 'A';
            }  
        }

        // 上下左右寻找和边界O相连的区域
        while(!que.isEmpty()){
            int[] temp = que.poll();
            int x = temp[0];
            int y = temp[1];
            for(int i = 0; i < 4; i++){
                int mx = x + dx[i];
                int my = y + dy[i];
                if(mx < 0 || my < 0 || mx >= n || my >= m || board[mx][my] != 'O')
                    continue;
                que.offer(new int[]{mx, my});
                board[mx][my] = 'A';
            }
        }

        // 遍历替换
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(board[i][j] == 'A')
                    board[i][j] = 'O';
                else if(board[i][j] == 'O')
                    board[i][j] = 'X';  
            }
        }
    }
}

算法:前缀树

原理

参考:blog.csdn.net/fdl123456/a…

TreeMap的底层就是字典树

数据结构为:

class TrieNode{
        boolean val;
        TrieNode[] children = new TrieNode[26];
    }

题目

208.实现前缀树

思路:insert、search API

class Trie {
    class TrieNode{
        boolean val;
        TrieNode[] children = new TrieNode[26];
    }

    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }
    
    public void insert(String word) {
        // 可以想像成一个单链表
        TrieNode p = root;
        for(char c : word.toCharArray()){
            int i = c - 'a';
            // 新建一个空节点
            if(p.children[i] == null){
                p.children[i] = new TrieNode();
            }
            p = p.children[i];
        }
        // 最后一个值赋值true
        p.val = true;
    }
    
    public boolean search(String word) {
        TrieNode p = root;
        // 每层由一个for循环搞定,找到一个字符就继续往下走
        for(char c : word.toCharArray()){
            int i = c - 'a';
            if(p.children[i] == null){
                return false;
            }
            p = p.children[i];
        }
        return p.val;
    }
    
    public boolean startsWith(String prefix) {
        TrieNode p = root;
        for(char c : prefix.toCharArray()){
            int i = c - 'a';
            if(p.children[i] == null){
                return false;
            }
            p = p.children[i];
        }
        return true;
    }
}
   

211. 添加与搜索单词

思路:递归搜索,hasKeyWithPattern API

class WordDictionary {

    class TrieNode{
        boolean val;
        TrieNode[] children = new TrieNode[26];
    }

    private TrieNode root;

    public WordDictionary() {
        root = new TrieNode();

    }
    
    public void addWord(String word) {
        // 可以想像成一个单链表
        TrieNode p = root;
        for(char c : word.toCharArray()){
            int i = c - 'a';
            // 新建一个空节点
            if(p.children[i] == null){
                p.children[i] = new TrieNode();
            }
            p = p.children[i];
        }
        // 最后一个值赋值true
        p.val = true;
    }
    
    public boolean search(String word) {
       return hasKeyWithPattern(root, word, 0);

    }
    // 函数定义:从 node 节点开始匹配 pattern[i..],返回是否成功匹配
    private boolean hasKeyWithPattern(TrieNode node,String pattern, int i) {
        if (node == null) {
            // 树枝不存在,即匹配失败
            return false;
        }
        if (i == pattern.length()) {
            // 模式串走到头了,看看匹配到的是否是一个键
            return node.val == true;
        }
        char c = pattern.charAt(i);
        // 没有遇到通配符
        if (c != '.') {
            // 从 node.children[c] 节点开始匹配 pattern[i+1..]
            int temp = c - 'a';
            return hasKeyWithPattern(node.children[temp], pattern, i + 1);
        }
        // 遇到通配符
        for (int j = 0; j < 26; j++) {
            // pattern[i] 可以变化成任意字符,尝试所有可能,只要遇到一个匹配成功就返回
            if (hasKeyWithPattern(node.children[j], pattern, i + 1)) {
                return true;
            }
        }
        // 都没有匹配
        return false;
    }

}

648. 单词替换

思路:最短匹配,shortestPrefixOf API

class Solution {
    class TrieNode{
        boolean val;
        TrieNode[] children = new TrieNode[26];
    }
    TrieNode root = new TrieNode();
    public String replaceWords(List<String> dictionary, String sentence) {
        
        for(String sub : dictionary){
            insert(sub);
        }

        StringBuilder sb = new StringBuilder();
        String[] words = sentence.split(" ");
        // 处理句子中的单词
        for (int i = 0; i < words.length; i++) {
            // 在 Trie 树中搜索最短词根(最短前缀)
            String prefix = shortestPrefixOf(words[i]);
            if (!prefix.isEmpty()) {
                // 如果搜索到了,改写为词根
                sb.append(prefix);
            } else {
                // 否则,原样放回
                sb.append(words[i]);
            }
            if (i != words.length - 1) {
                // 添加单词之间的空格
                sb.append(' ');
            }
        }
        return sb.toString();
    }

    public void insert(String word) {
        // 可以想像成一个单链表
        TrieNode p = root;
        for(char c : word.toCharArray()){
            int i = c - 'a';
            // 新建一个空节点
            if(p.children[i] == null){
                p.children[i] = new TrieNode();
            }
            p = p.children[i];
        }
        // 最后一个值赋值true
        p.val = true;
    }

    // 在所有键中寻找 query 的最短前缀
    public String shortestPrefixOf(String query) {
        TrieNode p = root;
        // 从节点 node 开始搜索 key
        for (int i = 0; i < query.length(); i++) {
            if (p == null) {
                // 无法向下搜索
                return "";
            }
            if (p.val == true) {
                // 找到一个键是 query 的前缀
                return query.substring(0, i);
            }
            // 向下搜索
            int c = query.charAt(i) - 'a';
            p = p.children[c];
        }
    
        if (p != null && p.val == true) {
            // 如果 query 本身就是一个键
            return query;
        }
        return "";
    }
}

677. 键值映射

思路:递归,for循环搜索

class MapSum {
    class TrieNode {
        int val;
        TrieNode[] children = new TrieNode[26];
    }
    private TrieNode root;
    public MapSum() {
        root = new TrieNode();
    }
    
    public void insert(String key, int val) {
        TrieNode p = root;
        for (char c : key.toCharArray()) {
            int i = c - 'a';
            if (p.children[i] == null) p.children[i] = new TrieNode();
            p = p.children[i];
        }
        p.val = val;
    }
    
    public int sum(String prefix) {
        TrieNode p = root;
        // 找到前缀 prefix 的最后一个节点
        for (char c : prefix.toCharArray()) {
            int i = c - 'a';
            if (p.children[i] == null) return 0;
            p = p.children[i];
        }
        return getAllSum(p);
    }
    // 辅助函数,求以 node 为根节点的子树的节点和 
    private int getAllSum(TrieNode node) {
        if (node == null) return 0;
        int sum = 0;
        for (int i = 0; i < 26; i++) {
            sum += getAllSum(node.children[i]);
        }
        return sum + node.val;
    }
}

算法:回溯

场景

回溯框架:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

总结排列、组合、子集问题:

可参考:labuladong.gitee.io/algo/1/8/

image.png

1、子集和组合用begin,排列用uesd数组

2、重复元素问题要排序和剪枝

3、关键在于设置合适的 base case

4、注意有重(剪枝排序);可复(回溯从i开始):backtrack(candidates, i, target)

5、注意重复问题,排列和集合的剪枝判断条件不一样。排列:if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) ;子集和组合:if(i > start && nums[i] == nums[i - 1])

题目

78.子集(集合无重不可复)

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> track = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backtrack(nums, 0);
        return res;
    }
    void backtrack(int[] nums, int start){
        res.add(new ArrayList<Integer>(track));
        for(int i = start; i < nums.length; i++){
            track.add(nums[i]);
            backtrack(nums, i + 1);
            
            // 注意:删除track的最后一个
            
            track.remove(track.size()-1);

        }
    }
}

77.组合(组合无重不可复)

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> track = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtrack(1, n, k);
        return res;
    }
    
    // 注意:集合、组合问题不要忘记start
    
    void backtrack(int start, int n, int target){
        if(target == track.size()){
            res.add(new ArrayList<>(track));
            return;
        }
        
        // 注意:题意是1 -> n
        
        for(int i = start; i <= n; i++){
            track.add(i);
            
            // 注意:是i + 1,i已经搜索过了
            
            backtrack(i+1, n, target);
            track.remove(track.size()-1);
        }
    }
}

46.全排列(排列无重不可复)

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {

        List<Integer> track = new ArrayList<>();
        
        // 注意:要指定申请长度
        
        boolean[] used = new boolean[nums.length];
        backtrack(nums, track, used);
        return res;
    }
    
    
    // 路径:记录在 track 中
    // 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false) 
    // 结束条件:nums 中的元素全都在 track 中出现

    // 注意:List<Integer> track形参的写法
    void backtrack(int[] nums, List<Integer> track, boolean[] used){
        if(track.size() == nums.length){
        
            // 注意:为什么这里不能直接添加?
            // 由于Java的特性,下次递归的时候,实际上是又在修改这个path,以及相应的res,致使在最后一次递归复原的时候,把path加入的东西全吐出来了,所以需要拷贝一份
            
            res.add(new ArrayList<>(track));
            return;
        }
        for(int i = 0; i < nums.length; i++){
            if(used[i])
                continue;
            track.add(nums[i]);
            
            // 注意:记得标识已经走过
            
            used[i] = true;
            backtrack(nums, track, used);
            
            // 注意:根据索引删除
            
            track.remove(track.size() - 1);
            used[i] = false;

        }
    }
}

90.子集II (集合可重复不可复用)

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> track = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
    
        // 注意:重复元素题目要 排序和剪枝
        
        Arrays.sort(nums);
        backtrack(nums, 0);
        return res;
    }
    
    // 注意:子集、组合的dfs要有start
    void backtrack(int[] nums, int start){
        res.add(new ArrayList<>(track));

        for(int i = start; i < nums.length; i++){
        
            // 注意:重复元素剪枝
            
            if(i > start && nums[i] == nums[i - 1])
                continue;

            track.add(nums[i]);
            backtrack(nums, i + 1);
            track.remove(track.size() - 1);

        }
    }
}

40.组合总和II(组合可重不可复选)

class Solution {

    List<List<Integer>> res = new ArrayList<>();
    List<Integer> track = new ArrayList<>();
    int trackSum = 0;

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
       // 重复、子集问题:剪枝、排序、使用start 
       Arrays.sort(candidates);   
       backtrack(candidates, 0, target);
        return res; 
    }

    void backtrack(int[] candidates, int start, int target){

        if(target == trackSum){
            res.add(new ArrayList<>(track));
            return;
        }

        // 注意: 找到比target大的要及时返回
        
        if(trackSum > target){
            return;
        }

        for(int i = start; i < candidates.length; i++){
            // 注意: 重复要剪枝()
            if(i > start && candidates[i] == candidates[i - 1]){
                continue;
            }
            trackSum += candidates[i];
            track.add(candidates[i]);
            
            // 注意:无重可复和无重不可复的差别在这里,前者传进去i,后者传i+1

            backtrack(candidates, i+1, target);
            trackSum -= candidates[i];
            track.remove(track.size() - 1);

        }
    }
}

47.全排列II(排列可重复不可再选)

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> track = new ArrayList<>();
    boolean[] used;
    public List<List<Integer>> permuteUnique(int[] nums) {
        used= new boolean[nums.length];

        // 注意:排列问题用used数组,重复元素要排序

        Arrays.sort(nums);
        backtrack(nums);
        return res;
    }
    void backtrack(int[] nums){
        if(track.size() == nums.length){
            res.add(new ArrayList<>(track));
            // 注意:排列问题要return
            return;
        }
        for(int i = 0; i < nums.length; i++){
            if(used[i] == true)
                continue;
                
            // 注意:如果前面的相邻相等元素 没有用过 ,则跳过
            // 注意:有 i - 1 所以要判断i > 0

            if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1])
                continue;

            track.add(nums[i]);
            used[i] = true;
            backtrack(nums);
            track.remove(track.size() - 1);
            used[i] = false;
        }
    }

}

39.组合总和(子集/组合 无重复可再选)

class Solution {

    List<List<Integer>> res = new ArrayList<>();
    List<Integer> track = new ArrayList<>();
    int trackSum = 0;

    public List<List<Integer>> combinationSum(int[] candidates, int target) {

        backtrack(candidates, 0, target);
        return res;
    }
    void backtrack(int[] candidates, int start, int target){

        if(target == trackSum){
            res.add(new ArrayList<>(track));
            return;
        }

        // 注意: 找到比target大的要及时返回
        
        if(trackSum > target){
            return;
        }

        for(int i = start; i < candidates.length; i++){
            trackSum += candidates[i];
            track.add(candidates[i]);
            
            // 注意:无重可复和无重不可复的差别在这里,前者传进去i,后者传i+1

            backtrack(candidates, i, target);
            trackSum -= candidates[i];
            track.remove(track.size() - 1);

        }

    }
}

17. 电话号码的字母组合

class Solution {

    // 数字到号码的映射
    private String[] map = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};

    // 路径
    private StringBuilder track = new StringBuilder();

    // 结果集
    private List<String> res = new ArrayList<>();

    public List<String> letterCombinations(String digits) {
        if(digits == null || digits.length() == 0) return res;
        backtrack(digits,0);
        return res;
    }

    // 回溯函数
    private void backtrack(String digits,int index) {
        if(track.length() == digits.length()) {
            res.add(track.toString());
            return;
        }
        // 思路:当前号码2,有3个字符,当前层要利用回溯遍历完,然后进入下个号码3
        // 和无重不可复的题一样,当前层都有多个元素选择,所以采用循环
        String val = map[digits.charAt(index)-'2'];
        for(char ch:val.toCharArray()) {
            track.append(ch);
            backtrack(digits,index+1);
            track.deleteCharAt(track.length()-1);
        }
    }
}

22.括号生成

class Solution {
    List<String> res = new ArrayList<>();
    StringBuilder track = new StringBuilder();

    public List<String> generateParenthesis(int n) {
        backtrack(0, 0, n);
        return res;
    }
    public void backtrack(int left, int right, int n){
        if(right == n){
            res.add(track.toString());
            return;
        }
        if(left < n){
            track.append("(");
            backtrack(left+1, right, n);
            track.deleteCharAt(track.length() - 1);
        }
        // 注意:不能用else if,这样满足上面的就不会走下面了
        if(left > right){
            track.append(")");
            backtrack(left, right+1, n);
            track.deleteCharAt(track.length() - 1);
        }
    }
}

140.单词拆分II

思路:回溯的条件要多做多想

class Solution {
    List<String> res = new ArrayList<>();
    List<String> track = new ArrayList<>();
    List<String> wordDict;
    public List<String> wordBreak(String s, List<String> wordDict) {
        this.wordDict = wordDict;
        backtrack(s, 0);
        return res; 
    }

    public void backtrack(String s, int i){
        if(i == s.length()){
            // 找到一个合法组合拼出整个 s,转化成字符串
            res.add(String.join(" ", track));
            return ;
        }

        for(String word : wordDict){
            // 看看哪个单词能够匹配 s[i..] 的前缀
            int len = word.length();
             // 找到一个单词匹配 s[i..i+len)
            if(i+len <= s.length() && s.substring(i, i+len).equals(word)){
                track.add(word);
                // 进入回溯树的下一层,继续匹配 s[i+len..]
                backtrack(s, i+len);
                track.remove(track.size()-1);
            }
        }
    }  
}

算法:贪心

场景

贪心有点难想,无套路。

经常需要排序

题目

122. 买卖股票的最佳时机 II

class Solution {
    public int maxProfit(int[] prices) {
        // 第二天减第一天,正数则加
        int sum = 0;
        for(int i = 1; i < prices.length; i++){
            int diff = prices[i] - prices[i - 1];
            if(diff > 0){
                sum += diff;
            }
        }
        return sum;
    }
}

435. 无重叠区间

注意:

排序要实现一下 new Comparator<int []>(){}, 也可采用lambda写法

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {

        // 注意:排序要实现一下 new Comparator<int []>(){}
        
        Arrays.sort(intervals, new Comparator<int []>(){
            public int compare(int[] a, int b[]){
                return a[1] - b[1];
            }
        });

         // 至少有一个区间不相交
        int count = 1;
        int preEnd = intervals[0][1];
        int n = intervals.length;
        for(int[] interval : intervals){
            
            // 第二个的start比第一个的end大,则不重叠
            // 求不重叠的区间数
            int nextStart = interval[0];
            if(preEnd <= nextStart){
                count++;
                preEnd = interval[1];
            }
           
        }
        return n-count;
    }
}

452. 用最少数量的箭引爆气球

class Solution {
    public int findMinArrowShots(int[][] points) {

        if (points.length == 0) {
            return 0;
        }
        // 注意:lambda不需要声明参数。表达式只有一句不需要大括号。但是这里是int相减,会溢出
        // Arrays.sort(points, (a, b) -> a[1] - b[1]);

        Arrays.sort(points, new Comparator<int[]>() {
            public int compare(int[] point1, int[] point2) {
                
                // 注意:1比2大,需要交换,所以return 1

                if (point1[1] > point2[1]) {
                    return 1;
                } else if (point1[1] < point2[1]) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });

        int count = 1;
        int preEnd = points[0][1];
        for(int[] point : points){

            // 如果最多有 n 个不重叠的区间,那么就至少需要 n 个箭头穿透所有区间
            // 以下求的是不重叠的情况
            
            int nextStart = point[0];
            if(preEnd < nextStart){
                count++;
                preEnd = point[1];
            }
           
        }
        return count;
    }
    
}

55. 跳跃游戏

class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        int pos = 0;
        for(int i = 0; i < n; i++){
            // pos前的都可以到达
            if(i <= pos){
                pos = Math.max(pos, nums[i] + i);
                if(pos >= n - 1){
                return true;
                }
            }              
        }
        return false;
    }
}

45. 跳跃游戏 II

class Solution {
    public int jump(int[] nums) {
        int border = 0;
        int n = nums.length;
        int farthest = 0;
        int steps = 0;

        // 到达最后一个的前一个,也即到达了边界,进入判断,前一个必不为0,所以不用跳到最后一个也能到达
        for(int i = 0; i < n - 1; i++){
            farthest = Math.max(farthest, nums[i] + i);

            // 到达边界,能到更远的地方再更新边界并且步数+1
            if(i == border){
                border = farthest;
                steps++;
            }
        }
        return steps;
    }
}

134. 加油站

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        // 首先判断总gas能不能大于等于总cost,如果总gas不够,一切都白搭对吧(总(gas- cost)不用单独去计算,和找最低点时一起计算即可,只遍历一次);

        // 再就是找总(gas-cost)的最低点,不管正负(当然如果最低点都是正的话那肯定能跑完了);
        
        // 找到最低点后,如果有解,那么解就是最低点的下一个点,因为总(gas-cost)是大于等于0的,所以前面损失的gas我从最低点下一个点开始都会拿回来,别管后面的趋势是先加后减还是先减后加,最终结果我是能填平前面的坑的。
        int n = gas.length;
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += gas[i] - cost[i];
        }
        if (sum < 0) {
            // 总油量小于总的消耗,无解
            return -1;
        }
        
        // 记录油箱中的油量
        int tank = 0;
        // 记录起点
        int start = 0;
        for (int i = 0; i < n; i++) {
            tank += gas[i] - cost[i];
            if (tank < 0) {
                // 无法从 start 到达 i + 1
                // 所以站点 i + 1 应该是起点
                tank = 0;
                start = i + 1;
            }
        }
        // 解是唯一的
        return start;
    }
}

406.根据身高重建队列

遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。好题

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        // 排序
        Arrays.sort(people,(a, b) ->{
            if(a[0] == b[0])
                return a[1] - b[1];
            return b[0] - a[0];
        });
        // 插入队列
        LinkedList<int[]> res = new LinkedList<>();
        for(int[] temp : people){
            // 根据k索引插入数据,注意add(int index, <T> value)可以根据索引插入
            res.add(temp[1], temp);
        }
        // 注意链表转数组的写法
        return res.toArray(new int[people.length][]);
        
    }
}

621. 任务调度器

class Solution {
    // 思路:贪心
    // 题解:https://leetcode.cn/problems/task-scheduler/solutions/509866/jian-ming-yi-dong-de-javajie-da-by-lan-s-jfl9/
    
   public int leastInterval(char[] tasks, int n) {
        int[] buckets = new int[26];
        for(int i = 0; i < tasks.length; i++){
            buckets[tasks[i] - 'A']++;
        }
        Arrays.sort(buckets);
        int maxTimes = buckets[25];
        int maxCount = 1;
        for(int i = 25; i >= 1; i--){
            if(buckets[i] == buckets[i - 1])
                maxCount++;
            else
                break;
        }
        int res = (maxTimes - 1) * (n + 1) + maxCount;
        return Math.max(res, tasks.length);
    }
}

算法:动态规划

题目

70. 爬楼梯

class Solution {
    public int climbStairs(int n) {
       //非递归:  
        //时间复杂度O(n)
         if(n>=0 && n<=2) //如果去掉这个,当数组长度为1,下面mem[2]=2赋值就会超出边界
             return n;
         int[] dp = new int[n+1];
         dp[1] = 1;
         dp[2] = 2;
         for(int i = 3;i<=n;i++)
             dp[i] = dp[i-1]+dp[i-2];
         return dp[n];
    }
}

53. 最大子数组和

class Solution {
    public int maxSubArray(int[] nums) {
        if(nums.length == 0)
            return 0;
        
        int n = nums.length;
        int max = nums[0];
        int[] dp = new int[n];
        dp[0] = nums[0];
        for(int i=1;i<n;i++){
            //dp存储的是当前i所能加到的最大值,并不是整个dp数组的最大值
            //所以还要将dp数组的最大值赋值给max
            //下次一定要考虑清楚dp代表的是什么,才不会做错
            dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
            if(max < dp[i])
                max = dp[i];
        }
           
        return max;
    }
}

322. 零钱兑换

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] == max ?  -1 : dp[amount];
    }
}

算法:位运算

题目

136. 只出现一次的数字

 public int singleNumber(int[] nums) {
        //方法二:
        //数组中只有一个数出现了奇数次,偶数次异或之后为0,只剩下奇数项
        int res = 0;
        for(int num:nums)
            res ^= num;
        return res;
    }

算法:并查集

原理

并查集包含三个函数:

  1. 并(union):将节点数较少的根节点连接到节点数较多的节点上
  2. 查(find):查找节点x的根节点
  3. 判断(isconnected):判断是否连通

题目

 class UnionFind{    
	//记录连通分量    
	private int count;    
	//记录父亲节点的数组,节点x的父亲节点是parent[x]    
	private int[] parent;    
	//记录树的节点数,size[3]=5表示,以3为根节点的树共有5个节点    
	private int[] size;
	
      	//构造函数,n为节点总数,即初始状态    
      	public UnionFind(int n){        
      		//开始互相之间都不连通,连通数为n        
      		this.count = n;        
      		//初始的时候,各个节点的父亲节点都是自己,所以parent[i]=i        
      		parent = new int[n];        
      		//初始化时的每个节点的节点数都是1   
      		size = new int[n];        
      		for (int i = 0; i < n; i++){      
      		      parent[i] = i;            
      		      size[i] = 1;        
      		}    
      	}    
      	//查找节点x的根节点。如果x的父亲节点不是x本身,将x连接到x的爷爷节点上,再将x的父亲节点赋值给x,
      	//一层一层地向上找,直到x的父亲节点是x本身,即parent[x] = x    
      	private int find(int x){        
      		while(parent[x] != x ){       
      		     x = parent[x];        
      		}        
      		return x;    
        }    
        //找到p,q的根节点,如果两者的根节点相同,说明两者已经是连通的了,无需再连通,直接返回;
        //如果两个根节点不是同一个,将节点数较少的根节点连接到节点数较多的节点上    
        public void union(int p, int q){        
        	int rootP = find(p);        
        	int rootQ = find(q);        
        	if (rootP == rootQ) return;        
        	//判断节点数,将节点数较少的根节点连接到节点数较多的节点上        
        	if (size[rootP] > size[rootQ]){            
        		//rootP的节点数大于rootQ的节点数,将rootQ的父亲节点设为rootP   
        		parent[rootQ] = rootP;            
        		//rootQ成为了rootP的一部分,那么将rootQ的节点数加到rootP上            
        		size[rootP] += size[rootQ];
        	} else {            
	        	//rootQ的节点数大于rootP的节点数,将rootP的父亲节点设为rootQ            
	        	parent[rootP] = rootQ;            
	        	size[rootQ] += size[rootP];        
        	}        
        	//将两个互相独立的岛屿,连接到一起,那么连通分量就会少一,即count--;        
        	count--;    
        }    
        //返回当前的连通分量个数    
        public int count(){        
        	return count;    
        }
    	public boolean connented(int p , int q){        
    		int rootP = find(p);        
    		int rootQ = find(q);        
    		return rootP == rootQ;    
    	}
  }

130.被围绕的岛屿

public void solve(char[][] board) {
        if (board == null || board.length == 0)
            return;

        int rows = board.length;
        int cols = board[0].length;

        // 用一个虚拟节点, 边界上的O 的父节点都是这个虚拟节点
        UnionFind uf = new UnionFind(rows * cols + 1);
        int dummyNode = rows * cols;

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == 'O') {
                    // 遇到O进行并查集操作合并
                    if (i == 0 || i == rows - 1 || j == 0 || j == cols - 1) {
                        // 边界上的O,把它和dummyNode 合并成一个连通区域.
                        uf.union(node(i, j), dummyNode);
                    } else {
                        // 和上下左右合并成一个连通区域.
                        if (i > 0 && board[i - 1][j] == 'O')
                            uf.union(node(i, j), node(i - 1, j));
                        if (i < rows - 1 && board[i + 1][j] == 'O')
                            uf.union(node(i, j), node(i + 1, j));
                        if (j > 0 && board[i][j - 1] == 'O')
                            uf.union(node(i, j), node(i, j - 1));
                        if (j < cols - 1 && board[i][j + 1] == 'O')
                            uf.union(node(i, j), node(i, j + 1));
                    }
                }
            }
        }

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (uf.isConnected(node(i, j), dummyNode)) {
                    // 和dummyNode 在一个连通区域的,那么就是O;
                    board[i][j] = 'O';
                } else {
                    board[i][j] = 'X';
                }
            }
        }
    }

    int node(int i, int j) {
        return i * cols + j;
    }
}

算法:设计题

题目

155. 最小栈

class MinStack {

    //s1正常存放,s2放最小值
    
    Stack<Integer> s1 = new Stack<>();
    Stack<Integer> s2 = new Stack<>();
    public MinStack() {
        
    }
    
    public void push(int x) {
        s1.push(x);
        //如果s2为空或者栈顶元素大于x,将x压入s2
        if(s2.isEmpty() || s2.peek() >= x)
            s2.push(x);
        
    }
    
    public void pop() {
        int value = s1.pop();
        //如果s1取出的值和s2栈顶元素相等,取出s2的元素
        if(s2.peek() == value)
            s2.pop();
        
    }
    
    public int top() {
        return s1.peek();
    }
    
    public int getMin() {
        return s2.peek();
    }
}

1.公共自行车管理系统

思路:递归、排序

public class BikeManager {
    private final Map<Integer, Integer> countMap = new HashMap<>(); // 记录每个节点的共享单车数量
    private final Map<Integer, Integer> parentMap = new HashMap<>(); // 记录每个节点的父节点编号
    private final int capacity;
    /**
     * 初始化
     *
     * @param preNode  父节点数组
     * @param capacity 租借点容量
     */

    public BikeManager(int[] preNode, int capacity) {
        this.capacity = capacity;
        for (int i = 1; i < preNode.length; i++) {
            parentMap.put(i, preNode[i]);
            countMap.put(i, capacity / 2);
        }
    }
    /**
     * 借车
     *
     * @param nodeId 租借点id
     * @param num    借车数量
     * @return 剩余数量
     */
    public int rentBikes(int nodeId, int num) {

        if (!countMap.containsKey(nodeId)) {
            return 0;
        }
        if (countMap.get(nodeId) < num) {
            rentBikes(parentMap.get(nodeId), num - countMap.get(nodeId));
            countMap.put(nodeId, 0);
            return 0;
        }
        countMap.put(nodeId, countMap.get(nodeId) - num);
        return countMap.get(nodeId);
    }
    /**
     * 还车
     *
     * @param nodeId 租借点id
     * @param num    还车数量
     * @return 剩余数量
     */

    public int returnBikes(int nodeId, int num) {

        if (!countMap.containsKey(nodeId)) {
            return 0;
        }
        int remain = countMap.get(nodeId) + num - capacity;
        if (remain > 0) {
            returnBikes(parentMap.get(nodeId), remain);
            countMap.put(nodeId, capacity);
            return capacity;
        }
        countMap.put(nodeId, countMap.get(nodeId) + num);
        return countMap.get(nodeId);
    }
    /**
     * 重置
     *
     * @return 重置节点的数量
     */

    public int reset() {
        int result = 0;
        for (Map.Entry<Integer, Integer> node : countMap.entrySet()) {
            if (node.getValue() == 0 || node.getValue() == capacity) {
                countMap.put(node.getKey(), capacity / 2);
                ++result;
            }
        }
        return result;
    }
    /**
     * 返回top5的节点id
     *
     * @return top5的节点id
     */
    public int[] getTop5Nodes() {

        final List<Integer> list = countMap.entrySet().stream()

                .sorted((n1, n2) -> n1.getValue().equals(n2.getValue()) ? n1.getKey() - n2.getKey() : n2.getValue() - n1.getValue())

                .map(Map.Entry::getKey).limit(5).collect(Collectors.toList());

        int[] result = new int[list.size()];

        for (int i = 0; i < list.size(); i++) {

            result[i] = list.get(i);
        }
        return result;
    }
}

2.招聘策略

思路:灵活使用filter; 合理用类包装属性

public class 招聘策略 {
    private static List<int[]> getRecruitmentResults(List<DeptDemand> deptDemands, List<Candidate> candidateAbilities) {

        List<DeptDemand> sortedDeptDemands = deptDemands.stream()
                .sorted(Comparator.comparingInt(dept -> dept.id))
                .collect(Collectors.toList());
        
        List<Candidate> sortedCandidates = candidateAbilities.stream().sorted((can1, can2) -> {
            int sortedBy;
            if (can1.techGrade == can2.techGrade) {
                sortedBy = -(can1.programGrade - can2.programGrade);
            } else {
                sortedBy = -(can1.techGrade - can2.techGrade);
            }
            return sortedBy == 0 ? can1.id - can2.id : sortedBy;
        }).collect(Collectors.toList());
        everyCommonPick(sortedDeptDemands, sortedCandidates);
        everySpecialPick(sortedDeptDemands, sortedCandidates);
        return sortedDeptDemands.stream()
                .map(dept -> dept.luckyBoys.stream().mapToInt(boy -> boy.id).toArray())
                .collect(Collectors.toList());
    }

    private static void everyCommonPick(List<DeptDemand> sortedDeptDemands, List<Candidate> sortedCandidates) {
        LinkedList<DeptDemand> queryDept = new LinkedList<>(sortedDeptDemands);
        while (!queryDept.isEmpty()) {
            DeptDemand curDept = queryDept.poll();
            boolean result = pickOne(curDept, sortedCandidates);
            if (result) {
                queryDept.offer(curDept);
            }
        }
    }

    private static void everySpecialPick(List<DeptDemand> sortedDeptDemands, List<Candidate> sortedCandidates) {
        for (DeptDemand deptDemand : sortedDeptDemands) {
            if (deptDemand.luckyBoys.isEmpty()) {
                continue;
            }
            Candidate candidate = deptDemand.luckyBoys.get(deptDemand.luckyBoys.size() - 1);
            int lastprogramGrade = candidate.programGrade, lastTechThd = candidate.techGrade;
            Optional<Candidate> anyOne = sortedCandidates.stream()
                    .filter(can -> can.programGrade == lastprogramGrade && can.techGrade == lastTechThd)
                    .findFirst();
            anyOne.ifPresent(one -> {
                deptDemand.luckyBoys.add(one);
                sortedCandidates.remove(one);
            });
        }
    }

    private static boolean pickOne(DeptDemand deptDemand, List<Candidate> candidateAbilities) {
        List<Candidate> collect = candidateAbilities.stream()
                .filter(can -> can.programGrade >= deptDemand.programGrade && can.techGrade >= deptDemand.techThd)
                .collect(Collectors.toList());
        if (collect.isEmpty() || deptDemand.employNum == 0) {
            return false;
        }
        Candidate suitableCandidate = collect.get(0);
        deptDemand.luckyBoys.add(suitableCandidate);
        candidateAbilities.remove(suitableCandidate);
        deptDemand.employNum--;
        return true;
    }

    static class Candidate {
        int id;
        int programGrade; // 机试分数
        int techGrade;  // 技面分数
        Candidate(int id, int[] grades) {
            this.id = id;
            this.programGrade = grades[0];
            this.techGrade = grades[1];
        }
        public String toString() {
            return String.format(Locale.ROOT, "{%d : %d %d}",
                    this.id, this.programGrade, this.techGrade);
        }
    }

    static class DeptDemand {
        int id = -1;
        int employNum = 0; // 招聘目标
        int programGrade = 0;  // 机考门槛值
        int techThd = 0;   // 技面门槛值
        List<Candidate> luckyBoys = new ArrayList<>();
        DeptDemand(int id, int[] arr) {
            this.id = id;
            this.employNum = arr[0];
            this.programGrade = arr[1];
            this.techThd = arr[2];
        }

        public String toString() {
            return String.format(Locale.ROOT, "{%d : %d %d %d}",
                    this.id, this.employNum, this.programGrade, this.techThd);
        }
    }
}

355. 设计推特

思路:哈希+链表+堆(好题目)

class Twitter {

    /**
     * 推文类,是一个单链表(结点视角)
     */
    private class Tweet {
        /**
         * 推文 id
         */
        private int id;

        /**
         * 发推文的时间戳
         */
        private int timestamp;
        private Tweet next;

        public Tweet(int id, int timestamp) {
            this.id = id;
            this.timestamp = timestamp;
        }
    }

    /**
     * 用户 id 和推文(单链表)的对应关系
     */
    private Map<Integer, Tweet> twitter;

    /**
     * 用户 id 和他关注的用户列表的对应关系
     */
    private Map<Integer, Set<Integer>> followings;

    /**
     * 全局使用的时间戳字段,用户每发布一条推文之前 + 1
     */
    private static int timestamp = 0;

    /**
     * 合并 k 组推文使用的数据结构(可以在方法里创建使用),声明成全局变量非必需,视个人情况使用
     */
    private static PriorityQueue<Tweet> maxHeap;

    /**
     * Initialize your data structure here.
     */
    public Twitter() {
        followings = new HashMap<>();
        twitter = new HashMap<>();
        maxHeap = new PriorityQueue<>((o1, o2) -> -o1.timestamp + o2.timestamp);
    }

    /**
     * Compose a new tweet.
     */
    public void postTweet(int userId, int tweetId) {
        timestamp++;
        if (twitter.containsKey(userId)) {
            Tweet oldHead = twitter.get(userId);
            Tweet newHead = new Tweet(tweetId, timestamp);
            newHead.next = oldHead;
            twitter.put(userId, newHead);
        } else {
            twitter.put(userId, new Tweet(tweetId, timestamp));
        }
    }

    /**
     * Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
     */
    public List<Integer> getNewsFeed(int userId) {
        // 由于是全局使用的,使用之前需要清空
        maxHeap.clear();

        // 如果自己发了推文也要算上
        if (twitter.containsKey(userId)) {
            maxHeap.offer(twitter.get(userId));
        }

        Set<Integer> followingList = followings.get(userId);
        if (followingList != null && followingList.size() > 0) {
            for (Integer followingId : followingList) {
                Tweet tweet = twitter.get(followingId);
                if (tweet != null) {
                    maxHeap.offer(tweet);
                }
            }
        }

        List<Integer> res = new ArrayList<>(10);
        int count = 0;
        while (!maxHeap.isEmpty() && count < 10) {
            Tweet head = maxHeap.poll();
            res.add(head.id);

      
            if (head.next != null) {
                maxHeap.offer(head.next);
            }
            count++;
        }
        return res;
    }


    /**
     * Follower follows a followee. If the operation is invalid, it should be a no-op.
     *
     * @param followerId 发起关注者 id
     * @param followeeId 被关注者 id
     */
    public void follow(int followerId, int followeeId) {
        // 被关注人不能是自己
        if (followeeId == followerId) {
            return;
        }

        // 获取我自己的关注列表
        Set<Integer> followingList = followings.get(followerId);
        if (followingList == null) {
            Set<Integer> init = new HashSet<>();
            init.add(followeeId);
            followings.put(followerId, init);
        } else {
            if (followingList.contains(followeeId)) {
                return;
            }
            followingList.add(followeeId);
        }
    }


    /**
     * Follower unfollows a followee. If the operation is invalid, it should be a no-op.
     *
     * @param followerId 发起取消关注的人的 id
     * @param followeeId 被取消关注的人的 id
     */
    public void unfollow(int followerId, int followeeId) {
        if (followeeId == followerId) {
            return;
        }

        // 获取我自己的关注列表
        Set<Integer> followingList = followings.get(followerId);

        if (followingList == null) {
            return;
        }
        // 这里删除之前无需做判断,因为查找是否存在以后,就可以删除,反正删除之前都要查找
        followingList.remove(followeeId);
    }

    
}

1396. 设计地铁系统

思路:使用两个map的结构

class UndergroundSystem {
   class Start {
        String stationName;
        int time;

        Start(String stationName, int time) {
            this.stationName = stationName;
            this.time = time;
        }
    }

    class SumAmount {
        int sum;
        int amount;

        SumAmount(int sum, int amount) {
            this.sum = sum;
            this.amount = amount;
        }

    }

    Map<Integer, Start> startStation;
    Map<String, SumAmount> countInfo;

    public UndergroundSystem() {
        startStation = new HashMap<>();
        countInfo = new HashMap<>();
    }

    public void checkIn(int id, String stationName, int t) {
        startStation.put(id, new Start(stationName, t));

    }

    public void checkOut(int id, String stationName, int t) {
        Start startStationInfo = startStation.get(id);
        String startStation = startStationInfo.stationName;
        int startTime = startStationInfo.time;
        String startEnd = startStation + "-"+ stationName;
        int countTime = t - startTime;
        SumAmount sumAmount = countInfo.getOrDefault(startEnd, new SumAmount(0, 0));
        sumAmount.sum += countTime;
        sumAmount.amount += 1;
        countInfo.put(startEnd, sumAmount);
    }

    public double getAverageTime(String startStation, String endStation) {
        String startEnd = startStation + "-" + endStation;
        SumAmount sumAmount = countInfo.get(startEnd);
        int sum = sumAmount.sum;
        int count = sumAmount.amount;
        
        // 注意:返回double时这么处理
        return 1.0 * sum / count;
    }
}

2043. 简易银行系统

class Bank {

    long[] balances;
    int len;

    public Bank(long[] balance) {
        len = balance.length;
        balances = new long[len + 1];
        System.arraycopy(balance, 0, balances, 1, len);
    }

    public boolean transfer(int account1, int account2, long money) {
        if (account1 > len || account2 > len)
            return false;
        if (money > balances[account1])
            return false;
        balances[account1] -= money;
        balances[account2] += money;
        return true;
    }

    public boolean deposit(int account, long money) {
        if (account > len)
            return false;
        balances[account] += money;
        return true;

    }

    public boolean withdraw(int account, long money) {
        if (account > len)
            return false;
        if (balances[account] < money)
            return false;
        balances[account] -= money;
        return true;
    }
}

1845. 座位预约管理系统

思路:使用小堆(重要思想)

class SeatManager {
	PriorityQueue<Integer> queue;

	public SeatManager(int n) {
		queue = new PriorityQueue<>();
		for (int i = 1; i <= n; i++) {
			queue.add(i);
		}
	}

	public int reserve() {
		return queue.poll();
	}

	public void unreserve(int seatNumber) {
		queue.add(seatNumber);
	}
}