算法刷题记录2

news2024/12/24 8:41:55

 4.图

4.1.被围绕的区域

思路:图中只有与边界上联通的O才不算是被X包围。因此本题就是从边界上的O开始递归,找与边界O联通的O,并标记为#(代表已遍历),最后图中剩下的O就是:被X包围的O。图中所有 '#' 则是与边界O联通的情况

class Solution {
   public void solve(char[][] board) {
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (i == 0 || j == 0 || i == board.length - 1 || j == board[0].length - 1) {//如果当前点在边界
                    if (board[i][j] == 'O') {//当前点在边界,且当前点为O
                        dfs(board, i, j);
                    }
                }
            }
        }

        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (board[i][j] == 'O') {//说明当前点是不与边界O联通的O,说明当前O被X包围
                    board[i][j] = 'X';//赋值为X
                }
                if (board[i][j] == '#') {//说明当前位置是O,但是是与边界O联通的O
                    board[i][j] = 'O';//说明当前点不被X包围,重置为O
                }
            }
        }
    }

    //该方法用于找到所有与边界O联通的O
    public void dfs(char[][] board, int i, int j) {
        if (i < 0 || i > board.length - 1 || j < 0 || j > board[0].length - 1//如果当前点越界
                ||
                board[i][j] == 'X' || board[i][j] == '#' //或者当前点不为O !
        ) return;//直接返回
        board[i][j] = '#';//能到这一步说明当前点,是与边界O联通的O,标记为已遍历(防止死循环)
        //找与当前位置相邻的、与边界O联通的O
        dfs(board, i + 1, j);
        dfs(board, i - 1, j);
        dfs(board, i, j + 1);
        dfs(board, i, j - 1);
    }
}

5.贪心

5.1项目利润问题

贪心的题目思路不太好写,最主要就是贪心的一种感觉。如果要严格的数学证明,太麻烦了

本题思路:本题就是一个局部最优解到全局最优解的题。每次都在已经解锁 (当前手上有的钱大于某个项目的花费,该项目就算是被解锁)了的项目中选一个利润最大的项目。这样最后得到的就是最大的最终资本。要注意的是,每次做完一次项目,手头上的钱就变为:之前的钱+本次项目的利润。因此,每次做项目之前,都要更新一下被解锁的项目!

代码实现:

class Solution {
    public int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {

        int[][] arr = new int[profits.length][2];
        //数组第一个元素为当前项目的最小资金,第二个元素为利润
        for (int i = 0; i < profits.length; i++) {
            arr[i][0] = capital[i];
            arr[i][1] = profits[i];
        }
        //用于存放每个项目花费的小根堆
        PriorityQueue<int[]> capitalQ = new PriorityQueue<>(
                new Comparator<int[]>() {
                    @Override
                    public int compare(int[] arr1, int[] arr2) {
                        return arr1[0] - arr2[0];
                    }
                });
        //存放已经解锁的每个项目的利润的大根堆(每次都要已经解锁了,切利润最大的项目)
        PriorityQueue<int[]> profitsQ = new PriorityQueue<>(
                new Comparator<int[]>() {
                    @Override
                    public int compare(int[] arr1, int[] arr2) {
                        return arr2[1] - arr1[1];
                    }
                }
        );
        //全部项目都进入被锁的队列中
        for (int i = 0; i < arr.length; i++) {
            capitalQ.add(arr[i]);
        }
        for (int i = 0; i < k; i++) {//进行k轮
            //如果资金w大于当前项目的花费且花费队列不为空,该项目被解锁
            //这个while循环执行完以后,就会把当前所有能解锁的项目(当前手上的钱w > 项目花费 视为解锁)全部放入利润大根堆。
            while (!capitalQ.isEmpty() && capitalQ.peek()[0] <= w) {
                profitsQ.add(capitalQ.poll());//该项目被解锁,放入利润大根堆
            }
            if (profitsQ.isEmpty()) {//当我手上的钱不足以做接下来任何一个项目时,就会出现利润大根堆提前为空的情况
                return w;//提前返回现在所有的钱
            }
            //每次都从利润大根堆找出利润最高的项目
            w += profitsQ.poll()[1];
        }
        return w;
    }
}

6.递归回溯

6.1N皇后问题

class Solution {
    public int totalNQueens(int n) {
        if (n < 1) return 0;
        //代表:第i行的皇后,放在了第record[i]列
        int[] record = new int[n];
        return process(0, record, n);
    }

    /**
     * @param i      目前来到了第i行
     * @param record 之前已经摆好皇后的位置(潜台词:前面的皇后位置都不冲突!)
     * @param n      整体一共有多少行
     */
    public int process(int i, int[] record, int n) {
        if (i == n) {//递归结束,前n个皇后都已经摆放好了位置
            return 1;//前n个皇后都已经摆好了位置,代表找到了一种摆法
        }
        int res = 0;
        for (int j = 0; j < n; j++) {//当前在第i行,遍历第i行的所有列,判断是否合法摆放。j代表列
            // 判断当前位置,是否可以摆放皇后
            if (isValid(record, i, j)) {
                record[i] = j;//当前位置可以摆放皇后
                //递归摆放接下来皇后的位置
                //当前行,尝试的所有位置做累加,就是当前情况(在record已经摆好的情况下,接下来所有合法情况的总数)
                res += process(i + 1, record, n);
            }
        }
        return res;
    }

    /**
     * 判断第i行的第i列,是否可以摆放皇后(是否和之前的皇后位置冲突)
     *
     * @param record 之前已经摆放好皇后的位置
     * @param i      第i行
     * @param j      第i列
     * @return true:可以摆放到当前位置,不冲突。false:位置冲突
     */
    private boolean isValid(int[] record, int i, int j) {
        for (int k = 0; k < i; k++) {//只有前k行皇后位置被摆放好,需要比较。遍历之前每个皇后的位置
            if (
                    j == record[k]//如果和之前某个皇后同一列
                            ||  //或者
                            Math.abs(record[k] - j) == Math.abs(i - k)//当前位置和之前某一个皇后位置在同一列(横坐标之差绝对值=纵坐标之差绝对值)
            ) {
                return false;
            }
        }
        return true;
    }
}

 6.2经典汉诺塔问题

这类问题,只要把握住局部情况,每次的局部情况符合要求,整体情况就不会出错。

比如说:当前A中有i个盘

1.先把上面i-1个盘,从A经过C,移动到B。把第i个盘空出来(因为大盘不能叠在小盘上)。

2.现在第i个盘空出来了,可以直接从A移动到C。

3.最后把上面i-1个盘,从B经过A移动到C。

这样当A中有i个盘的情况就完成了。

        如果我每次移动都遵守这个规律,每次要移动大盘的时候,就把上面的小盘全部移都,把大盘空出来,这次移动就保证了大盘一定不会叠着小盘,且大盘在小盘下。整体情况,就一定也会满足这个要求。

        这类递归题目不好理解。对于尝试的时候,我们没必要想着全局情况是怎么样变化的。而是应该定义一个统一的标准,每次局部都满足这个情况。整体一定也满足!

        总的来说,我们不应该想着全局如何变化。应该想在当前局部情况我们应该怎么样拆解问题,只要局部拆解问题对了,决策对了。整体也就对了。

        还有一点要注意的就是,我们拆解问题,拆到最后一定有一个拆不了的情况。比如说本题就是,当A中只有一个盘的情况,就可以直接移动了。这就是递归的终止条件!

public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        func(A.size(), A, B, C);
    }

    /**
     * 当前A中有1~i个圆盘,把最下面一个圆盘,从A经过B,移动到C
     *
     * @param i 圆盘数量
     * @param A 移动起始位置
     * @param B 经过位置
     * @param C 目标位置
     */
    public void func(int i, List<Integer> A, List<Integer> B, List<Integer> C) {
        if (i == 1) {//当A中只有一个元素了(一定是最小的),直接从A移动到C
            C.add(A.remove(1));
        }
        //先把A上面i - 1个圆盘从A经过C移动到B。
        func(A.size() - 1, A, C, B);
        //再把第i个圆盘从A移动到C,因为此时i-1个圆盘全部在B,可以直接移动
        C.add(A.remove(i));
        //再把i - 1个圆盘从B移动回A
        func(A.size() - 1, B, C, A);
    }

6.3.岛屿数量

本题借鉴了leetcode大佬的思路,讲的非常清晰!

. - 力扣(LeetCode)

class Solution {
    public int numIslands(char[][] grid) {
        int res = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == '1') {//如果当前位置是一个岛屿
                    dfs(grid, i, j);
                    res++;
                }
            }
        }
        return res;
    }

    public void dfs(char[][] grid, int r, int c) {//当前元素在第r行,第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 + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }

    public boolean inArea(char[][] grid, int r, int c) {//判断当前位置是否越界,true 代表没越界
        return r >= 0 && r < grid.length && c >= 0 && c < grid[0].length;
    }
}

6.4.岛屿最大面积

本题大致思路和上一题差不多。

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int max = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                max = Math.max(max, dfs(grid, i, j));
            }
        }
        return max;
    }

    public 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, c - 1) +//当前岛屿的面积等于:当前位置就是一个岛屿面积为1 + 上下左右方岛屿面积之和
                dfs(grid, r, c + 1) +
                dfs(grid, r - 1, c) +
                dfs(grid, r + 1, c);
    }
    public boolean inArea(int[][] grid, int r, int c) {
        return r >= 0 && r < grid.length && c >= 0 && c < grid[0].length;
    }
}

6.5.岛屿周长

本题思路与上面类似,就是注意base case的判断

class Solution {
    public int islandPerimeter(int[][] grid) {
        int max = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == 1) return dfs(grid, i, j);
            }
        }
        return 0;
    }

    public int dfs(int[][] grid, int r, int c) {
        // 函数因为「坐标 (r, c) 超出网格范围」返回,对应一条越界的边
        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, c - 1) +//当前岛屿的周长等于:上下左右方岛屿面积之和
                dfs(grid, r, c + 1) +
                dfs(grid, r - 1, c) +
                dfs(grid, r + 1, c);
    }

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

6.6.预测赢家(博弈)

public boolean predictTheWinner(int[] nums) {
        // 返回我先手能获取的最大值,和后手获取的最大值
        return a(nums, 0, nums.length - 1) >= b(nums, 0, nums.length - 1);
    }

    //来到当前位置,还剩第l个元素到第r个元素没有选,我先选
    public int a(int[] arr, int l, int r) {
        if (l == r) {//如果只剩一个元素了,还是我先选
            return arr[l];
        }
        return Math.max(
                arr[l] + b(arr, l + 1, r),//选了第l个元素,那么接下来我就是后手了
                arr[r] + b(arr, l, r - 1)//选了第r个元素,接下来我也是后手  且选择元素范围也改变了
        );
    }

    //来到当前位置,还剩第l个元素到第r个元素没有选,我后手
    //注意:当前不是我拿,是对方先手在l到r范围上拿
    public int b(int[] arr, int l, int r) {
        if (l == r) {//当前最后只有最后一个元素,我还是后手,肯定就被对手选了
            return 0;//返回0,没有元素选了
        }
        //注意:这里可能不好理解为什么是min
        //这两种情况是:对手做完,我是后手情况,被迫接受的!所以就是最差情况min
        //因为这是二人博弈,对手先手选的是最大的值,轮到后手的我,选的只会是最小的值。
        //对手选的分多,我的分就少了。下回合轮到我先手也是同理,对手分就少了
        return Math.min(//注意当前是我在等待对方先手在l到r范围上拿,我没得拿!
                a(arr, l + 1, r),
                a(arr, l, r - 1)
        );
    }

7.动态规划

7.1.买卖股票的最佳时期1

思路:动态规划问题,最重要的就是找到问题的状态。把问题状态不重不漏的划分出来。对于本题:每日的股票持有最大价值可以分为两种情况。

1.第i天我手上有股票的最大价值:两种情况,1.和我昨天有股票的情况一样。2.昨天没有股票,买当天的股票。

2.第i天我手上没有股票的最大价值:两种情况,1.和我昨天没有股票的情况一样。2.昨天有股票,当天卖了股票。

要注意的是:这里只能买一次股票。注意和下一题比较区分。

class Solution {
    public int maxProfit(int[] prices) {
        //dp[i][0]:代表第i天有股票(之前就有股票/当天买入)的最大利润
        //dp[i][1]:代表第i天没有股票(之前没有/当天卖了)的最大利润
        int[][] dp = new int[prices.length][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i = 1; i < prices.length; i++){
            //dp[i][0]:代表第i天有股票(之前就有股票/当天买入)的最大利润 = 前一天的股票价值/前天没有,当天买入的最大值
            dp[i][0] = Math.max(dp[i-1][0],-prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }
        return Math.max(dp[prices.length-1][0],dp[prices.length-1][1]);
    }
}

7.2.买卖股票的最佳时期2

大致思路和上一题差不多,但要区分这里可以进行多次股票买卖。

注意:这里的:dp[i-1][1]-prices[i] 就包含了多次买卖股票的情况。

class Solution {
    public int maxProfit(int[] prices) {
        //dp[i][0]:代表第i天有股票(之前就有股票/当天买入)的最大利润
        //dp[i][1]:代表第i天没有股票(之前没有/当天卖了)的最大利润
        int[][] dp = new int[prices.length][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i = 1; i < prices.length; i++){
            //dp[i][0]:代表第i天有股票(之前就有股票/当天买入)的最大利润 = 前一天的股票价值/前天没有,当天买入的最大值
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }
        return Math.max(dp[prices.length-1][0],dp[prices.length-1][1]);
    }
}

7.3.买卖股票的最佳时期3

class Solution {
    public int maxProfit(int[] prices) {
        // dp[i][0]: 第一次有股票 = Math.max(前天就有,买今天的) 注意买第i天股票就是 -prices[i]
        // dp[i][1]: 第一次卖完股票,不持有股票 = Math.max(前天没有,今天还有,当天卖) 
        // dp[i][2]: 第二次有股票 = Math.max(前天就有,在第一次买卖完没有股票的基础上,买入今天股票)
        // dp[i][3]: 第二次卖完股票,不持有 = Math.max(前天就没有,今天是第二次有股票,当天卖)
        int[][] dp = new int[prices.length][4];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        dp[0][2] = -prices[0];
        dp[0][3] = 0;
        for (int i = 1; i < prices.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i][0] + prices[i]);
            dp[i][2] = Math.max(dp[i - 1][2], dp[i][1] - prices[i]);
            dp[i][3] = Math.max(dp[i - 1][3], dp[i][2] + prices[i]);
        }
        return dp[prices.length - 1][3];
    }
}

7.4.买卖股票的最佳时期4

7.5.买卖股票的最佳时期(含手续费)

7.6.买卖股票的最佳时期(含冷冻期)

7.7.打家劫舍

class Solution {
    public int rob(int[] nums) {
        if(nums.length == 1) return nums[0];
        if(nums.length < 3) return Math.max(nums[0],nums[1]);
        int[][] dp = new int[nums.length + 1][2];
        dp[1][0] = nums[0];
        dp[1][1] = 0;
        dp[2][0] = nums[1];
        dp[2][1] = nums[0];
        for(int i = 3; i <= nums.length; i++){
            //抢第i家能获取的最大金额
            dp[i][0] = Math.max(dp[i - 2][0],dp[i -1][1]) + nums[i -1];
            //不抢第i家
            dp[i][1] = Math.max(dp[i - 1][0],dp[i - 1][1]);
        }
        return Math.max(dp[nums.length][0],dp[nums.length][1]);
    }
}

7.7.打家劫舍III (树形dp)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
        int[] process = process(root);
        return Math.max(process[0],process[1]);
    }

    //返回数组有两个元素,第一个元素代表偷当前节点的最大值,第二个元素为不偷当前节点的最大值
    public int[] process(TreeNode root) {
        if (root == null) return new int[]{0, 0};
        if (root.left == null && root.right == null) return new int[]{root.val, 0};
        int[] left = process(root.left);
        int[] right = process(root.right);
        //偷当前节点最大值 = 当前节点的值 + 不偷左右孩子
        int max1 = root.val + left[1] + right[1];
        //不偷当前节点 = 偷/不偷左孩子价值更大的一个 + 偷/不偷右孩子价值更大的一个 
        int max2 = Math.max(left[1], left[0]) + Math.max(right[0], right[1]);
        return new int[]{max1, max2};
    }
}

7.8.单词拆分

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        //把wordDict中的元素放到set集合中,方便后续判断有没有某个单词
        Set<String> set = new HashSet<String>(wordDict);
        //dp数组,dp[i] 代表字符串0到i,在不在set中,能不能被字典中的单词表示
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for(int i = 1; i <= s.length(); i++){//遍历字符串s
            for(int j = 0; j < i; j++){//判断从字符串0到i的长度,能不能被字典中的单词表示
                if(dp[j] && set.contains(s.substring(j, i))){//如果0到j能被表示,且j到i能被表示 (在字典中)
                    dp[i] = true;//则0到i也能被表示
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

7.9.零钱兑换(完全背包问题)

class Solution {
    public int coinChange(int[] coins, int amount) {
        // dp[i] 就代表凑齐 i 元所需要的硬币数量
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (i >= coins[j]) {
                    //当前遍历到第j个硬币,一共凑齐了i元
                    //凑齐这i元最小硬币数:dp[i] 没有用当前硬币
                    //用了当前硬币才凑齐的i元所用的硬币数:dp[i - coins[j]] + 1(当前硬币的数量)
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] == amount + 1 ? -1 : dp[amount];
    }
}

7.10.骑士在棋盘上的概率(三维dp)

左神第p18大概一小时左右讲了类似题目

class Solution {
    //用来代表每一对骑士的走法
    int[][] dirs = new int[][]{{-1,-2},{-1,2},{1,-2},{1,2},{-2,1},{-2,-1},{2,1},{2,-1}};
    public double knightProbability(int n, int k, int row, int column) {
        //一个三维dp数组,z方向代表剩余步数
        //x,y方向用来决定当前骑士的位置
        //如果dp[][][] = 1 代表骑士在棋盘里的概率
        double[][][] dp = new double[n][n][k + 1];
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                dp[i][j][0] = 1;//剩余步数为0,棋盘中的每一个点都代表在棋盘里
            }
        }
        //每一层(z轴)都依赖于下一层的走法
        for(int h = 1; h <= k; h++){
            //遍历当前层的每一个点
            for(int i = 0; i < n; i++){
                for(int j = 0; j < n; j++){
                    for(int[] d : dirs){
                        //走完以后的位置
                        int nx = i + d[0], ny = j + d[1];
                        //如果越界了,就不进入概率计算
                        if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
                        //如果当前位置在棋盘里
                        dp[i][j][h] += dp[nx][ny][h - 1] / 8;
                    }
                }
            }
        }
        // 本题求从最下面一层的起始 x = row y = column 到达最上面一层合法区域的概率
        // 和,求从最上面的 x = row y = column 出发,达到最下面一层的合法区域的概率 是相同的!
        // 因此返回值为从最下面合法区域的任何一个点开始,到达最顶上 x = row y = column 的概率
        return dp[row][column][k];
    }
}

8.滑动窗口和单调栈

1.滑动窗口最大值

 

注意:每次从队尾添加一个数进队列,一定要跟在比它大的元素后面。如果队尾元素不满足比当前添加元素大,就一直弹出。直到满足队尾元素比它大,或者队列弹空了。

原理:窗口中维持的信息是:有可能成为最大值的下标。每次添加一个值,已经在队列中且比当前元素小的元素就再也不可能成为最大值了。因为,那些数比当前元素小,且当前元素比它们更晚被淘汰 (更晚出队列,因为从左往右遍历的,如果窗口左边界也移动,当前元素一定比那些元素更晚淘汰!)

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length < k) {
            int max = Integer.MIN_VALUE;
            for (int num : nums) {
                max = Math.max(num, max);
            }
            return new int[]{max};
        }

        int l = -1;//窗口左边界
        int r = -1;//窗口右边界
        int index = 0;
        //存放答案的数组
        int[] res = new int[nums.length - k + 1];
        //双端队列,规则:队头元素始终是最大元素,队头到队尾由小到大
        LinkedList<Integer> maxQ = new LinkedList<>();
        while (++r < nums.length) {//当窗口还没遍历结束
            //如果队尾还有元素,且队尾元素比当前元素小
            while (!maxQ.isEmpty() && nums[maxQ.peekLast()] <= nums[r]) {
                maxQ.pollLast();
            }
            //到了这里说明队列为空或者队尾元素比当前元素大
            //当前元素入队尾
            maxQ.addLast(r);
            if (maxQ.peekFirst() == l) {//如果当前队头元素过期,出队
                maxQ.pollFirst();
            }
            if (r - l == k) {
                l++;
                res[index++] = nums[maxQ.peekFirst()];
            }
        }
        return res;
    }
}

2.每日温度

思路:维护一个由小到大的单调栈,栈顶元素最小。每次从栈顶加入一个数都要进行一次判断:

如果:加入的元素 <= 栈顶元素  满足单调性,直接入栈。

           加入的元素 > 栈顶元素  栈顶元素弹出,一直弹到栈为空或者加入元素小于等于栈顶元素。

每次从栈顶弹出一次元素,对该元素进行计算。当前加入的元素,就是比栈顶弹出的元素大且离它最近的元素。

class Solution {
   public int[] dailyTemperatures(int[] temperatures) {
        //单调栈
        LinkedList<Integer> stack = new LinkedList<>();
        //用于保存结果,res[i] 就代表比temperatures[i] 大的右边最近一个元素距离i的位置
        int[] res = new int[temperatures.length];
        for (int i = 0; i < temperatures.length; i++) {
            //当栈为空,或者栈顶元素比当前元素大
            while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
                Integer pop = stack.pop();
                res[pop] = i - pop;
            }
            //能到这一步说明,栈为空/栈顶元素大于等于当前元素
            stack.push(i);
        }
        return res;
    }
}

3.接雨水

思路:与上题略有不同的就是,本题还要找遍历当前值左侧最近且比当前值大的元素。就是栈中每个值下面一个元素。

class Solution {
    public int trap(int[] height) {
        int res = 0;
        LinkedList<Integer> stack = new LinkedList<>();

        for (int i = 0; i < height.length; i++) {
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int pop = stack.pop();
                Integer peek = stack.peek();
                if (peek != null) {//peek == null 说明当前出栈的栈顶元素左边没有比它大的数
                    // 出栈元素pop对应雨水面积的高为 左右两个中较小的那个 - pop元素的高 
                    int h = Math.min(height[i], height[peek]) - height[pop];
                    res += h * (i - peek - 1);
                }
            }
            stack.push(i);
        }
        return res;
    }
}

4.长度最小子序列(滑动窗口)

class Solution {
   public int minSubArrayLen(int target, int[] nums) {
        int res = Integer.MAX_VALUE;
        int l = 0;//代表滑动窗口左边界
        int r = 0;//代表滑动窗口右边界
        int cnt = 0;//用于计算窗口中元素的和
        while (r < nums.length) {
            while (r < nums.length) {//不断右移r往窗口中加元素
                cnt += nums[r];
                if (cnt >= target) break;
                r++;
            }
            while (l < r) {//不断缩小窗口
                if (cnt - nums[l] >= target) {
                    cnt -= nums[l];
                    l++;
                } else {
                    break;
                }
            }
            //到了这一步,要不就是窗口中元素大于等于target,要不就是r遍历完
            res = Math.min(res, r - l + 1);
            r++;
        }
        return cnt >= target ? res : 0;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1607844.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用LVGL提升交互效率:基于启明智显Model3A方案的7寸智能屏用户界面(UI)设计介绍

项目概述&#xff1a; 【启明智显】&#xff0c;作为一家专注于HMI和AIoT解决方案的公司&#xff0c;我们致力于为用户提供创新、可靠且高效的产品和解决方案。近日我们推出了高性能及高性价比的HMI芯片——Model3A。芯片搭载了强大的2D图形加速引擎&#xff0c;能够提供高达7…

RNN知识体系构筑:详尽阐述其理论基础、技术架构及其在处理序列数据挑战中的创新应用

一、为什么需要RNN 尽管神经网络被视为一种强大且理论上能够近似任何连续函数的模型&#xff0c;尤其当训练数据充足时&#xff0c;它们能够在输入空间中的某个点( x )映射到输出空间的特定值( y )&#xff0c;然而&#xff0c;这并不能完全解释为何在众多应用场景中&#xff…

基于Spring Boot的新生宿舍管理系统设计与开发

基于Spring Boot的新生宿舍管理系统设计与开发 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 报修处理管理界面图&#xff0c;在报修处理管理页面…

QA测试开发工程师面试题满分问答16: 微信输入框如何设计测试用例?

可以涵盖基础功能、用户交互、编辑撤回、权限相关、网络信号、异常、并发性能和安全性等方面的测试用例&#xff1a; 基础功能&#xff1a; 验证输入框是否能够接收用户输入&#xff0c;并显示输入的文字。检查输入框是否支持常见的字符输入&#xff0c;如字母、数字、标点符号…

Unity Shader 流光 边缘光

前言 Unity2021.3.23 一、实现原理 Time控制UV的变化,再采样一张流光贴图.即可实现流光效果。 二、效果及源码展示 1.流光效果 效果描述: 1.边缘光(菲尼尔), 2.从上到下扫描光. 效果图如下: 代码如下&#xff1a; Shader "Unlit/ScanCode" {Properties{_MainTe…

【数据结构】树与二叉树、树与森林部分习题与算法设计例题

目录 【数据结构】树与二叉树部分习题与算法设计例题一、单选题二、算法设计题判断二叉树是否为完全二叉树求二叉树的最小深度 以及 二叉树树高 树与二叉树知识点文章: 【数据结构】树与二叉树&#xff08;递归法先序、中序、后序、层次遍历二叉树、二叉树的建立以及求树高的方…

必看——通配符SSL证书在线免费申请方法!

申请通配符SSL证书&#xff0c;就相当于给你的网站及所有子域名都戴上同一顶加密“帽子”&#xff0c;保护它们的安全通信。以下是三步搞定的方法&#xff1a; 第一步&#xff1a;找权威机构 就像你要找个官方认证的地方办证明一样&#xff0c;先选一家靠谱的证书颁发机构&…

软考中级网络工程师-2024上岸宝典

1.软考是什么 简单说就是计算机技术 相关的国家级证书考试&#xff0c;想听专业点给大家截一张官网的图&#xff0c;不想听废话直接往下。 同为国家级证书的&#xff1a;注册会计师、法律职业资格证、一级建筑师&#xff0c;证书的价值是比较高的。 很多人都是在求职前或者大…

产废端实时音视频监控系统在运输车辆驾驶室中的应用

实时音视频监控系统可通过在运输车辆驾驶室安装音视频摄录设备&#xff0c;实现将运输车辆内部及周围环境音视频数据通过移动网络实时回传指挥中心的功能。 前端摄录设备主要负责采集车内外的视音频信息&#xff0c;为了保障车辆及运输人员 的安全&#xff0c;应合理选择摄录设…

探索半导体测试领域:哲讯TCC智能化管理系统的应用与优势

在半导体行业中&#xff0c;封装和测试环节是至关重要的一环。半导体封装测试是指将通过测试的晶圆按照产品型号及功能需求加工得到独立芯片的过程。半导体封测包括封装和测试两个环节&#xff0c;封装是保护芯片免受物理、化学等环境因素造成的损伤&#xff0c;增强芯片的散热…

node.js-模块化

定义&#xff1a;CommonJS模块是为Node.js打包Javascript代码的原始方式。Node.js还支持浏览器和其他Javascript运行时使用的ECMAScript模块标准。 在Node.js中&#xff0c;每个文件都被视为一个单独的模块。 概念&#xff1a;项目是由很多个模块文件组成的 好处&#xff1a…

一文读懂电阻并联电路和串联电路的特性

电阻并联电路是最基本的并联电路&#xff0c;所有的电路都可以转化为电阻串联电路和电阻并联电路来了解其工作原理。并联电路和串联电路具有完全不同的特性。它们是完全不同的电路&#xff0c;不能相互等效&#xff08;电阻并联电路图&#xff09;。 串联电路 特点与特性&…

教你三招,玩转AI通用大模型ChatGPT

工欲善其事必先利其器&#xff0c;想要高效的用好ChatGPT&#xff0c;首先&#xff0c;让我们从如何与它进行有效的对话开始。要知道&#xff0c;ChatGPT并非简单的问答机器&#xff0c;而是一个可以通过交互学习和适应的智能体。那么&#xff0c;如何让ChatGPT来更好地理解我们…

用云手机运营TikTok有什么好处?

在数字化浪潮的推动下&#xff0c;社交媒体平台正重塑商业推广与品牌建设的面貌。TikTok&#xff0c;这款全球热门的短视频应用&#xff0c;已经吸引了亿万用户的瞩目。对于出海电商和品牌推广而言&#xff0c;借助云手机运营TikTok&#xff0c;能够解锁更多潜在可能&#xff0…

短视频素材哪个网站好?8个视频素材下载免费网站

在视频制作的宏大舞台上&#xff0c;寻找恰到好处的素材是每位创作者的日常挑战。高清、无水印的视频素材不仅能够让你的作品焕发光彩&#xff0c;还能在讲述故事时增添无限动力。除了蛙学府&#xff0c;这里汇集了世界各地的视频素材网站&#xff0c;每个都带有独特的魅力&…

数据结构(七)——散列表

7.5.1 散列表的基本概念 散列表&#xff08;哈希表&#xff0c;Hash Table)︰是一种数据结构。特点是∶可以根据数据元素的关键字计算出它在散列表中的存储地址 散列函数&#xff08;哈希函数)︰AddrH(key)建立了“关键字”→“存储地址”的映射关系 冲突&#xff08;碰撞)︰在…

【Linux】地址空间虚拟地址

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 虚拟地址1.1 虚拟地址引入1.2 虚拟地址理解1.3 虚拟地址细节问题 2. 地址空间2.1 理解地址空间2.2 页表和写时拷贝 3. 进程调度 1. 虚拟地址 1.1 虚拟地址引入 先先来一个测试代码&#xff1a; 1 #include<st…

JMM与内存屏障

一、cpu多核并发缓存架构解析 JMM内存模型&#xff1a;java多线程内存模型跟cpu缓存模型类似&#xff0c;是基于cpu缓存模型来建立的&#xff0c;java线程内存模型是标准化的&#xff0c;屏蔽掉了底层不同计算机的区别 JMM数据原子操作 read(读取)&#xff1a;从主内存读取数据…

asp.net core 依赖注入后的服务生命周期

ASP.NET Core 依赖注入&#xff08;DI&#xff09;容器支持三种服务的生命周期选项&#xff0c;它们定义了服务实例的创建和销毁的时机。理解这三种生命周期对于设计健壯且高效的应用程序非常重要&#xff1a; 瞬时&#xff08;Transient&#xff09;&#xff1a; 瞬时服务每次…

【Flutter】GetX状态管理及路由管理用法

目录 一、安装二、使用1.安装GetX插件&#xff0c;快捷生成模版代码2.主入口MaterialApp改成GetMaterialApp3.定义路由常量RoutePath类、别名映射页面RoutePages类4. 初始initialRoute&#xff0c;getPages。5.调用 总结 一、安装 dependencies: get: ^4.6.6二、使用 1.安装G…