LeetCode Weekly Competition Journey #43 Is Computer Science Essentially Mathematics?

⭐️This article has been included in AndroidFamily, technical and workplace issues, please follow the public account [Peng Xurui] and BaguTree Pro Knowledge Planet to ask questions.

The key to learning data structures and algorithms is to master the algorithmic thinking framework behind the problem. The more abstract your thinking, the wider the problem domain it can cover and the more complex it is to understand. In this column, Xiao Peng shares with you the problem-solving reports of each LeetCode weekly competition, and lets you experience the journey to score points together.

This article is the 43rd article in the LeetCode ascending journey series. To review previous issues, please go to the end of the article~

LeetCode Biweekly Contest 112

T1. Determine whether strings can be made equal through operations I (Easy)

  • Tag: Simulation

T2. Determine whether strings can be equalized through operations II (Medium)

  • Tags: simulation, counting, sorting

T3. Maximum sum of almost unique subarrays (Medium)

  • Tags: sliding window, counting

T4. Count the maximum number of beautiful values ​​of k subsequences of a string (Hard)

  • Tags: enumeration, greedy, sorting, multiplication principle, combinatorial numbers


T1. Determine whether strings can be made equal through operations I (Easy)

https://leetcode.cn/problems/check-if-strings-can-be-made-equal-with-operations-i/

Problem Solving (Simulation)

Because only positions that are an even multiple of the distance can be exchanged, it is equivalent to comparing whether the elements on the same parity subscript of two strings are equal.

  • Writing method 1: Based on hash table
class Solution {
    
    
    fun canBeEqual(s1: String, s2: String): Boolean {
    
    
        return setOf(s1[0], s1[2]) == setOf(s2[0], s2[2]) && setOf(s1[1], s1[3]) == setOf(s2[1], s2[3])
    }
}
  • Writing method 2: Based on string
class Solution:
    def checkStrings(self, s1: str, s2: str) -> bool:
        return sorted(s1[0::2]) == sorted(s2[0::2]) and sorted(s1[1::2]) == sorted(s2[1::2])

Complexity analysis:

  • Time complexity: O ( 1 ) O(1)O(1)
  • Space complexity: O ( 1 ) O(1)O(1)

T2. Determine whether strings can be equalized through operations II (Medium)

https://leetcode.cn/problems/check-if-strings-can-be-made-equal-with-operations-ii/

Problem Solving (Simulation)

Same as above, count whether the number of elements on the odd and even subscripts is equal.

Writing method 1: based on counting;

class Solution {
    
    
    fun checkStrings(s1: String, s2: String): Boolean {
    
    
        val U = 26
        val cnts = Array(2) {
    
     IntArray(U) }
        for ((i, e) in s1.withIndex()) {
    
    
            cnts[i % 2][e - 'a']++
        }
        for ((i, e) in s2.withIndex()) {
    
    
            cnts[i % 2][e - 'a']--
        }
        return cnts[0].all {
    
    it == 0} && cnts[1].all {
    
    it == 0}
    }
}

Complexity analysis:

  • Time complexity: O ( n + U ) O(n + U)O ( n+U ) Linear traversal time and counting time;
  • Space complexity: O ( U ) O(U)O ( U ) counting array space.

Writing method 2: Based on string:

class Solution:
    def checkStrings(self, s: str, t: str) -> bool:
        return all(sorted(s[p::2]) == sorted(t[p::2]) for p in range(2))

Complexity analysis:

  • Time complexity: O (nlgn) O(nlgn)O ( n l g n ) sorting time;
  • Space complexity: O ( n ) O(n)O ( n ) constructs the string space.

T3. Maximum sum of almost unique subarrays (Medium)

https://leetcode.cn/problems/maximum-sum-of-almost-unique-subarray/

Solution (sliding window + counting)

Sliding window template question, maintain the number and sum of different elements in the window:

class Solution {
    
    
    fun maxSum(nums: List<Int>, m: Int, k: Int): Long {
    
    
        var cnts = HashMap<Int, Int>()
        var type = 0
        var sum = 0L
        var ret = 0L
        for (j in nums.indices) {
    
    
            // 滑入
            cnts[nums[j]] = cnts.getOrDefault(nums[j], 0) + 1
            if (1 == cnts[nums[j]]!!) type++
            sum += nums[j]
            // 滑出
            if (j >= k) {
    
    
                val i = j - k
                cnts[nums[i]] = cnts[nums[i]]!! - 1
                if (0 == cnts[nums[i]]) type --
                sum -= nums[i]
            }
            // 记录
            if (j >= k - 1 && type >= m) {
    
    
                ret = max(ret, sum)
            }
        }
        return ret
    }
}

Complexity analysis:

  • Time complexity: O ( n ) O(n)O ( n ) linear traversal time;
  • Space complexity: O ( n ) O(n)O ( n ) hash table space.

T4. Count the maximum number of beautiful values ​​of k subsequences of a string (Hard)

https://leetcode.cn/problems/count-k-subsequences-of-a-string-with-maximum-beauty/

problem analysis

  • Problem goal: Find all lengths of kkThe number of subsequences whose beauty value is the maximum value in the subsequence of k ;
  • Problem elements: first calculate the length as kkThe maximum beauty value of the subsequence of k , and then calculate the number of subsequence solutions that satisfy the maximum beauty value;
  • Key information 1: Select non-repeating letters for the subsequence;
  • Key information 2: The same character can construct different subsequences at different positions in the original string;
  • Core variable: f ( c ) f(c)f ( c ) is the characterccThe number of occurrences of c , the beauty value is f (c) f(c)of the characters in the subsequenceThe sum of f ( c ) ;
  • Boundary case: Since the subsequence needs to select non-repeating letters, there is a boundary case. When kkk > The number of character types in the string: then kkmust not be constructedk subsequence, return0 00

Solution 1 (violent enumeration + multiplication principle)

The simplest way, we can enumerate all possible kkk subsequences and record the number of solutions with the largest beauty value. How to achieve this?

  • Method 1 - Considering that the subsequence needs to retain the order of the original string, the direct idea is to enumerate each subscript s [i] s[i] in the strings [ i ] is selected or not, but the time complexity isO ( 2 n ) O(2^n)O(2n )is obviously not established;
  • Method 2 - In fact, we do not need to enumerate from the perspective of the original string, but can enumerate from the perspective of the character set, so that the time complexity can be optimized using the multiplication principle. For example a, the number of occurrences is 2 22 , andbthe number of occurrences of is3 33 , thenthe number of subsequence schemes that can be constructedaand.b2 * 3 = 6

So, will the method time out? Let’s briefly analyze it:

Due to the size of the character set UUU is only at most26 2626 , then the number of subsequence solutions is at mostC 26 k C_{26}^kC26k, and because kkkDayuUU __U 's plan does not exist, so the maximum number of legal plans isCUU 2 = C 26 13 = 10400600 C_{U}^{\frac{U}{2}} = C_{26}^{13} = 10400600CU2U=C2613=10400600 is approximately equal to1 0 7 10^7107 . As long as we ensure that the time complexity of solving each sub-problem isO ( 1 ) O(1)If O ( 1 ) , it can be passed.

Enumeration implementation:

class Solution {
    
    
    fun countKSubsequencesWithMaxBeauty(s: String, k: Int): Int {
    
    
        val MOD = 1000000007
        // 计数
        val cnts = HashMap<Char, Int>()
        for (e in s) {
    
    
            cnts[e] = cnts.getOrDefault(e, 0) + 1
        }
        val m = cnts.size
        if (m < k) return 0 // 特判
        // 枚举子序列
        val keys = cnts.toList()
        var maxCount = 0L
        var maxF = 0
        // 回溯
        fun count(index: Int, size: Int, curF: Int, curCount: Long) {
    
    
            // 终止条件
            if (size == k) {
    
                    
                if (curF > maxF) {
    
    
                    maxF = curF
                    maxCount = curCount // 更新最大美丽值方案数
                } else if (curF == maxF) {
    
    
                    maxCount = (maxCount + curCount) % MOD // 增加方案数
                }
                return
            }
            if (size + m - index < k) return // 剪枝(长度不够)
            for (i in index until m) {
    
    
                val (c, cnt) = keys[i]
                count(i + 1, size + 1, curF + cnt, curCount * cnt % MOD /* 乘法原理 */) 
            }
        }
        count(0, 0, 0, 1)
        return maxCount.toInt()
    }
}

Complexity analysis:

  • Time complexity: O ( C mk ) O(C_m^k)O(Cmk) wheremmm is the character type;
  • Space complexity: O ( m ) O(m)O ( m ) hash table space and recursive stack space.

Solution 2 (sorting + greedy + multiplication principle)

Consider k = 1 k = 1k=Boundary case of 1 :

Obviously we need to choose f ( c ) f(c)f ( c ) has the largest value1 11 letter if presentmmm lettersf ( c ) f(c)f ( c ) is equal to the maximum value, then there isC m 1 = m C_m^1 = mCm1=m options. This shows that we do not need to enumerate the subsequences of all letters:since the characters in the subsequence are not repeated, kkK subsequence must choosef (c) f(c)kkwith the largest f ( c ) valuek letters, we can put the letters according tof (c) f(c)f ( c ) is sorted in reverse order, giving priority tof (c) f(c)f ( c ) is a larger letter.

Specific implementation:

We arrange the letters according to f ( c ) f(c)f ( c ) Sort by bucket, if the number of letters in the bucketis KKK is less than or equal tokkk , then all the elements in the bucket need to be selected, otherwise it is necessary to calculate the elements in the bucket and selectkkNumber of k plans:

  • Select all elements in the bucket, the number of options is cnt K cnt^KcntK
  • Select some elements in the bucket, the number of options is CK k ⋅ cntk C_K^k · cnt^kCKkcntk

It involves power operations, which are essentially the idea of ​​doubling:

// 快速幂 x^n
private fun powM(a: Int, b: Int, mod: Int) : Long {
    
    
    var x = a.toLong()
    var n = b.toLong()
    var ret = 1L
    while (n > 0L) {
    
    
        if (n % 2 == 1L) ret = ret * x % mod
        x = x * x % mod
        n /= 2
    }
    return ret
}

Which involves the number of combinations :

  • Calculation formula:
// 组合数计算公式 O(k)
private fun comb(n: Int, k: Int, mod: Int) : Int {
    
    
    var ret = 1L
    for (i in 1 .. k) {
    
    
        ret = ret * (n - i + 1) / i % mod
    }
    return ret.toInt()
}
  • Recursive formula (Yang Hui triangle):
// 递归 O(n^2)
private fun comb(n: Int, k: Int, mod: Int) : Int {
    
    
    if (n == k) {
    
    
        return 1
    } else if (k == 1) {
    
    
        return n
    } else {
    
    
        return (comb(n - 1, k - 1, mod) + comb(n - 1, k, mod)) % mod
    }
}

// 迭代 O(n^2)
private fun comb(n: Int, k: Int, mod: Int) : Int {
    
    
    val c = Array(n + 1) {
    
     IntArray(n + 1) }
    for (i in 1 .. n) {
    
    
        c[i][0] = 1
        c[i][i] = 1
        for (j in 1 until i) {
    
    
            c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod
        }
    }
    return c[n][k]
}
  • Lucas theorem: Lucas theorem is used when the scale of the problem is large and the module is not too large.

// 组合数计算公式
private fun comb(n: Long, k: Long, mod: Int) : Int {
    
    
    var n = n
    var ret = 1L
    for (i in 1 .. k) {
    
    
        ret = ret * n-- / i % mod
    }
    return ret.toInt()
}

// 卢卡斯定理
fun Lucas(n: Long, k: Long, mod: Int) : Long {
    
    
    if (k == 0L) return 1L;
    return (comb(n % mod, k % mod, mod) * Lucas(n / mod, k / mod, mod)) % mod;
}

Complete code:

class Solution {
    
    
    fun countKSubsequencesWithMaxBeauty(s: String, k: Int): Int {
    
    
        val MOD = 1000000007
        // 计数
        val cnts = HashMap<Char, Int>()
        var maxCnt = 0
        for (e in s) {
    
    
            cnts[e] = cnts.getOrDefault(e, 0) + 1
            maxCnt = max(maxCnt, cnts[e]!!)
        }
        val m = cnts.size
        if (m < k) return 0 // 特判
        // 有序集合
        val map = TreeMap<Int, Int>() {
    
     c1, c2 -> 
            c2 - c1
        }
        // 二次频率
        for ((_, c) in cnts) {
    
    
            map[c] = map.getOrDefault(c, 0) + 1
        }
        val cntCnts = map.toList()
        // println(cntCnts.joinToString())
        // 构造方案
        var ret = 1L
        var leftK = k
        for ((cnt, K) in cntCnts) {
    
    
            if (K > leftK) {
    
    
                ret = ret * powM(cnt, leftK, MOD) * comb(K, leftK, MOD) % MOD
            } else {
    
    
                ret = ret * powM(cnt, K, MOD) % MOD
            }
            leftK -= K
            if (leftK <= 0) break
        }
        return ret.toInt()
    }

    // 组合数计算公式 C_n^k
    private fun comb(n: Int, k: Int, mod: Int) : Int {
    
    
        if (n == k) {
    
    
            return 1
        } else if (k == 1) {
    
    
            return n
        } else {
    
    
            return (comb(n - 1, k - 1, mod) + comb(n - 1, k, mod)) % mod
        }
    }

    // 快速幂 x^n
    private fun powM(x_: Int, n_: Int, mod: Int) : Long {
    
    
        var x = x_.toLong()
        var n = n_.toLong()
        var ret = 1L
        while (n > 0L) {
    
    
            if (n % 2 == 1L) ret = ret * x % mod
            x = x * x % mod
            n /= 2
        }
        return ret
    }
}

Combining numbers and exponentiation operations in Python can be easily done using library functions:

class Solution:
    def countKSubsequencesWithMaxBeauty(self, s: str, k: int) -> int:
        MOD = 10 ** 9 + 7
        ans = 1
        cnt = Counter(Counter(s).values())
        for c, num in sorted(cnt.items(), reverse=True): # 二次计数
            if num >= k:
                return ans * pow(c, k, MOD) * comb(num, k) % MOD
            ans *= pow(c, num, MOD)
            k -= num
        return 0

Complexity analysis:

  • Time complexity: O ( n + m ) O(n + m)O ( n+m ) The main time is spent in enumerating strings;
  • Space complexity: O ( m ) O(m)O ( m ) hash table space.

recommended reading

Review of past issues of LeetCode’s journey series:

⭐️ Always believe that good things are about to happen, welcome to join Xiao Peng’s Android communication community~

Guess you like

Origin blog.csdn.net/pengxurui/article/details/132666638