【题解】—— LeetCode一周小结

1.经营摩天轮的最大利润

题目链接: 1599. 经营摩天轮的最大利润

你正在经营一座摩天轮,该摩天轮共有 4 个座舱 ,每个座舱 最多可以容纳 4 位游客 。你可以 逆时针 轮转座舱,但每次轮转都需要支付一定的运行成本 runningCost 。摩天轮每次轮转都恰好转动 1 / 4 周。

给你一个长度为 n 的数组 customerscustomers[i] 是在第 i 次轮转(下标从 0 开始)之前到达的新游客的数量。这也意味着你必须在新游客到来前轮转 i 次。每位游客在登上离地面最近的座舱前都会支付登舱成本 boardingCost ,一旦该座舱再次抵达地面,他们就会离开座舱结束游玩。

你可以随时停下摩天轮,即便是 在服务所有游客之前 。如果你决定停止运营摩天轮,为了保证所有游客安全着陆,将免费进行所有后续轮转 。注意,如果有超过 4 位游客在等摩天轮,那么只有 4 位游客可以登上摩天轮,其余的需要等待 下一次轮转 。

返回最大化利润所需执行的 最小轮转次数 。 如果不存在利润为正的方案,则返回 -1

示例 1:
在这里插入图片描述

输入:customers = [8,3], boardingCost = 5, runningCost = 6
输出:3
解释:座舱上标注的数字是该座舱的当前游客数。

  1. 8 位游客抵达,4 位登舱,4 位等待下一舱,摩天轮轮转。当前利润为 4 * $5 - 1 * $6 = $14 。
  2. 3 位游客抵达,4 位在等待的游客登舱,其他 3 位等待,摩天轮轮转。当前利润为 8 * $5 - 2 * $6 = $28 。
  3. 最后 3 位游客登舱,摩天轮轮转。当前利润为 11 * $5 - 3 * $6 = $37 。 轮转 3 次得到最大利润,最大利润为 $37 。

示例 2:

输入:customers = [10,9,6], boardingCost = 6, runningCost = 4
输出:7
解释:

  1. 10 位游客抵达,4 位登舱,6 位等待下一舱,摩天轮轮转。当前利润为 4 * $6 - 1 * $4 = $20 。
  2. 9 位游客抵达,4 位登舱,11 位等待(2 位是先前就在等待的,9 位新加入等待的),摩天轮轮转。当前利润为 8 * $6 - 2 * $4 = $40 。
  3. 最后 6 位游客抵达,4 位登舱,13 位等待,摩天轮轮转。当前利润为 12 * $6 - 3 * $4 = $60 。
  4. 4 位登舱,9 位等待,摩天轮轮转。当前利润为 * $6 - 4 * $4 = $80 。
  5. 4 位登舱,5 位等待,摩天轮轮转。当前利润为 20 * $6 - 5 * $4 = $100 。
  6. 4 位登舱,1 位等待,摩天轮轮转。当前利润为 24 * $6 - 6 * $4 = $120 。
  7. 1 位登舱,摩天轮轮转。当前利润为 25 * $6 - 7 * $4 = $122 。 轮转 7 次得到最大利润,最大利润为$122 。

示例 3:

输入:customers = [3,4,0,5,1], boardingCost = 1, runningCost = 92
输出:-1
解释:

  1. 3 位游客抵达,3 位登舱,0 位等待,摩天轮轮转。当前利润为 3 * $1 - 1 * $92 = -$89 。
  2. 4 位游客抵达,4 位登舱,0 位等待,摩天轮轮转。当前利润为 7 * $1 - 2 * $92 = -$177 。
  3. 0 位游客抵达,0 位登舱,0 位等待,摩天轮轮转。当前利润为 7 * $1 - 3 * $92 = -$269 。
  4. 5 位游客抵达,4 位登舱,1 位等待,摩天轮轮转。当前利润为 11 * $1 - 4 * $92 = -$357 。
  5. 1 位游客抵达,2 位登舱,0 位等待,摩天轮轮转。当前利润为 13 * $1 - 5 * $92 = -$447 。 利润永不为正,所以返回 -1 。

提示:

n == customers.length

1 <= n <= 105

0 <= customers[i] <= 50

1 <= boardingCost, runningCost <= 100

题解

        新年第一天想到头痛

        看了半天题干里面 随时可以停下,免费进行后续 原来是指游客上摩天轮以后就不用管他们了…

方法1模拟

        我们可以直接模拟摩天轮的轮转过程,每次轮转时,累加等待的游客以及新到达的游客,然后最多 4 个人上船,更新等待的游客数和利润,记录最大利润与其对应的轮转次数。

class Solution {
    public int minOperationsMaxProfit(int[] customers, int boardingCost, int runningCost) {
        int ans = -1; // 初始化答案为-1,表示没有找到最大利润的方案
        int max = 0, t = 0; // 初始化最大利润为0,当前总利润为0
        int wait = 0, i = 0; // 初始化等待人数为0,轮转次数为0
        // 当等待人数大于0或者还有客户需要服务时,继续循环
        while (wait > 0 || i < customers.length) { 
            // 如果还有游客,将等待人数加入等待游客的人数
            wait += i < customers.length ? customers[i] : 0; 
            int up = Math.min(4, wait); // 计算可以上车的人数,最多为4人
            wait -= up; // 更新等待人数
            i++; // 轮转次数
            t += up * boardingCost - runningCost; // 更新当前总利润
            if (t > max) { // 如果当前总利润大于最大利润
                max = t; // 更新最大利润
                ans = i; // 更新答案为当前的轮转次数
            }
        }
        return ans; // 返回答案
    }
}

方法2贪心

        维护变量 time 代表转动的次数,那么成本就是 cost = time * runningCost;

        维护变量 customer 代表当前有多少等在上摩天轮的游客数量;

        枚举 customers[i] 那么当前 下标i 可以看做第几轮转动前的状态,那么更新 customer += customers[i] 得到当前等待的游客数量,由于存在大量游客(大于等于4)的可能,那么我们就可以直接得出,当前的等待游客如果 “尽量凑4人” 需要转多少轮,也就是更新 time += customer // 4 ,假设预计完成的轮次 time 大于当前 下标i 那么很明显,我们可以等后面游客过来补充 “填数”,如此形成循环。这个循环停止,代表当前游客不能再凑齐 4 人了,而且必须启动转一下,那么直接计算转一下把 “剩余游客” 接上。最后外循环判断还有没有下一轮游客过来即可;

        整个过程记录其最大利润并记录这个例如出现时的 time 即为答案;

        ps:计算如果直接用除法、乘法和余法会非常慢,可以直接位运算加速

class Solution {
    public int minOperationsMaxProfit(int[] customers, int boardingCost, int runningCost) {
        // 如果每次摩天轮的载客量乘以4小于等于运行成本,则无法盈利,返回-1
        if (boardingCost * 4 <= runningCost) return -1;
        
        int customer = 0, time = 0, income = 0, bestRes = 0, bestTime = -1;
        
        for (int i = 0; i < customers.length; ) {
            // 计算当前轮次前是否有新的游客加入,如果之前存在游客,尽量凑 4 人
            for (; i < customers.length && i <= time; i++) {
                customer += customers[i]; // 累加游客人数
                income += (customer >> 2 << 2) * boardingCost; // 计算收入
                time += customer >> 2; // 更新轮转次数
                customer &= 3; // 更新剩余游客人数
            }
            // 上轮循环跳出证明当前出现无游客凑够 4 人的情况,所以先核算之前满载 4 人的最优情况下利润是否创新高
            if (bestRes < income - runningCost * time) {
                bestRes = income - runningCost * time; // 更新最佳利润
                bestTime = time; // 更新轮转次数
            }
            // 到这里证明游客少于 4 人,且必须转动,那么就直接计算并判断
            income += boardingCost * customer; // 计算收入
            customer = 0; // 更新剩余游客人数
            time++; // 更新轮转次数
            if (bestRes < income - runningCost * time) {
                bestRes = income - runningCost * time; // 更新最佳利润
                bestTime = time; // 更新轮转次数
            }
        }

        return bestTime; // 返回轮转次数
    }
}

2.统计重复个数

题目链接:466. 统计重复个数

定义 str = [s, n] 表示 strn 个字符串 s 连接构成。

  • 例如,str == ["abc", 3] =="abcabcabc"

如果可以从 s2 中删除某些字符使其变为 s1,则称字符串 s1 可以从字符串 s2 获得。

  • 例如,根据定义,s1 = "abc" 可以从 s2 = "abdbec" 获得,仅需要删除加粗且用斜体标识的字符。

现在给你两个字符串 s1s2 和两个整数 n1n2 。由此构造得到两个字符串,其中 str1 = [s1, n1]str2 = [s2, n2]

请你找出一个最大整数 m ,以满足 str = [str2, m] 可以从 str1 获得。

示例 1:

输入:s1 = “acb”, n1 = 4, s2 = “ab”, n2 = 2
输出:2

示例 2:

输入:s1 = “acb”, n1 = 1, s2 = “acb”, n2 = 1
输出:1

提示:

1 <= s1.length, s2.length <= 100

s1 和 s2 由小写英文字母组成

1 <= n1, n2 <= 106

题解

方法(来自官方题解):找出循环节(个人觉得太麻烦)
思路

        由于题目中的 n1 和 n2 都很大,因此我们无法真正把 S1 = [s1, n1] 和 S2 = [s2, n2] 都显式地表示出来。由于这两个字符串都是不断循环的,因此我们可以考虑找出 S2 在 S1 中出现的循环节,如果我们找到了循环节,那么我们就可以很快算出 S2 在 S1 中出现了多少次了。

        有些读者可能对循环节这个概念会有些陌生,这个概念我们可以类比无限循环小数,如果从小数部分的某一位起向右进行到某一位止的一节数字「循环」出现,首尾衔接,称这种小数为「无限循环小数」,这一节数字称为「无限循环小数」。比如对于 3.56789789789… 这个无限循环小数,它的小数部分就是以 789 为一个「循环节」在无限循环,且开头可能会有部分不循环的部分,这个数字中即为 56。

        那么回到这题,我们可以将不断循环的 s2 组成的字符串类比作上面小数部分,去找是否存在一个子串,即「循环节」,满足不断在 S2 中循环,且这个循环节能对应固定数量的 s1 。如下图所示,在第一次出现后,S2 的子串 bdadc 构成一个循环节:之后 bdadc 的每次出现都需要有相应的两段 s1。
在这里插入图片描述
        当我们找出循环节后,我们即可知道一个循环节内包含 s1 的数量,以及在循环节出现前的 s1 的数量,这样就可以在 O(1)的时间内,通过简单的运算求出 s2 在 S1 中出现的次数了。当然,由于 S1 中 s1 的数量 n1 是有限的,因此可能会存在循环节最后一个部分没有完全匹配,如上图最后会单独剩一个 s1 出来无法完全匹配完循环节,这部分我们需要单独拿出来遍历处理统计。

        有些读者可能会怀疑循环节是否一定存在,这里我们给出的答案是肯定的,根据鸽笼原理,我们最多只要找过 |s2| + 1 个 s1,就一定会出现循环节。

class Solution {
    public int getMaxRepetitions(String s1, int n1, String s2, int n2) {
        if (n1 == 0) {
            return 0;
        }
        int s1cnt = 0, index = 0, s2cnt = 0;
        // recall 是我们用来找循环节的变量,它是一个哈希映射
        // 我们如何找循环节?假设我们遍历了 s1cnt 个 s1,此时匹配到了第 s2cnt 个 s2 中的第 index 个字符
        // 如果我们之前遍历了 s1cnt' 个 s1 时,匹配到的是第 s2cnt' 个 s2 中同样的第 index 个字符,那么就有循环节了
        // 我们用 (s1cnt', s2cnt', index) 和 (s1cnt, s2cnt, index) 表示两次包含相同 index 的匹配结果
        // 那么哈希映射中的键就是 index,值就是 (s1cnt', s2cnt') 这个二元组
        // 循环节就是;
        //    - 前 s1cnt' 个 s1 包含了 s2cnt' 个 s2
        //    - 以后的每 (s1cnt - s1cnt') 个 s1 包含了 (s2cnt - s2cnt') 个 s2
        // 那么还会剩下 (n1 - s1cnt') % (s1cnt - s1cnt') 个 s1, 我们对这些与 s2 进行暴力匹配
        // 注意 s2 要从第 index 个字符开始匹配
        Map<Integer, int[]> recall = new HashMap<Integer, int[]>();
        int[] preLoop = new int[2];
        int[] inLoop = new int[2];
        while (true) {
            // 我们多遍历一个 s1,看看能不能找到循环节
            ++s1cnt;
            for (int i = 0; i < s1.length(); ++i) {
                char ch = s1.charAt(i);
                if (ch == s2.charAt(index)) {
                    index += 1;
                    if (index == s2.length()) {
                        ++s2cnt;
                        index = 0;
                    }
                }
            }
            // 还没有找到循环节,所有的 s1 就用完了
            if (s1cnt == n1) {
                return s2cnt / n2;
            }
            // 出现了之前的 index,表示找到了循环节
            if (recall.containsKey(index)) {
                int[] value = recall.get(index);
                int s1cntPrime = value[0];
                int s2cntPrime = value[1];
                // 前 s1cnt' 个 s1 包含了 s2cnt' 个 s2
                preLoop = new int[]{s1cntPrime, s2cntPrime};
                // 以后的每 (s1cnt - s1cnt') 个 s1 包含了 (s2cnt - s2cnt') 个 s2
                inLoop = new int[]{s1cnt - s1cntPrime, s2cnt - s2cntPrime};
                break;
            } else {
                recall.put(index, new int[]{s1cnt, s2cnt});
            }
        }
        // ans 存储的是 S1 包含的 s2 的数量,考虑的之前的 preLoop 和 inLoop
        int ans = preLoop[1] + (n1 - preLoop[0]) / inLoop[0] * inLoop[1];
        // S1 的末尾还剩下一些 s1,我们暴力进行匹配
        int rest = (n1 - preLoop[0]) % inLoop[0];
        for (int i = 0; i < rest; ++i) {
            for (int j = 0; j < s1.length(); ++j) {
                char ch = s1.charAt(j);
                if (ch == s2.charAt(index)) {
                    ++index;
                    if (index == s2.length()) {
                        ++ans;
                        index = 0;
                    }
                }
            }
        }
        // S1 包含 ans 个 s2,那么就包含 ans / n2 个 S2
        return ans / n2;
    }
}

方法:递归
        我们预处理出以字符串 s2 的每个位置 i 开始匹配一个完整的 s1 后,下一个位置 jjj 以及经过了多少个 s2,即 d[i]=(cnt,j),其中 cnt 表示匹配了多少个 s2,而 j 表示字符串 s2 的下一个位置。

        接下来,我们初始化 j=0,然后循环 n1 次,每一次将 d[j][0] 加到答案中,然后更新 j=d[j][1]。

        最后得到的答案就是 n1 个 s1所能匹配的 s2 的个数,除以 n2 即可得到答案。

class Solution {
    public int getMaxRepetitions(String s1, int n1, String s2, int n2) {
        int len1 = s1.length();
        int len2 = s2.length();
        int[][] DP = new int[len2][0]; // 0 记录s2匹配的个数 1 记录下一个s2匹配的位置
        for (int i = 0; i < len2; i++) {
            int j = i; // 记录下一个s2匹配的位置
            int cnt = 0; // 记录s2匹配的个数: 一个s1中可能含有多个s2串
            for (int k = 0; k < len1; k++) { // 遍历s1字符串进行匹配
                if (s1.charAt(k) == s2.charAt(j)) { // 匹配相同的字符串
                    j++; // 指向下一个位置
                    if (j == len2) { // s2匹配完一段
                        j = 0; // 重置索引
                        cnt++; // 匹配个数加1
                    }
                }
            }
            // 循环结束,j的位置始终对标在s1中的位置
            DP[i] = new int[]{cnt, j}; // 匹配完一次 则将结果添加到数组中
        }
        int ans = 0; // 定义结果变量
        for (int j = 0; n1 > 0; n1--) { // 遍历n1,记录每个循环中s2下标出现的数量
            ans += DP[j][0]; // 累加匹配数
            j = DP[j][1]; // 获取下一个位置
        }
        return ans / n2; // 将n2个s2作为一个整体数量,砍掉n2个就是结果
    }
}

3.从链表中移除节点

题目链接:2487. 从链表中移除节点

给你一个链表的头节点 head 。

移除每个右侧有一个更大数值的节点。

返回修改后链表的头节点 head 。

示例 1:
在这里插入图片描述

输入:head = [5,2,13,3,8]
输出:[13,8]
解释:需要移除的节点是 5 ,2 和 3 。

  • 节点 13 在节点 5 右侧。
  • 节点 13 在节点 2 右侧。
  • 节点 8 在节点 3 右侧。

示例 2:

输入:head = [1,1,1,1]
输出:[1,1,1,1]
解释:每个节点的值都是 1 ,所以没有需要移除的节点。

提示:

给定列表中的节点数目在范围 [1, 105] 内
1 <= Node.val <= 105

题解

方法:递归
        由题意可知,节点对它右侧的所有节点都没有影响,因此我们可以对它的右侧节点递归地进行移除操作:

  • 该节点为空,那么递归函数返回空指针。

  • 该节点不为空,那么先对它的右侧节点进行移除操作,得到一个新的子链表,如果子链表的表头节点值大于该节点的值,那么移除该节点,否则将该节点作为子链表的表头节点,最后返回该子链表。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNodes(ListNode head) {
        if (head == null) {
            return null;
        }
        head.next = removeNodes(head.next);
        if (head.next != null && head.val < head.next.val) {
            return head.next;
        } else {
            return head;
        }
    }
}

4.被列覆盖的最多行数

题目链接:2397. 被列覆盖的最多行数

给你一个下标从 0 开始、大小为 m x n 的二进制矩阵 matrix ;另给你一个整数 numSelect,表示你必须从 matrix 中选择的 不同 列的数量。

如果一行中所有的 1 都被你选中的列所覆盖,则认为这一行被 覆盖 了。

形式上,假设 s = {c1, c2, ...., cnumSelect} 是你选择的列的集合。对于矩阵中的某一行 row ,如果满足下述条件,则认为这一行被集合 s 覆盖:

  • 对于满足 matrix[row][col] == 1 的每个单元格 matrix[row][col]0 <= col <= n - 1),col 均存在于 s 中,或者
  • row不存在 值为 1 的单元格。
    你需要从矩阵中选出 numSelect 个列,使集合覆盖的行数最大化。

返回一个整数,表示可以由 numSelect 列构成的集合 覆盖最大行数

示例 1:
在这里插入图片描述

输入:matrix = [[0,0,0],[1,0,1],[0,1,1],[0,0,1]], numSelect = 2
输出:3
解释:
图示中显示了一种覆盖 3 行的可行办法。 选择 s = {0, 2} 。

  • 第 0 行被覆盖,因为其中没有出现 1 。
  • 第 1 行被覆盖,因为值为 1 的两列(即 0 和 2)均存在于 s 中。
  • 第 2 行未被覆盖,因为 matrix[2][1] == 1 但是 1 未存在于 s 中。
  • 第 3 行被覆盖,因为 matrix[2][2] == 1 且 2 存在于 s 中。 因此,可以覆盖 3 行。 另外 s = {1, 2} 也可以覆盖 3 行,但可以证明无法覆盖更多行。

示例 2:
在这里插入图片描述

输入:matrix = [[1],[0]], numSelect = 1
输出:2
解释:
选择唯一的一列,两行都被覆盖了,因为整个矩阵都被覆盖了。 所以我们返回 2 。

提示:

m == matrix.length
n == matrix[i].length
1 <= m, n <= 12
matrix[i][j]要么是 0 要么是 1
1 <= numSelect <= n

题解
方法:二进制枚举

        我们先将矩阵中的每一行转换成一个二进制数,记录在数组 rows中,其中 rows[i]表示第 i 行对应的二进制数,而 rows[i] 这个二进制数的第 j 位表示矩阵第 i 行第 j 列的值。

        接下来,我们枚举所有的 2n2 种列选择方案,其中 n 为矩阵的列数。对于每一种列选择方案,我们判断是否选中了 numSelect列,如果不是,则跳过。否则,我们统计矩阵中有多少行中的所有 1 都被选中的列覆盖,即统计有多少行的二进制数 rows[i]与列选择方案 mask 按位与的结果等于 rows[i],并更新最大的行数。

class Solution {
    public int maximumRows(int[][] matrix, int numSelect) {
        // 获取矩阵的行数m和列数n
        int m = matrix.length, n = matrix[0].length;
        // 定义一个长度为m的一维数组rows,用于存储每一行的二进制表示
        int[] rows = new int[m];
        // 遍历矩阵的每一行
        for (int i = 0; i < m; ++i) {
            // 遍历矩阵的每一列
            for (int j = 0; j < n; ++j) {
                // 如果当前位置的元素值为1,则将该位置对应的二进制位设置为1
                if (matrix[i][j] == 1) {
                    rows[i] |= 1 << j;
                }
            }
        }
        // 初始化答案ans为0
        int ans = 0;
        // 遍历所有可能的二进制掩码(从1到2^n-1)
        for (int mask = 1; mask < 1 << n; ++mask) {
            // 如果当前掩码中1的个数不等于numSelect,则跳过当前循环
            if (Integer.bitCount(mask) != numSelect) {
                continue;
            }
            // 初始化计数器t为0
            int t = 0;
            // 遍历每一行的二进制表示
            for (int x : rows) {
                // 如果当前行的二进制表示与当前掩码进行按位与运算后的结果等于当前行的二进制表示,则计数器t加1
                if ((x & mask) == x) {
                    ++t;
                }
            }
            // 更新答案ans为ans和t中的较大值
            ans = Math.max(ans, t);
        }
        // 返回答案ans
        return ans;
    }
}


5.队列中可以看到的人数

题目链接:1944. 队列中可以看到的人数

有 n 个人排成一个队列,从左到右 编号为 0 到 n - 1 。给你以一个整数数组 heights ,每个整数 互不相同,heights[i] 表示第 i 个人的高度。

一个人能 看到 他右边另一个人的条件是这两人之间的所有人都比他们两人 。更正式的,第 i 个人能看到第 j 个人的条件是 i < j 且 min(heights[i], heights[j]) > max(heights[i+1], heights[i+2], …, heights[j-1]) 。

请你返回一个长度为 n 的数组 answer ,其中 answer[i] 是第 i 个人在他右侧队列中能 看到人数

示例 1:在这里插入图片描述

输入:heights = [10,6,8,5,11,9]
输出:[3,1,2,1,1,0]
解释:
第 0 个人能看到编号为 1 ,2 和
4 的人。
第 1 个人能看到编号为 2 的人。
第 2 个人能看到编号为 3 和 4 的人。
第 3 个人能看到编号为 4 的人。
第 4个人能看到编号为 5 的人。
第 5 个人谁也看不到因为他右边没人。

示例 2:

输入:heights = [5,1,2,3,10]
输出:[4,1,1,1,0]

提示:

n == heights.length

1 <= n <= 105

1 <= heights[i] <= 105

heights 中所有数 互不相同 。

提示 1

如何在二次复杂度中解决这个问题?

提示 2

对于从索引i开始的每个子数组,继续寻找新的最大值,直到找到一个大于arr[i]的值。

提示 3

因为极限很高,你需要一个线性解。

提示 4

在从末尾到开始迭代数组时,使用堆栈来保持数组值的排序。

提示 5

继续从堆栈中弹出元素,直到找到一个大于arr[i]的值,这些是我可以看到的人。

题解
方法:单调栈
        由题可知,对于第 i 个人来说,他能看到的人一定是按从左到右高度严格单调递增的。

        因此,我们可以倒序遍历数组 heights,用一个从栈顶到栈底单调递增的栈 stk 记录已经遍历过的人的高度。

        对于第 i 个人,如果栈不为空并且栈顶元素小于 heights[i],累加当前第 i 个人能看到的人数,然后将栈顶元素出栈,直到栈为空或者栈顶元素大于等于 heights[i]。如果此时栈不为空,说明栈顶元素大于等于 heights[i],那么第 i 个人能看到的人数还要再加 1。

        接下来,我们将 heights[i]入栈,继续遍历下一个人。

        遍历结束后,返回答案数组 ans。

class Solution {
    public int[] canSeePersonsCount(int[] heights) { 
        int n = heights.length; 
        int[] ans = new int[n]; 
        Deque<Integer> stk = new ArrayDeque<>(); // 双端队列stk,用于存储高度信息
        for (int i = n - 1; i >= 0; --i) { // 倒序遍历数组
            while (!stk.isEmpty() && stk.peek() < heights[i]) { // 如果栈不为空且栈顶元素小于当前元素
                stk.pop(); // 弹出栈顶元素
                ++ans[i]; 
            }
            if (!stk.isEmpty()) { 
                ++ans[i];
            }
            stk.push(heights[i]); // 将当前元素压入栈中
        }
        return ans; 
    }
}

6.在链表中插入最大公约数

题目链接: 2807. 在链表中插入最大公约数

给你一个链表的头 head ,每个结点包含一个整数值。

在相邻结点之间,请你插入一个新的结点,结点值为这两个相邻结点值的 最大公约数

请你返回插入之后的链表。

两个数的 最大公约数 是可以被两个数字整除的最大正整数。

示例 1:
在这里插入图片描述

输入:head = [18,6,10,3]

输出:[18,6,6,2,10,1,3]

解释:第一幅图是一开始的链表,第二幅图是插入新结点后的图(蓝色结点为新插入结点)。

  • 18 和 6 的最大公约数为 6 ,插入第一和第二个结点之间。
  • 6 和 10 的最大公约数为 2 ,插入第二和第三个结点之间。
  • 10 和 3 的最大公约数为 1 ,插入第三和第四个结点之间。

所有相邻结点之间都插入完毕,返回链表。

示例 2:

在这里插入图片描述

输入:head = [7]

输出:[7]

解释:第一幅图是一开始的链表,第二幅图是插入新结点后的图(蓝色结点为新插入结点)。

没有相邻结点,所以返回初始链表。

提示:

链表中结点数目在 [1, 5000] 之间。

1 <= Node.val <= 1000

题解
方法:遍历
        遍历链表,在当前节点node 后面插入gcd节点,同时gcd节点指向下一个node 节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    // 插入最大公约数节点的方法
    public ListNode insertGreatestCommonDivisors(ListNode head) {
        ListNode node = head; // 初始化当前节点为头节点
        while (node.next != null) { // 遍历链表,直到最后一个节点
            node.next = new ListNode(gcd(node.val, node.next.val), node.next); // 将当前节点的下一个节点替换为最大公约数节点
            node = node.next.next; // 移动到新插入的最大公约数节点的下一个节点
        }
        return head; // 返回修改后的链表头节点
    }

    // 计算两个整数的最大公约数的方法
    public int gcd(int a, int b) {
        while (b != 0) { // 当b不为0时,继续循环
            int tmp = a % b; // 计算a除以b的余数
            a = b; // 将b的值赋给a
            b = tmp; // 将余数赋给b
        }
        return a; // 返回最大公约数
    }
}

7.赎金信

题目链接:383. 赎金信

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = “a”, magazine = “b”

输出:false

示例 2:

输入:ransomNote = “aa”, magazine = “ab”

输出:false

示例 3:

输入:ransomNote = “aa”, magazine = “aab”

输出:true

提示:

1 <= ransomNote.length, magazine.length <= 105

ransomNote 和 magazine 由小写英文字母组成

题解

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] memo = new int[26];
        
        // 遍历magazine字符串中的每个字符,将其转换为小写字母后在memo数组中对应的位置加1
        for (char ch : magazine.toCharArray()) memo[ch - 'a']++;
        
        // 遍历ransomNote字符串中的每个字符,将其转换为小写字母后在memo数组中对应的位置减1
        for (char ch : ransomNote.toCharArray()) if (--memo[ch - 'a'] < 0) return false;
        
        // 如果减1后的值小于0,说明ransomNote中的某个字符在magazine中不存在,返回false
        // 如果ransomNote中的所有字符都在magazine中存在,返回true
        return true;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JRT打印元素绘制协议之-A4Double

以前打印相信很多人因为A4打印两个报告头大过&#xff0c;M要把一堆报告既有A4的也有A5的还有微生物的&#xff0c;可能输出Page还不那么严谨。要么换页不对叠加了、要么多空白页、中间夹杂A4报告就更加头大。也有的人为了打印页码的共几页而头大。 借助新设计的优势&#xff…

uni-app中轮播图实现大图预览

参考效果 当轮播图滑动切换的时候更新自定义下标&#xff0c;当图片被点击的时候大图预览。 参考代码 商品详情页轮播图交互 <script setup lang"ts"> // 轮播图变化时 const currentIndex ref(0) const onChange: UniHelper.SwiperOnChange (ev) > …

TAX税类小知识

历史及发展 我国作为流转税为主国家&#xff0c;目前经历3类税制&#xff1a;统收统付--包干制---分税制&#xff0c; 分别对应时间为建国--改革开放--93年朱提出分税延续至今&#xff1b; 未来趋势必然是完善所得税。 ps:流转税是间接税&#xff0c;间接转嫁给消费者&#…

TypeScript基础

ts学习 目录概述需求&#xff1a; 设计思路实现思路分析1.TypeScript 基础类型2.TypeScript 变量声明3.TypeScript 接口4.TypeScript 类5.TypeScript 函数5.TypeScript 泛型5.TypeScript 枚举TypeScript 类型推论TypeScript 类型兼容性TypeScript 高级类型TypeScript 迭代器和生…

深度解析Dubbo的基本应用与高级应用:负载均衡、服务超时、集群容错、服务降级、本地存根、本地伪装、参数回调等关键技术详解

负载均衡 官网地址&#xff1a; http://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/ 如果在消费端和服务端都配置了负载均衡策略&#xff0c; 以消费端为准。 这其中比较难理解的就是最少活跃调用数是如何进行统计的&#xff1f; 讲道理&#xff0c; 最少活跃数…

金和OA C6 upload_json 任意文件上传漏洞

产品介绍 金和网络是专业信息化服务商,为城市监管部门提供了互联网监管解决方案,为企事业单位提供组织协同OA系统开发平台,电子政务一体化平台,智慧电商平台等服务。 漏洞概述 金和 OA C6 upload_json接口处存在任意文件上传漏洞&#xff0c;攻击者可以通过构造特殊请求包上…

视频号小店新手该怎么运营?团队实操经验分享!

我是电商珠珠 视频号小店作为一个新兴平台&#xff0c;自然预示着有更多的机会在。 所以有很多新手想要了解这个平台并进行入驻&#xff0c;但是却不知道该怎么去运营&#xff0c;没有货源怎么办。 今天我就来给大家详细的讲一下。 一、入驻 入驻的话需要一张企业的营业执…

阿里巴巴秋招前端笔试题

单选题 下面的 JSX 代码中&#xff0c;哪一个无法达到预期的效果&#xff1f; A.<h2>Hello World</h2> B.<input type”checkbox”/> C.<div class”msg-box”>{msg}</div> D.<label htmlFor”name”>Leo</label> E.div styl…

人工智能_机器学习091_使用三维瑞士卷数据_KMeans聚类算法进行瑞士卷数据聚类---人工智能工作笔记0131

然后我们首先来构建一下数据 准备瑞士卷数据: import numpy as np 导入数学计算包 import matplotlib.pyplot as plt 导入画图包 #自底向上聚类 from sklearn.cluster import AgglceerativeClustering 导入分层聚类模型 from sklearn.datasets import make_swiss_roll # 瑞士卷…

“五岳杯”2023量子计算挑战赛启动仪式在南方科技大学成功举办

​2023年11月4日下午&#xff0c;由北京图象图形学学会、中国移动云能力中心&#xff08;以下简称“移动云”&#xff09;主办&#xff0c;南方科技大学承办&#xff0c;北京玻色量子科技有限公司&#xff08;以下简称“玻色量子”&#xff09;协办的“五岳杯”2023量子计算挑战…

RT-Thread:SPI万能驱动 SFUD 驱动Flash W25Q64,通过 STM32CubeMX 配置 STM32 SPI 驱动

关键词&#xff1a;SFUD,FLASH,W25Q64&#xff0c;W25Q128&#xff0c;STM32F407 说明&#xff1a;RT-Thread 系统 使用 SPI万能驱动 SFUD 驱动 Flash W25Q64&#xff0c;通过 STM32CubeMX 配置 STM32 SPI 驱动。 提示&#xff1a;SFUD添加后的存储位置 1.打开RT-Thread Sett…

OpenGL学习笔记-Blending

混合方程中&#xff0c;Csource是片段着色器输出的颜色向量&#xff08;the color output of the fragment shader&#xff09;&#xff0c;其权重为Fsource。Cdestination是当前存储在color buffer中的颜色向量&#xff08;the color vector that is currently stored in the …

ROS2 Humble学习笔记

本文发表与个人的github pages。部分内容未同步到这里。 想查看完整内容&#xff0c;请移步到ROS2 Humble学习笔记。 一、前言 2013年的时候已经接触ROS了&#xff0c;当时断断续续学习了一些ROS的基础知识。16年搬到深圳之后&#xff0c;也有幸参加过星火的一次关于ROS的一些…

S281 LoRa网关在智能电力监测系统中的应用

随着能源消耗的增加和环境保护的要求&#xff0c;智能电力监测系统在电力行业得到了广泛的应用。作为一家领先的科技公司&#xff0c;钡铼技术有限公司推出的S281 LoRa网关为智能电力监测系统提供了强大的支持和解决方案。本文将重点介绍S281 LoRa网关在智能电力监测系统中的应…

【Android Studio】APP练手小项目——切换图片APP

本项目效果&#xff1a; 前言&#xff1a;本项目最终实现生成一个安卓APP软件&#xff0c;点击按钮可实现按钮切换图片。项目包含页面布局、功能实现的逻辑代码以及设置APP图标LOGO和自定义APP名称。 关于Android Studio的下载与安装见我的博文&#xff1a;Android Studio 最新…

Native Crash回溯栈

获取调用栈四种方案&#xff1a;Android Native Crash 收集 1、使用系统的<unwind.h>库 可以获取到出错文件与函数名。只不过需要自己解析函数符号&#xff0c;同时经常会捕获到系统错误&#xff0c;需要手动过滤。 2、libcorkscrew 在4.1.1以上&#xff0c;5.0以下&…

C# 强制类型转换和as区别和不同使用场景

文章目录 1.强制类型转换2. as 运算符3.实例总结&#xff1a; 在C#中&#xff0c;as 和 强制类型转换&#xff08;例如 (T)value&#xff09;的主要区别在于它们处理类型转换不成功时的行为和适用场景&#xff1a; 1.强制类型转换 使用语法&#xff1a;Type variable (Type)…

设计模式之适配器模式【结构型模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

“高端”的位运算

王有志&#xff0c;一个分享硬核Java技术的互金摸鱼侠加入Java人的提桶跑路群&#xff1a;共同富裕的Java人 原计划迭代作为预备知识的收尾&#xff0c;不过在解2的幂和4的幂时&#xff0c;想到关于数字2的问题可以通过位运算去解决&#xff0c;因此补充了关于位运算的内容。 …

基于ssm的生鲜在线销售系统的设计与实现论文

摘 要 使用旧方法对生鲜在线销售系统的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在生鲜在线销售系统的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。这次开发的生…