3007.价值和小于等于K的最大数字

目标

给你一个整数 k 和一个整数 x 。整数 num 的价值是它的二进制表示中在 x2x3x …… 等位置处 set bit(指在某数的二进制表示中值为 1 的二进制位) 的数目(从最低有效位开始)。下面的表格包含了如何计算价值的例子。

x num Binary Representation Price
1 13 000001101 3
2 13 000001101 1
2 233 011101001 3
3 13 000001101 1
3 362 101101010 2

num累加价值 是从 1num 的数字的 价值。如果 num 的累加价值小于或等于 k 则被认为是 廉价 的。

请你返回 最大 的廉价数字。

示例 1:

输入:k = 9, x = 1
输出:6
解释:由下表所示,6 是最大的廉价数字。
x num Binary Representation Price Accumulated Price
1 1 001 1 1
1 2 010 1 2
1 3 011 2 4
1 4 100 1 5
1 5 101 2 7
1 6 110 2 9
1 7 111 3 12

示例 2:

输入:k = 7, x = 2
输出:9
解释:由下表所示,9 是最大的廉价数字。
x num Binary Representation Price Accumulated Price
2 1 0001 0 0
2 2 0010 1 1
2 3 0011 1 2
2 4 0100 0 2
2 5 0101 0 2
2 6 0110 1 3
2 7 0111 1 4
2 8 1000 1 5
2 9 1001 1 6
2 10 1010 2 8

说明:

  • 1 <= k <= 10^15
  • 1 <= x <= 8

提示:

  • Binary search the answer.
  • In each step of the binary search you should calculate the number of the set bits in the ith position. Then calculate the sum of them.

思路

给定步长x,数字的价值定义为从最低有效位开始,以步长x遍历数字的二进制表示,并记录bit为1的个数。数字n的累加价值定义为 1 ~ n 的价值之和。给定数字k,求累加价值不超过k的最大数字。

先尝试模拟暴力求解。注意到返回值是 long 类型,说明n的规模超过了10^9,即便是 O(n) 的解法也会超时,并且每个数字都要循环bit位计数,肯定超时。我们需要要找一种 O(logn) 的解法。

提示说使用二分查找,问题的关键是如何直接计算出给定数字的累加价值。

// todo

代码

性能

3154.到达第 K 级台阶的方案数

目标

给你有一个 非负 整数 k 。有一个无限长度的台阶,最低 一层编号为 0 。

Alice 有一个整数 jump ,一开始值为 0 。Alice 从台阶 1 开始,可以使用 任意 次操作,目标是到达第 k 级台阶。假设 Alice 位于台阶 i ,一次 操作 中,Alice 可以:

  • 向下走一级到 i - 1 ,但该操作 不能 连续使用,如果在台阶第 0 级也不能使用。
  • 向上走到台阶 i + 2^jump 处,然后 jump 变为 jump + 1 。

请你返回 Alice 到达台阶 k 处的总方案数。

注意,Alice 可能到达台阶 k 处后,通过一些操作重新回到台阶 k 处,这视为不同的方案。

示例 1:

输入:k = 0
输出:2
解释:
2 种到达台阶 0 的方案为:
- Alice 从台阶 1 开始。
    执行第一种操作,从台阶 1 向下走到台阶 0 。
- Alice 从台阶 1 开始。
    执行第一种操作,从台阶 1 向下走到台阶 0 。
    执行第二种操作,向上走 20 级台阶到台阶 1 。
    执行第一种操作,从台阶 1 向下走到台阶 0 。

示例 2:

输入:k = 1
输出:4
解释:

4 种到达台阶 1 的方案为:
- Alice 从台阶 1 开始,已经到达台阶 1 。
- Alice 从台阶 1 开始。
    执行第一种操作,从台阶 1 向下走到台阶 0 。
    执行第二种操作,向上走 20 级台阶到台阶 1 。
- Alice 从台阶 1 开始。
    执行第二种操作,向上走 20 级台阶到台阶 2 。
    执行第一种操作,向下走 1 级台阶到台阶 1 。
- Alice 从台阶 1 开始。
    执行第一种操作,从台阶 1 向下走到台阶 0 。
    执行第二种操作,向上走 20 级台阶到台阶 1 。
    执行第一种操作,向下走 1 级台阶到台阶 0 。
    执行第二种操作,向上走 21 级台阶到台阶 2 。
    执行第一种操作,向下走 1 级台阶到台阶 1 。

说明:

0 <= k <= 10^9

思路

//todo

代码

性能

552.学生出勤记录II

目标

可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

  • 'A':Absent,缺勤
  • 'L':Late,迟到
  • 'P':Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

  • 按 总出勤 计,学生缺勤('A')严格 少于两天。
  • 学生 不会 存在 连续 3 天或 连续 3 天以上的迟到('L')记录。

给你一个整数 n ,表示出勤记录的长度(次数)。请你返回记录长度为 n 时,可能获得出勤奖励的记录情况 数量 。答案可能很大,所以返回对 10^9 + 7 取余 的结果。

示例 1:

输入:n = 2
输出:8
解释:
有 8 种长度为 2 的记录将被视为可奖励:
"PP" , "AP", "PA", "LP", "PL", "AL", "LA", "LL" 
只有"AA"不会被视为可奖励,因为缺勤次数为 2 次(需要少于 2 次)。

示例 2:

输入:n = 1
输出:3

示例 3:

输入:n = 10101
输出:183236316

说明:

  • 1 <= n <= 10^5

思路

使用 A L P 三种字符组成长度为 n 的字符串,要求字符串中最多包含一个 'A',并且不能连续出现三个及以上的 'L',问满足条件的字符串有多少个。

很明显需要使用动态规划求解。定义状态 dp[i][hadAbsent][ithState] 表示长度为i,以ithState(0:L; 1:P; 2:A)结尾,hadAbsent(0 ~ i-1,包含1/不包含0)'A'的字符串的个数。

状态转移方程为

  • dp[i][0][0] = dp[i-2][0][0] + 2*dp[i-2][0][1] LPL PPL PLL
  • dp[i][0][1] = dp[i-1][0][0] + dp[i-1][0][1] LP PP
  • dp[i][0][2] = dp[i-1][0][0] + dp[i-1][0][1] LA PA
  • dp[i][1][0] = dp[i-2][1][0] + 2*dp[i-2][1][1] + 2*dp[i-2][0][2] + dp[i-1][0][2] (A)LPL (A)PPL (A)PLL ALL APL AL
  • dp[i][1][1] = dp[i-1][1][0] + dp[i-1][1][1] + dp[i-1][0][2] (A)LP (A)PP AP

初始条件需要提前计算:

  • dp[0][0][0] = 1; L
  • dp[0][0][1] = 1; P
  • dp[0][0][2] = 1; A
  • dp[1][0][0] = 2; LL PL
  • dp[1][0][1] = 2; LP PP
  • dp[1][0][2] = 2; LA PA
  • dp[1][1][0] = 1; AL
  • dp[1][1][1] = 1; AP

模运算(取余)满足以下规律:

  • (a + b + c) % mod = ((a % mod) + (b % mod) + (c % mod)) % mod
  • (a * b * c) % mod = ((a % mod) * (b % mod) * (c % mod)) % mod

代码

/**
 * @date 2024-08-19 9:53
 */
public class CheckRecord552 {
    private static final int MOD = 1000000007;

    public int checkRecord(int n) {
        if (n == 1) {
            return 3;
        }
        long[][][] dp = new long[n][2][3];
        dp[0][0][0] = 1;
        dp[0][0][1] = 1;
        dp[0][0][2] = 1;

        dp[1][0][0] = 2;
        dp[1][0][1] = 2;
        dp[1][0][2] = 2;
        dp[1][1][0] = 1;
        dp[1][1][1] = 1;

        for (int i = 2; i < n; i++) {
            int pre = i - 1;
            int prepre = i - 2;
            dp[i][0][0] = (dp[prepre][0][0] + 2 * dp[prepre][0][1]) % MOD;
            dp[i][0][1] = (dp[pre][0][0] + dp[pre][0][1]) % MOD;
            dp[i][0][2] = (dp[pre][0][0] + dp[pre][0][1]) % MOD;
            dp[i][1][0] = (dp[prepre][1][0] + 2 * dp[prepre][1][1] + 2 * dp[prepre][0][2] + dp[pre][0][2]) % MOD;
            dp[i][1][1] = (dp[pre][1][0] + dp[pre][1][1] + dp[pre][0][2]) % MOD;
        }
        return (int) ((dp[n - 1][0][0] + dp[n - 1][0][1] + dp[n - 1][0][2] + dp[n - 1][1][0] + dp[n - 1][1][1]) % MOD);
    }

}

性能

551.学生出勤记录I

目标

给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

  • 'A':Absent,缺勤
  • 'L':Late,迟到
  • 'P':Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

  • 按 总出勤 计,学生缺勤('A')严格 少于两天。
  • 学生 不会 存在 连续 3 天或 连续 3 天以上的迟到('L')记录。

如果学生可以获得出勤奖励,返回 true ;否则,返回 false 。

示例 1:

输入:s = "PPALLP"
输出:true
解释:学生缺勤次数少于 2 次,且不存在 3 天或以上的连续迟到记录。

示例 2:

输入:s = "PPALLL"
输出:false
解释:学生最后三天连续迟到,所以不满足出勤奖励的条件。

说明:

  • 1 <= s.length <= 1000
  • s[i] 为 'A'、'L' 或 'P'

思路

记录缺勤总次数,判断连续迟到天数即可。

代码


/**
 * @date 2024-08-18 19:52
 */
public class CheckRecord551 {

    public boolean checkRecord(String s) {
        int n = s.length();
        int absent = 0;
        int late = 0;
        for (int i = 0; i < n; i++) {
            char c = s.charAt(i);
            if (c == 'A') {
                absent++;
                if (absent == 2) {
                    return false;
                }
            } else if (c == 'L') {
                late++;
                if (late == 3) {
                    return false;
                }
                continue;
            }
            late = 0;
        }
        return true;
    }
}

性能

3137.K周期字符串需要的最少操作次数

目标

给你一个长度为 n 的字符串 word 和一个整数 k ,其中 k 是 n 的因数。

在一次操作中,你可以选择任意两个下标 i 和 j,其中 0 <= i, j < n ,且这两个下标都可以被 k 整除,然后用从 j 开始的长度为 k 的子串替换从 i 开始的长度为 k 的子串。也就是说,将子串 word[i..i + k - 1] 替换为子串 word[j..j + k - 1] 。

返回使 word 成为 K 周期字符串 所需的 最少 操作次数。

如果存在某个长度为 k 的字符串 s,使得 word 可以表示为任意次数连接 s ,则称字符串 word 是 K 周期字符串 。例如,如果 word == "ababab",那么 word 就是 s = "ab" 时的 2 周期字符串 。

示例 1:

输入:word = "leetcodeleet", k = 4
输出:1
解释:可以选择 i = 4 和 j = 0 获得一个 4 周期字符串。这次操作后,word 变为 "leetleetleet" 。

示例 2:

输入:word = "leetcoleet", k = 2
输出:3
解释:可以执行以下操作获得一个 2 周期字符串。

i j word
0 2 etetcoleet
4 0 etetetleet
6 0 etetetetet

说明:

  • 1 <= n == word.length <= 10^5
  • 1 <= k <= word.length
  • k 能整除 word.length 。
  • word 仅由小写英文字母组成。

思路

给定字符串 word 以及周期 k(k 为 word 长度的因数,即可以被 word 长度整除),将字符串分为 word.length()/k 份,每次操作可以将其中一份替换为其它份字符串。问最少需要多少次操作,可以使每份字符串都相等。

用份数减去出现次数最多的子串即可。

代码


/**
 * @date 2024-08-17 20:44
 */
public class MinimumOperationsToMakeKPeriodic3137 {

    /**
     * 使用substring效率更高
     * 循环步长为k
     */
    public int minimumOperationsToMakeKPeriodic(String word, int k) {
        int n = word.length();
        int max = 0;
        int parties = n / k;
        // 优化点:指定初始容量
        Map<String, Integer> map = new HashMap<>(parties);
        // 出错点,这里循环条件使 <= n,而非小于n,因为我们使用的使substring,不包括结束index
        for (int i = k; i <= n; i += k) {
            String period = word.substring(i - k, i);
            // 优化点:这里可以直接拿到cnt,不用后面再从map里get
            int cnt = map.merge(period, 1, Integer::sum);
            max = Math.max(cnt, max);
        }
        return parties - max;
    }

}

性能

50.快速幂

目标

实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,x^n)。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2^-2 = 1/2^2 = 1/4 = 0.25

说明:

  • -100.0 < x < 100.0
  • -2^31 <= n <= 2^31-1
  • n 是一个整数
  • 要么 x 不为零,要么 n > 0 。
  • -10^4 <= x^n <= 10^4

思路

通常求 x^n 最直接的算法是迭代相乘,时间复杂度为 O(n)。但是当 n 超过 10^6 就需要考虑 O(logn) 的算法了。

快速幂的核心思想是根据幂次 power 的二进制表示来确定 对应的指数 是否需要计入结果。

比如 2^13, 幂次 13 的二进制表示为 1101,而 2^13 = 2^1 * 2^4 * 2^8,其中 1、4、8 刚好对应幂次的二进制表示中bit为 1 所代表的数字。我们可以循环将幂右移 1 位,直到幂为 0,并在循环中累计 base = base * base,当相应 bit 位为 1 时将 base 计入结果即可。

代码

/**
 * @date 2024-08-16 10:19
 */
public class MyPow50 {

    public double myPow(double x, int n) {
        long cnt = n >= 0 ? n : -((long) n);
        double res = 1.0;
        while (cnt > 0) {
            if ((cnt & 1) == 1) {
                res = res * x;
            }
            x = x * x;
            cnt = cnt >> 1;
        }
        return n == 0 ? 1 : n > 0 ? res : 1 / res;
    }

}

性能

3148.矩阵中的最大得分

目标

给你一个由 正整数 组成、大小为 m x n 的矩阵 grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧的任意单元格(不必相邻)。从值为 c1 的单元格移动到值为 c2 的单元格的得分为 c2 - c1 。

你可以从 任一 单元格开始,并且必须至少移动一次。

返回你能得到的 最大 总得分。

示例 1:

输入:grid = [[9,5,7,3],[8,9,6,1],[6,7,14,3],[2,5,3,1]]
输出:9
解释:从单元格 (0, 1) 开始,并执行以下移动:
- 从单元格 (0, 1) 移动到 (2, 1),得分为 7 - 5 = 2 。
- 从单元格 (2, 1) 移动到 (2, 2),得分为 14 - 7 = 7 。
总得分为 2 + 7 = 9 。

示例 2:

输入:grid = [[4,3,2],[3,2,1]]
输出:-1
解释:从单元格 (0, 0) 开始,执行一次移动:从 (0, 0) 到 (0, 1) 。得分为 3 - 4 = -1 。

说明:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 10^5
  • 1 <= grid[i][j] <= 10^5

思路

有一个二维矩阵 grid,可以从任意格子出发向下或右移动(不必相邻),移动的得分为元素值之差 to - from,求最大的得分数。

首先想到动态规划,关键点是想清楚最大得分其实就是以当前元素为左上顶点(不包括其自身),以 m - 1, n - 1 为右下顶点的矩形中的最大值减去当前元素值。如果把状态定义错误还是会超时的,比如当前元素出发所能取得的最大值,需要依次向下/向右比较,时间复杂度 O(m * n * (m +n))m = 1000 n = 95,循环次数 10^5 * (10^3 + 95) 超时,耗时878 ms,下午又提交了几次,耗时 1200 ~ 1300 ms。参考特殊数组II 中的时间复杂度分析。对于O(n)的算法,10^8 差不多就是极限了,单个用例在1s左右,多个肯定超时。

代码

/**
 * @date 2024-08-15 0:22
 */
public class MaxScore3148 {

    /**
     * 修改了dp的定义,表示以i,j为起点到右下的矩形的最大值
     * 执行通过
     */
    public int maxScore_v2(List<List<Integer>> grid) {
        int m = grid.size();
        int n = grid.get(0).size();
        int[][] dp = new int[m][n];
        int res = Integer.MIN_VALUE;
        dp[m - 1][n - 1] = grid.get(m - 1).get(n - 1);
        for (int i = n - 2; i >= 0; i--) {
            int cur = grid.get(m - 1).get(i);
            // 注意这里使用的是上一个位置为顶点的矩形中的最大值
            res = Math.max(dp[m - 1][i + 1] - cur, res);
            // 如果先进行这个计算,然后再进行上面的计算,就有会出现不移动的情况,但积分只有移动才能取得,可以是负值
            dp[m - 1][i] = Math.max(cur, dp[m - 1][i + 1]);
        }
        for (int i = m - 2; i >= 0; i--) {
            int cur = grid.get(i).get(n - 1);
            res = Math.max(dp[i + 1][n - 1] - cur, res);
            dp[i][n - 1] = Math.max(cur, dp[i + 1][n - 1]);
        }

        for (int i = m - 2; i >= 0; i--) {
            for (int j = n - 2; j >= 0; j--) {
                int cur = grid.get(i).get(j);
                res = Math.max(dp[i + 1][j] - cur, res);
                res = Math.max(dp[i][j + 1] - cur, res);
                dp[i][j] = Math.max(cur, dp[i + 1][j]);
                dp[i][j] = Math.max(dp[i][j], dp[i][j + 1]);
            }
        }
        return res;
    }

    /**
     * 560 / 564 超时  m = 1000  n = 95
     * O(m * n * (m +n)) 循环次数 10^5 * (10^3 + 95)
     * 878 ms
     */
    public int maxScore_v1(List<List<Integer>> grid) {
        int m = grid.size();
        int n = grid.get(0).size();
        int[][] dp = new int[m][n];
        for (int[] row : dp) {
            Arrays.fill(row, Integer.MIN_VALUE);
        }
        dp[m - 1][n - 1] = 0;
        int res = Integer.MIN_VALUE;
        for (int i = n - 2; i >= 0; i--) {
            for (int j = i + 1; j < n; j++) {
                int diff = grid.get(m - 1).get(j) - grid.get(m - 1).get(i);
                if (dp[m - 1][j] > 0) {
                    dp[m - 1][i] = Math.max(diff + dp[m - 1][j], dp[m - 1][i]);
                }
                dp[m - 1][i] = Math.max(diff, dp[m - 1][i]);
            }
            res = Math.max(dp[m - 1][i], res);
        }
        for (int i = m - 2; i >= 0; i--) {
            for (int j = i + 1; j < m; j++) {
                int diff = grid.get(j).get(n - 1) - grid.get(i).get(n - 1);
                if (dp[j][n - 1] > 0) {
                    dp[i][n - 1] = Math.max(diff + dp[j][n - 1], dp[i][n - 1]);
                }
                dp[i][n - 1] = Math.max(diff, dp[i][n - 1]);
            }
            res = Math.max(dp[i][n - 1], res);
        }

        for (int i = m - 2; i >= 0; i--) {
            for (int j = n - 2; j >= 0; j--) {
                for (int h = i + 1; h < m; h++) {
                    int rowDiff = grid.get(h).get(j) - grid.get(i).get(j);
                    if (dp[h][j] > 0) {
                        dp[i][j] = Math.max(rowDiff + dp[h][j], dp[i][j]);
                    }
                    dp[i][j] = Math.max(rowDiff, dp[i][j]);
                }
                for (int k = j + 1; k < n; k++) {
                    int colDiff = grid.get(i).get(k) - grid.get(i).get(j);
                    if (dp[i][k] > 0) {
                        dp[i][j] = Math.max(colDiff + dp[i][k], dp[i][j]);
                    }
                    dp[i][j] = Math.max(colDiff, dp[i][j]);
                }
                res = Math.max(dp[i][j], res);
            }
        }
        return res;
    }

}

性能

3152.特殊数组II

目标

如果数组的每一对相邻元素都是两个奇偶性不同的数字,则该数组被认为是一个 特殊数组 。

有一个整数数组 nums 和一个二维整数矩阵 queries,对于 queries[i] = [fromi, toi],请你帮助检查子数组 nums[fromi..toi] 是不是一个 特殊数组 。

返回布尔数组 answer,如果 nums[fromi..toi] 是特殊数组,则 answer[i] 为 true ,否则,answer[i] 为 false 。

示例 1:

输入:nums = [3,4,1,2,6], queries = [[0,4]]
输出:[false]
解释:
子数组是 [3,4,1,2,6]。2 和 6 都是偶数。

示例 2:

输入:nums = [4,3,1,6], queries = [[0,2],[2,3]]
输出:[false,true]
解释:
子数组是 [4,3,1]。3 和 1 都是奇数。因此这个查询的答案是 false。
子数组是 [1,6]。只有一对:(1,6),且包含了奇偶性不同的数字。因此这个查询的答案是 true。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5
  • 1 <= queries.length <= 10^5
  • queries[i].length == 2
  • 0 <= queries[i][0] <= queries[i][1] <= nums.length - 1

思路

类似于特殊数组I,只不过是判断子数组是否是特殊数组。我们可以记录奇偶性相同的下标,如果 queries[i] = [fromi, toi] 包含其中的下标对返回false。但是如果所有元素的奇偶性都相同,下标对有n个,查询也是n次,O(n^2) 对于 10^5 的数据规模会超时。

我们可以将问题转化一下,由于仅考虑相邻元素的奇偶性,将数组中的偶数元素都替换为0,奇数元素都替换为1,这样0与1交替出现的是特殊数组。使用前缀和判断不了是否交替出现,只能初步排除一些区间。

之所以超时是因为进行了许多重复判断,我们想直接判断查询区间是否包含奇偶相同的下标对。可以使用二分查找,如果查出的from,to下标相同,说明不相交。

这里比较坑的一点是,题目中没有说明如果查询区间只包含一个值视为奇偶性不同。

log2(10^5) ≈ 16.6 时间复杂度为O(nlogn),耗时30ms,说明10^6的数据规模,O(n)的解法不会超时,以后多留意一下。

看了题解,其实可以使用前缀和,只不过不是将原数组转为0或1计算前缀和,而是定义数组diff,原数组nums相邻元素奇偶性相同为0,不同为1。对应给定的区间,我们就可以根据diff的前缀和判断是否含有奇偶性相同的元素。时间复杂度为O(n),耗时3ms。

也有使用动态规划求解的,记录最近一次奇偶性相同的位置。时间复杂度为O(n),耗时3ms。

代码

/**
 * @date 2024-08-14 9:58
 */
public class IsArraySpecial3152 {

    public boolean[] isArraySpecial_v1(int[] nums, int[][] queries) {
        int n = nums.length;
        List<Integer> from = new ArrayList<>();
        List<Integer> to = new ArrayList<>();
        for (int i = 1; i < n; i++) {
            int j = i - 1;
            if (nums[i] % 2 == nums[j] % 2) {
                from.add(j);
                to.add(i);
            }
        }
        int l = from.size();
        int[] fromArray = new int[l];
        int[] toArray = new int[l];
        for (int i = 0; i < l; i++) {
            fromArray[i] = from.get(i);
            toArray[i] = to.get(i);
        }
        int length = queries.length;
        boolean[] res = new boolean[length];
        Arrays.fill(res, true);
        for (int i = 0; i < length; i++) {
            int[] query = queries[i];
            if (query[0] == query[1]) {
                // 如果查询区间相同认为是特殊区间
                res[i] = true;
                continue;
            }
            int fromIndex = Arrays.binarySearch(fromArray, query[0]);
            if (fromIndex >= 0) {
                // 如果需要查询的数组包含了query[0],由于前面判断了相等的情况,
                // 执行到这里query[1] > query[0],而对应的toIndex 为 fromIndex + 1,
                // 推出 query[1] >= toIndex,说明包含奇偶性相同的下标对,
                res[i] = false;
                continue;
            }
            int toIndex = Arrays.binarySearch(toArray, query[1]);
            if (fromIndex != toIndex) {
                // 执行到这里,fromIndex < 0,如果 toIndex >= 0,由于 query[0] < query[1],推出 query[0] <= fromIndex
                // 如果 toIndex < 0,说明查询区间开始与结束下标都没有找到,
                // 如果插入位置不同,说明也包含了奇偶性相同的元素
                res[i] = false;
            }
        }
        return res;
    }

}

性能

3151.特殊数组I

目标

如果数组的每一对相邻元素都是两个奇偶性不同的数字,则该数组被认为是一个 特殊数组 。

Aging 有一个整数数组 nums。如果 nums 是一个 特殊数组 ,返回 true,否则返回 false。

示例 1:

输入:nums = [1]
输出:true
解释:只有一个元素,所以答案为 true。

示例 2:

输入:nums = [2,1,4]
输出:true
解释:只有两对相邻元素: (2,1) 和 (1,4),它们都包含了奇偶性不同的数字,因此答案为 true。

示例 3:

输入:nums = [4,3,1,6]
输出:false
解释:nums[1] 和 nums[2] 都是奇数。因此答案为 false。

说明:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100

思路

判断数组是否是特殊数组,所谓特殊数组指其每一对相邻元素的奇偶性不同。

依题意判断即可。

代码

/**
 * @date 2024-08-13 8:56
 */
public class IsArraySpecial3135 {
    public boolean isArraySpecial(int[] nums) {
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] % 2 == nums[i - 1] % 2) {
                return false;
            }
        }
        return true;
    }
}

性能

676.实现一个魔法字典

目标

设计一个使用单词列表进行初始化的数据结构,单词列表中的单词 互不相同 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。

实现 MagicDictionary 类:

  • MagicDictionary() 初始化对象
  • void buildDict(String[] dictionary) 使用字符串数组 dictionary 设定该数据结构,dictionary 中的字符串互不相同
  • bool search(String searchWord) 给定一个字符串 searchWord ,判定能否只将字符串中 一个 字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 true ;否则,返回 false 。

示例:

输入
["MagicDictionary", "buildDict", "search", "search", "search", "search"]
[[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]]
输出
[null, null, false, true, false, false]

解释
MagicDictionary magicDictionary = new MagicDictionary();
magicDictionary.buildDict(["hello", "leetcode"]);
magicDictionary.search("hello"); // 返回 False
magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True
magicDictionary.search("hell"); // 返回 False
magicDictionary.search("leetcoded"); // 返回 False

说明:

  • 1 <= dictionary.length <= 100
  • 1 <= dictionary[i].length <= 100
  • dictionary[i] 仅由小写英文字母组成
  • dictionary 中的所有字符串 互不相同
  • 1 <= searchWord.length <= 100
  • searchWord 仅由小写英文字母组成
  • buildDict 仅在 search 之前调用一次
  • 最多调用 100 次 search

思路

实现这样一个数据结构,初始化一个单词列表,查询 给定单词修改一个字母(修改的字母与原字母不能相同)后是否位于字典中。言外之意,要在字典中查找长度相同,但字母有一个不同的单词。

如何判断两个长度 相等 的字符串之间是否只有一个字母不同?直接的想法是逐个字母比较,并记录不同字母的个数,最坏情况下查询的时间复杂度是 O(mn),m为被查单词的平均长度,n为单词列表长度。

一个更好的想法是构建一颗字典树,这样只需沿着给定单词的路径去查找而无需与字典中单词一一比较,最坏的情况是第一个字母就不同,这样得遍历字典中同级的其它字母下的子树共|Σ|个,时间复杂度降为O(|Σ|m)。最好的情况就是单词的最后一个字母不同,时间复杂度为O(m)。

针对本题的数据范围以及调用次数,暴力解法就足够了,字典树反而体现不出优势。

代码

/**
 * @date 2024-08-12 9:08
 */
public class MagicDictionary676 {

    private String[] dictionary;

    public MagicDictionary676() {

    }

    public void buildDict(String[] dictionary) {
        this.dictionary = dictionary;
    }

    public boolean search(String searchWord) {
        for (String word : dictionary) {
            int length = searchWord.length();
            if (word.length() != length) {
                continue;
            }
            int cnt = 0;
            for (int i = 0; i < length; i++) {
                if (word.charAt(i) != searchWord.charAt(i)) {
                    cnt++;
                }
                if (cnt > 1) {
                    // 提前结束比较,效率有明显提升
                    break;
                }
            }
            if (cnt == 1) {
                return true;
            }
        }
        return false;
    }

}

性能