20241027LeetCode421周賽

anyj1024發表於2024-10-30

目錄

  • 題目A - 陣列的最大因子得分
  • 題目B - 字串轉換後的長度Ⅰ
  • 題目C - 最大公約數相等的子序列數量
  • 題目D - 字串轉換後的長度Ⅱ
  • 總結與思考

題目A - 陣列的最大因子得分

題目概述

給你一個整數陣列 nums。

因子得分 定義為陣列所有元素的最小公倍數(LCM)與最大公約數(GCD)的 乘積。

在 最多 移除一個元素的情況下,返回 nums 的 最大因子得分。

注意,單個數字的 LCM 和 GCD 都是其本身,而 空陣列 的因子得分為 0。

解題思路

  1. 暴力列舉每一個元素作為被移除元素在此題也是可以的,因為n的大小隻有100。
  2. 考慮最佳化,我們可以用一個字首陣列和一個字尾陣列,去記錄當前字首或字尾的gcd/lcm
  3. 最後遍歷前字尾陣列即可,注意gcd(a, b, c) = gcd(gcd(a, b), c)lcm(a, b, c) = lcm(lcm(a, b), c)

程式碼實現

public long maxScore(int[] nums) {
    int n = nums.length;
    if (n == 1) return 1l * nums[0] * nums[0];
    long[] preGCD = new long[n], sufGCD = new long[n];
    long[] preLCM = new long[n], sufLCM = new long[n];
    preGCD[0] = nums[0];
    preLCM[0] = nums[0];
    for (int i = 1; i < n; i++) {
        preGCD[i] = gcd(preGCD[i - 1], nums[i]);
        preLCM[i] = lcm(preLCM[i - 1], nums[i]);
    }

    sufGCD[n - 1] = nums[n - 1];
    sufLCM[n - 1] = nums[n - 1];
    for (int i = n - 2; i >= 0; i--) {
        sufGCD[i] = gcd(sufGCD[i + 1], nums[i]);
        sufLCM[i] = lcm(sufLCM[i + 1], nums[i]);
    }

    long ans = sufGCD[0] * sufLCM[0];
    for (int i = 0; i < n; i++) {
        long curGCD, curLCM;
        if (i == 0) {
            curGCD = sufGCD[1];
            curLCM = sufLCM[1];
        } else if (i == n - 1) {
            curGCD = preGCD[n - 2];
            curLCM = preLCM[n - 2];
        } else {
            curGCD = gcd(preGCD[i - 1], sufGCD[i + 1]);
            curLCM = lcm(preLCM[i - 1], sufLCM[i + 1]);
        }
        ans = Math.max(ans, curGCD * curLCM);
    }
    return ans;
}

private static long gcd(long a, long b) {
    return b == 0 ? a : gcd(b, a % b);
}

private static long lcm(long a, long b) {
    return (a / gcd(a, b)) * b;
}

複雜度分析

  • 時間複雜度O(n),處理前字尾陣列O(n),遍歷陣列O(n)
  • 空間複雜度O(n),用於儲存前字尾陣列

題目B - 字串轉換後的長度Ⅰ

題目概述

給你一個字串 S 和一個整數 t,表示要執行的 轉換 次數。每次轉換需要根據以下規則替換字串 S 中的每個字元:

  • 如果字元是 'z',則將其替換為字串 "ab"
  • 否則,將其替換為字母表中的下一個字元。例如,'a' 替換為 'b''b' 替換為 'c',依此類推。

返回恰好執行 t 次轉換後得到的字串的長度。

由於答案可能非常大,返回其對 (10^9 + 7) 取餘的結果。

解題思路

  1. 先看資料範圍,t最大為10^5且只有小寫字母,所以模擬每一次變化的情況是可行的
  2. 用兩個長度為26的長整形陣列,其中一個初始化每個位置設定為1,然後按照題意逐步模擬各個元素出現的變化
  3. 維護一個count陣列,計算源字串中各個字母出現的頻數
  4. 對結果求和,求和邏輯為count[i] * dp[i],邊求和邊取模防止溢位

程式碼實現

public int lengthAfterTransformations(String s, int t) {
    int n = s.length(), MOD = 1000000007;
    long[] dp1 = new long[26], dp2 = new long[26];
    Arrays.fill(dp1, 1);

    for (int step = 1; step <= t; step++) {
        for (int c = 0; c < 26; c++) {
            if (c < 25) {
                dp2[c] = dp1[c + 1];
            } else {
                dp2[c] = (dp1[0] + dp1[1]) % MOD;
            }
        }
        for (int i = 0; i < 26; i++) {
            dp1[i] = dp2[i];
        }
    }

    int[] count = new int[26];
    for (char c : s.toCharArray()) {
        count[c - 'a']++;
    }
    long ans = 0;
    for (int c = 0; c < 26; c++) {
        ans = (ans + dp1[c] * count[c]) % MOD;
    }
    return (int) ans;
}

複雜度分析

  • 時間複雜度O(t + n),嚴格來說是確切的運算量是2 * 26 * t + n = 52t + n
  • 空間複雜度O(1),使用的都是常數空間

題目C - 最大公約數相等的子序列數量

題目概述

給你一個整數陣列 nums

請你統計所有滿足一下條件的非空 子序列(seq1, seq2) 的數量:

  • 子序列 seq1seq2 不相交,意味著 nums 中不存在同時出現在兩個序列中的下標。
  • seq1 元素的 GCD 等於 seq2 元素的 GCD。

返回滿足條件的子序列對的總數。

由於答案可能非常大,請返回其對 (10^9 + 7) 取餘的結果。

解題思路

  1. 遇事不決,先觀察資料量,陣列長度和元素大小都在200以內,而極端情況200 * 200 * 200 = 8 * 10^6是符合題意的
  2. 定義一個二維的dp陣列,dp[i][j]表示在已經選擇的元素形成兩個子序列的GCD分別為ij的方案數
  3. 對於陣列中的每個元素num,建立一個新的動態規劃狀態,分為 (不加入,加入seq1,加入seq2) 三種狀態
  4. gcd1max(nums)的情況累加即可

程式碼實現

public static int subsequencePairCount(int[] nums) {
    int n = nums.length, maxNum = Integer.MIN_VALUE;
    int MOD = 1000000007;
    for (int i = 0; i < n; i++) maxNum = Math.max(maxNum, nums[i]);
    int[][] dp = new int[maxNum + 1][maxNum + 1];
    dp[0][0] = 1;

    for (int num : nums) {
        int[][] temp = new int[maxNum + 1][maxNum + 1];
        for (int i = 0; i <= maxNum; i++) {
            for (int j = 0; j <= maxNum; j++) {
                int prev = dp[i][j];
                if (prev == 0) continue;
                temp[i][j] = (temp[i][j] + prev) % MOD;
                int i1 = i == 0 ? num : gcd(num, i);
                temp[i1][j] = (temp[i1][j] + prev) % MOD;
                int j1 = j == 0 ? num : gcd(num, j);
                temp[i][j1] = (temp[i][j1] + prev) % MOD;
            }
        }
        dp = temp;
    }

    int ans = 0;
    for (int i = 1; i <= maxNum; i++) {
        ans = (ans + dp[i][i]) % MOD;
    }
    return ans;
}

private static int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

複雜度分析

  • 時間複雜度O(n * k ^ 2), 其中k為陣列最大值,主要時間複雜度在處理dp狀態
  • 空間複雜度O(k ^ 2), 用於進行dp狀態轉移

題目D - 字串轉換後的長度Ⅱ

題目概述

給你一個由小寫英文字母組成的字串 s,一個整數 t 表示要執行的 轉換 次數,以及一個長度為 26 的陣列 nums。每次 轉換 需要根據以下規則對字串 s 中的每個字元:

  1. s[i] 替換為字母表中後續的 nums[s[i] - 'a'] 個連續字元。例如,如果 s[i] = 'a'nums[0] = 3,則字元 'a' 轉換為後面 3 個連續字元,結果為 "bcd"

  2. 如果轉換超過了 'z',則迴繞到字母表開頭。例如,如果 s[i] = 'y'nums[24] = 3,則字元 'y' 轉換為後面 3 個連續字元,結果為 "zab"

返回 恰好 執行 t 次轉換後得到的字串的 長度

由於答案可能非常大,返回答案對 10^9 + 7 取餘的結果。

解題思路

  1. 先看資料範圍,t最大為10^9,那每次模擬過程變化顯然是不現實了,由於是一個線性的遞推關係,考慮使用矩陣快速冪最佳化
  2. 對於一個簡單的子問題,有這樣一個轉移方程dp[i][j] = (dp[i - 1][j + 1] + ······ + dp[i - 1][j + nums[j]])
  3. 初始假設每個元素出現次數為1,初始化為一個26 * 1的矩陣prev, 考慮單次轉換過程,其實是去乘一個26 * 26大小的矩陣,設矩陣為matrix
  4. 單次的狀態轉移應該是: curr = matrix * prev,最終的運算結果應該是res = matrix ^ t * prev
  5. 最後計算每個元素在原始串中出現的次數,對各種情況求和即可,邊求和邊取模防止溢位

程式碼實現

public static int lengthAfterTransformations(String s, int t, List<Integer> nums) {
    int MOD = 1000000007, n = 26;
    long[][] matrix = new long[n][n];
    for (int i = 0; i < n; i++) {
        int len = nums.get(i);
        for (int j = 0; j < len; j++) {
            int k = (j + 1 + i) % n;
            matrix[k][i] = (matrix[k][i] + 1) % MOD;
        }
    }

    long[][] quickPowMatrix = matrixPower(matrix, t, MOD);
    long[] cnt1 = new long[n], cnt2 = new long[n];
    for (int i = 0; i < s.length(); i++) cnt1[s.charAt(i) - 'a']++;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cnt2[i] = (cnt2[i] + quickPowMatrix[i][j] * cnt1[j]) % MOD;
        }
    }

    long ans = 0;
    for (int i = 0; i < n; i++) ans = (ans + cnt2[i]) % MOD;
    return (int) ans;
}

private static long[][] matrixPower(long[][] matrix, int t, int mod) {
    int n = matrix.length;
    long[][] ans = new long[n][n];
    for (int i = 0; i < n; i++) {
        ans[i][i] = 1;
    }
    while (t > 0) {
        if ((t & 1) == 1) {
            ans = multiplyMatrix(ans, matrix, mod);
        }
        matrix = multiplyMatrix(matrix, matrix, mod);
        t >>= 1;
    }
    return ans;
}

private static long[][] multiplyMatrix(long[][] a, long[][] b, int mod) {
    int n = a.length;
    long[][] ans = new long[n][n];
    for (int i = 0; i < n; i++) {
        for (int k = 0; k < n; k++) {
            if (a[i][k] == 0) continue;
            for (int j = 0; j < n; j++) {
                if (b[k][j] == 0) continue;
                ans[i][j] = (ans[i][j] + a[i][k] * b[k][j]) % mod;
            }
        }
    }
    return ans;
}

複雜度分析

  • 時間複雜度O(n + t * 26 ^ 3), 遍歷陣列O(n), 處理矩陣O(t * 26 ^ 3)
  • 空間複雜度O(1), 與字符集相關,該題為26,常數空間

總結與思考

感覺題目還是不錯的,上了大分~~~

相關文章