《labuladong的算法小抄》专题 4:算法技巧

https://github.com/labuladong/fucking-algorithm

贪心法

力扣55 跳跃游戏

https://leetcode.cn/problems/jump-game/

class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        int farthest = 0;
        for (int i = 0; i < n - 1; i++) {
            // 不断计算能跳到的最远距离
            farthest = Math.max(farthest, i + nums[i]);
            // 可能碰到了0,卡住跳不动了
            if (i == farthest) {
                return false;
            }
        }
        return farthest >= n - 1;
    }
}

力扣45 跳跃游戏II

https://leetcode.cn/problems/jump-game-ii/

class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        // 跳跃次数
        int count = 0;
        // 跳跃最远距离
        int farthest = 0;
        // 记录上次跳跃的最远距离
        int current = 0;
        for (int i = 0; i < n - 1; i++) {
            farthest = Math.max(farthest, i + nums[i]);
            if (i == current) {
                count++;
                current = farthest;
            }
        }
        return count;
    }
}

力扣435 无重叠区间

https://leetcode.cn/problems/non-overlapping-intervals/

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        // 按区间结束位置升序排列
        Arrays.sort(intervals, new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return a[1] - b[1];
            }
        });
        // 计数有多少个无重叠区间
        int count = 1;
        int line = intervals[0][1];
        for(int[] vals : intervals){
            int start = vals[0];
            if(start >= line){
                count++;
                line = vals[1];
            }
        }
        return intervals.length - count;
    }
}

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

https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/

class Solution {
    public int findMinArrowShots(int[][] points) {
        // 按区间结束位置升序排列
        Arrays.sort(points, (a, b) -> {
            // 使用a[1] - b[1]可能会溢出, 有几个用例无法通过
            return Integer.compare(a[1], b[1]);
        });
        // 计数有多少个无重叠区间
        int count = 1;
        int line = points[0][1];
        for(int[] point : points){
            int start = point[0];
            if(start > line){
                count++;
                line = point[1];
            }
        }
        return count;
    }
}

力扣1024 视频拼接

https://leetcode.cn/problems/video-stitching/

class Solution {
    public int videoStitching(int[][] clips, int time) {
        // 按起点升序排列,起点相同的降序排列
        Arrays.sort(clips, (a, b) -> {
            if (a[0] == b[0]) {
                return b[1] - a[1];
            }
            return a[0] - b[0];
        });
        // 基准线, 可认为时间节点line之前的视频已拼接完成
        int line = 0;
        int maxEnd = 0;
        int count = 0;
        int i = 0;
        int n = clips.length;
        while(i < n && clips[i][0] <= line){
            // 寻找开始时间不过线的情况下的最大结束时间
            while (i < n && clips[i][0] <= line) {
                maxEnd = Math.max(maxEnd, clips[i][1]);
                i++;
            }
            // 更新线
            count++;
            line = maxEnd;
            if (line >= time) {
                return count;
            }
        }
        return -1;
    }
}

力扣134 加油站

https://leetcode.cn/problems/gas-station/

回溯法

回溯与DFS的区别在于,对多叉树进行遍历时,回溯维护了一条从根结点到当前结点的路径,而DFS通常只关注当前结点。

// 回溯算法标准框架
result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

力扣46 全排列

https://leetcode.cn/problems/permutations/

class Solution {
    List<List<Integer>> res;

    /**
     * 主方法
     */
    public List<List<Integer>> permute(int[] nums) {
        res = new LinkedList();
        List<Integer> track = new LinkedList();
        boolean[] used = new boolean[nums.length];
        backtrack(nums, track, used);
        return res;
    }

    /**
     * 回溯方法
     */
    public void backtrack(int[] nums, List<Integer> track, boolean[] used){
        // 结束条件: nums中的元素全部在track中出现
        if(track.size() == nums.length){
            // 这里之所以要new LinkedList, 是因为回溯时用的始终是同一个list
            res.add(new LinkedList(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;
        }
    }
}

力扣78 子集

https://leetcode.cn/problems/subsets/

class Solution {
    List<List<Integer>> res;
    List<Integer> track;

    /**
     * 主方法
     */
    public List<List<Integer>> subsets(int[] nums) {
        res = new LinkedList();
        track = new LinkedList();
        backtrack(nums, 0);
        return res;
    }

    /**
     * 回溯方法
     */
    public void backtrack(int[] nums, int start){
        // 前序位置
        res.add(new LinkedList(track));
        // 回溯
        for(int i = start; i < nums.length; i++){
            track.add(nums[i]);
            backtrack(nums, i + 1);
            track.remove(track.size() - 1);
        }
    } 
}

力扣77 组合

https://leetcode.cn/problems/combinations/

class Solution {
    List<List<Integer>> res;
    List<Integer> track;

    /**
     * 主方法
     */
    public List<List<Integer>> combine(int n, int k) {
        if(k > n){
            return null;
        }
        res = new LinkedList();
        track = new LinkedList();
        backtrack(n, k, 1);
        return res;
    }

    /**
     * 回溯方法
     */
    public void backtrack(int n, int k, int start){
        if(track.size() == k){
            res.add(new LinkedList(track));
            return;
        }
        for(int i = start; i <= n; i++){
            track.add(i);
            backtrack(n, k, i + 1);
            track.remove(track.size() - 1);
        }
    }
}

力扣90 子集II

https://leetcode.cn/problems/subsets-ii/

class Solution {
    List<List<Integer>> res;
    List<Integer> track;

    /**
     * 主方法
     */
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        res = new LinkedList();
        track = new LinkedList();
        Arrays.sort(nums);
        backtrack(nums, 0);
        return res;
    }

    /**
     * 回溯方法
     */
    public void backtrack(int[] nums, int start){
        res.add(new LinkedList(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);
        }
    }
}

力扣47 全排列II

https://leetcode.cn/problems/permutations-ii/

class Solution {
    List<List<Integer>> res = new LinkedList();
    List<Integer> track = new LinkedList();
    boolean[] used;

    /**
     * 主方法
     */
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        used = new boolean[nums.length];
        backtrack(nums, track, used);
        return res;
    }

    /**
     * 回溯方法
     */
    public void backtrack(int[] nums, List<Integer> track, boolean[] used){
        if(track.size() == nums.length){
            res.add(new LinkedList(track));
            return;
        }
        for(int i = 0; i < nums.length; i++){
            if(used[i]){
                continue;
            }
            // !used[i - 1]保证重复数字之间的顺序恒定
            if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
                continue;
            }
            track.add(nums[i]);
            used[i] = true;
            backtrack(nums, track, used);
            track.remove(track.size() - 1);
            used[i] = false;
        }
    }
}

力扣39 组合总和

https://leetcode.cn/problems/combination-sum/

class Solution {
    List<List<Integer>> res = new LinkedList();
    List<Integer> track = new LinkedList();
    int sum = 0;

    /**
     * 主方法
     */
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backtrack(candidates, target, 0);
        return res;
    }

    /**
     * 回溯方法
     */
    public void backtrack(int[] candidates, int target, int start){
        if(sum == target){
            res.add(new LinkedList(track));
            return;
        }
        if(sum > target){
            return;
        }
        for(int i = start; i < candidates.length; i++){
            sum += candidates[i];
            track.add(candidates[i]);
            backtrack(candidates, target, i);
            sum -= candidates[i];
            track.remove(track.size() - 1);
        }
    }
}

力扣22 括号生成

https://leetcode.cn/problems/generate-parentheses/

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

    /**
     * 主方法
     */
    public List<String> generateParenthesis(int n) {
        backtrack(n, 0, 0);
        return res;
    }

    /**
     * 回溯方法
     */
    public void backtrack(int n, int left, int right){
        if(left > n || right > n){
            return;
        }
        if(left < right){
            return;
        }
        if(left == n && right == n){
            res.add(track.toString());
        }
        // 因为选择只有'('和')'两种, 所以没有写for循环
        track.append('(');
        backtrack(n, left + 1, right);
        track.deleteCharAt(track.length() - 1);
        track.append(')');
        backtrack(n, left, right + 1);
        track.deleteCharAt(track.length() - 1);
    }
}

力扣51 N皇后

https://leetcode.cn/problems/n-queens/

力扣698 划分为k个相等的子集

https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/

DFS:深度优先搜索

// 二维矩阵遍历框架
void dfs(int[][] grid, int i, int j, boolean[][] visited) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        // 超出索引边界
        return;
    }
    if (visited[i][j]) {
        // 已遍历过(i, j)
        return;
    }
    // 进入节点(i, j)
    visited[i][j] = true;
    dfs(grid, i - 1, j, visited); // 上
    dfs(grid, i + 1, j, visited); // 下
    dfs(grid, i, j - 1, visited); // 左
    dfs(grid, i, j + 1, visited); // 右
}

力扣200 岛屿数量

https://leetcode.cn/problems/number-of-islands/

class Solution {
    int m;
    int n;

    /**
     * 主方法
     */
    public int numIslands(char[][] grid) {
        m = grid.length;
        n = grid[0].length;
        // 计算岛屿的数量
        int res = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == '1'){
                    res++;
                    dfs(grid, i, j);
                }
            }
        }
        return res;
    }

    /**
     * 深度优先搜索, 每找到一个岛屿, 就把这个岛屿沉入水底
     */
    public void dfs(char[][] grid, int i, int j){
        if(i < 0 || i >= m || j < 0 || j >= n){
            return;
        }
        if(grid[i][j] == '0'){
            return;
        }
        grid[i][j] = '0';
        dfs(grid, i - 1, j);
        dfs(grid, i, j - 1);
        dfs(grid, i + 1, j);
        dfs(grid, i, j + 1);
    }
}

力扣1254 统计封闭岛屿的数目

https://leetcode.cn/problems/number-of-closed-islands/

class Solution {
 int m;
    int n;

    /**
     * 主方法
     */
    public int closedIsland(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        // 先把靠近四条边的土地沉入水底
        for(int i = 0; i < m; i++){
            dfs(grid, i, 0);
            dfs(grid, i, n - 1);
        }
        for(int j = 0; j < n; j++){
            dfs(grid, 0, j);
            dfs(grid, m - 1, j);
        }
        // 统计封闭岛屿数目
        int res = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == 0){
                    res++;
                    dfs(grid, i, j);
                }
            }
        }
        return res;
    }

    /**
     * 深度优先搜索, 每找到一个岛屿, 就把这个岛屿沉入水底
     */
    public void dfs(int[][] grid, int i, int j){
        if(i < 0 || i >= m || j < 0 || j >= n){
            return;
        }
        if(grid[i][j] == 1){
            return;
        }
        grid[i][j] = 1;
        dfs(grid, i - 1, j);
        dfs(grid, i, j - 1);
        dfs(grid, i + 1, j);
        dfs(grid, i, j + 1);
    }
}

力扣1020 飞地的数量

https://leetcode.cn/problems/number-of-enclaves/

class Solution {
    int m;
    int n;

    /**
     * 主方法
     */
    public int numEnclaves(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        // 先把靠近四条边的土地沉入水底
        for(int i = 0; i < m; i++){
            dfs(grid, i, 0);
            dfs(grid, i, n - 1);
        }
        for(int j = 0; j < n; j++){
            dfs(grid, 0, j);
            dfs(grid, m - 1, j);
        }
        // 统计飞地的数量
        int res = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == 1){
                    res++;
                }
            }
        }
        return res;
    }

    /**
     * 深度优先搜索
     */
    public void dfs(int[][] grid, int i, int j){
        if(i < 0 || i >= m || j < 0 || j >= n){
            return;
        }
        if(grid[i][j] == 0){
            return;
        }
        grid[i][j] = 0;
        dfs(grid, i - 1, j);
        dfs(grid, i, j - 1);
        dfs(grid, i + 1, j);
        dfs(grid, i, j + 1);
    }
}

力扣695 岛屿的最大面积

https://leetcode.cn/problems/max-area-of-island/

class Solution {
    int m;
    int n;

    /**
     * 主方法
     */
    public int maxAreaOfIsland(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        // 计算岛屿的最大面积
        int maxArea = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == 1){
                    maxArea = Math.max(dfs(grid, i, j), maxArea);
                }
            }
        }
        return maxArea;
    }

    /**
     * 深度优先搜索, 获取当前岛屿的面积
     */
    public int dfs(int[][] grid, int i, int j){
        if(i < 0 || i >= m || j < 0 || j >= n){
            return 0;
        }
        if(grid[i][j] == 0){
            return 0;
        }
        grid[i][j] = 0;
        return dfs(grid, i - 1, j) + dfs(grid, i, j - 1) + dfs(grid, i + 1, j) + dfs(grid, i, j + 1) + 1;
    }
}

力扣1905 统计子岛屿

https://leetcode.cn/problems/count-sub-islands/

class Solution {
    int m;
    int n;

    /**
     * 主方法
     */
    public int countSubIslands(int[][] grid1, int[][] grid2) {
        m = grid1.length;
        n = grid1[0].length;
        // 去掉grid2中不是子岛屿的岛屿
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid1[i][j] == 0 && grid2[i][j] == 1) {
                    dfs(grid2, i, j);
                }
            }
        }
        // 统计子岛屿
        int res = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid2[i][j] == 1){
                    res++;
                    dfs(grid2, i, j);
                }
            }
        }
        return res;
    }

    /**
     * 深度优先搜索, 每找到一个岛屿, 就把这个岛屿沉入水底
     */
    public void dfs(int[][] grid2, int i, int j){
        if(i < 0 || i >= m || j < 0 || j >= n){
            return;
        }
        if(grid2[i][j] == 0){
            return;
        }
        grid2[i][j] = 0;
        dfs(grid2, i - 1, j);
        dfs(grid2, i, j - 1);
        dfs(grid2, i + 1, j);
        dfs(grid2, i, j + 1);
    }
}

力扣37 解数独

https://leetcode.cn/problems/sudoku-solver/

BFS:广度优先搜索

// 计算从起点start到终点target的最近距离
int BFS(Node start, Node target) {
    // 核心数据结构: 队列
    Queue<Node> q;
    // 已经经过的结点
    Set<Node> visited;

    // 将起点加入队列
    q.offer(start);
    visited.add(start);
    // 记录扩散的步数
    int step = 0;

    while (q not empty) {
        int sz = q.size();
        // 将当前队列中的所有节点向四周扩散
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            // 划重点:这里判断是否到达终点
            if (cur is target)
                return step;
            // 将cur的相邻节点加入队列
            for (Node x : cur.adj()) {
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
                }
            }
        }
        // 划重点:更新步数在这里
        step++;
    }
}

力扣111 二叉树的最小深度

https://leetcode.cn/problems/minimum-depth-of-binary-tree/

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        // bfs核心数据结构: 队列
        Queue<TreeNode> queue = new LinkedList();
        queue.offer(root);
        int depth = 0;
        while(!queue.isEmpty()){
            depth++;
            // 逐层for循环
            int sz = queue.size();
            // 这里有个小坑, 在循环体中queue.size()是会变化的, 所以循环判定条件使用 i < sz 而不是 i < queue.size()
            for(int i = 0; i < sz; i++){
                TreeNode current = queue.poll();
                if(current.left == null && current.right == null){
                    return depth;
                }
                if(current.left != null){
                    queue.offer(current.left);
                }
                if(current.right != null){
                    queue.offer(current.right);
                }
            }
        }
        return depth;
    }
}

力扣752 打开转盘锁

https://leetcode.cn/problems/open-the-lock/

力扣773 滑动谜题

https://leetcode.cn/problems/sliding-puzzle/

分治法

力扣241 为运算表达式设计优先级

https://leetcode.cn/problems/different-ways-to-add-parentheses/

class Solution {
    public List<Integer> diffWaysToCompute(String expression) {
        List<Integer> res = new LinkedList();
        for(int i = 0; i < expression.length(); i++){
            char c = expression.charAt(i);
            if(c == '+' || c == '-' || c == '*'){
                // 分
                List<Integer> left = diffWaysToCompute(expression.substring(0, i));
                List<Integer> right = diffWaysToCompute(expression.substring(i + 1));
                // 治
                for(int a : left){
                    for(int b : right){
                        if(c == '+'){
                            res.add(a + b);
                        }
                        if(c == '-'){
                            res.add(a - b);
                        }
                        if(c == '*'){
                            res.add(a * b);
                        }
                    }
                }
            }
        }
        // 纯数字
        if (res.isEmpty()) {
            res.add(Integer.parseInt(expression));
        }
        return res;
    }
}

位运算

// 将英文字符转换成小写
('a' | ' ') => 'a'
('A' | ' ') => 'a'
// 将英文字符转换成大写
('b' & '_') => 'B'
('B' & '_') => 'B'
// 将英文字符大小写互换
('d' ^ ' ') => 'D'
('D' ^ ' ') => 'd'

// 判断两个整数是否异号
int a = -1, b = 2;
boolean m = ((a ^ b) < 0); // true
int c = 3, d = 2;
boolean n = ((c ^ d) < 0); // false

// 一个数和它本身做异或运算结果为0
a ^ a = 0;
// 一个数和0做异或运算的结果为它本身
a ^ 0 = a;

力扣191 位1的个数

https://leetcode.cn/problems/number-of-1-bits/

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int res = 0;
        while(n != 0){
            // 经典 n & (n - 1), 抹去最低的一位1
            n = n & (n - 1);
            res++;
        }
        return res;
    }
}

力扣231 2的幂

https://leetcode.cn/problems/power-of-two/

class Solution {
    public boolean isPowerOfTwo(int n) {
        if (n <= 0){
            return false;
        }
        // 2的幂的各个位置上有且仅有一个1
        return (n & (n - 1)) == 0;
    }
}

力扣136 只出现一次的数字

https://leetcode.cn/problems/single-number/

class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        for(int i : nums){
            // 异或的性质: a ^ 0 = a, a ^ a = 0
            res ^= i;
        }
        return res;
    }
}

力扣268 丢失的数字

https://leetcode.cn/problems/missing-number/

class Solution {
    public int missingNumber(int[] nums) {
        int res = 0;
        // a ^ 0 = a
        for(int i = 0; i <= nums.length; i++){
            res ^= i;
        }
        // a ^ a = 0
        for(int j : nums){
            res ^= j;
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值