【LeetCode 周赛】渐入佳境

2023-09-09 14:06:15 浏览数 (1)

T1. 最长交替子序列(Easy)

代码语言:javascript复制
https://leetcode.cn/problems/longest-alternating-subarray/

题解一(模拟)

这道题与上周周赛 T1 还是比较相似的。

使用两层循环,枚举从每个元素 nums[i] 为起点开始的最长交替子序列长度。

代码语言:javascript复制
class Solution {
    fun alternatingSubarray(nums: IntArray): Int {
        var ret = -1
        for (i in 0 until nums.size) {
            var target = 1
            for (j in i   1 until nums.size) {
                if (nums[j] - nums[j - 1] != target) break
                ret = Math.max(ret, j - i   1)
                target *= -1
            }
        }
        return ret
    }
}

复杂度分析:

  • 时间复杂度:
O(n^2)

其中 n 为 nums 数组的长度;

  • 空间复杂度:仅使用常量级别空间。

题解二(同向双指针)

这个解法基于 KMP 思想。

在题解一中,我们会重复计算同一段交替子序列的,我们可以使用一次遍历,再交替子序列终止时避免重复回退到该子序列内部。需要注意的是,由于不同的交替子序列可能存在 1 位重叠,所以要把 i 指针指向 j 指针,而不是指向 j 指针的下一位,才能保证没有缺失。例如 [3,4,3,4,5,4,5] 数组,第一组交替子数组为 [3,4,3,4] 和第二组交替子数组为 [4,5,4,5] 这两组有重叠部分。

代码语言:javascript复制
class Solution {
    fun alternatingSubarray(nums: IntArray): Int {
        val n = nums.size
        var ret = -1
        var i = 0
        while (i < n - 1) {
            // 寻找起点
            while (i < n - 1 && nums[i   1] - nums[i] != 1) {
                i  
            }
            var target = 1
            var j = i
            while (j < n - 1 && nums[j   1] - nums[j] == target)  {
                ret = Math.max(ret,   j - i   1)
                target *= -1
            }
            i = j
        }
        return ret
    }
}

复杂度分析:

  • 时间复杂度:
O(n)

线性遍历

  • 空间复杂度:
O(1)

仅使用常量级别空间。


T2. 重新放置石块(Medium)

代码语言:javascript复制
https://leetcode.cn/problems/relocate-marbles/

题解(模拟 散列表)

在每部操作中,我们会将位置 moveFrom[i] 上所有的石头移动到 moveTo[i] 上,「所有」的含义意味着石头的数量是无关紧要的,我们可以使用散列表维护剩余的石头,最后对剩余石头排序。

代码语言:javascript复制
class Solution {
    fun relocateMarbles(nums: IntArray, moveFrom: IntArray, moveTo: IntArray): List<Int> {
        if (moveFrom.size != moveTo.size) return Collections.emptyList()
        val set = nums.toHashSet()
        for (i in moveFrom.indices) {
            set.remove(moveFrom[i])
            set.add(moveTo[i])
        }
        return set.toMutableList().sorted()
    }
}

复杂度分析:

  • 时间复杂度:
O(nlgn)

瓶颈在排序上;

  • 空间复杂度:
O(n)

散列表空间。


T3. 将字符串分割为最少的美丽子字符串(Medium)

代码语言:javascript复制
https://leetcode.cn/problems/partition-string-into-minimum-beautiful-substrings/

题解一(记忆化递归)

比较直观的子集问题,我们枚举所有分割点(可以构造 5 的幂)的位置并记录最短结果。由于题目的数据范围比较小,我们可以预处理出数据范围内所有 5 的幂。

  • 定义 backTrack(i) 表示从 [i] 为起点的最少美丽字符串个数,枚举以 [i] 为起点的所有可行方案,从中得出最优解。
代码语言:javascript复制
class Solution {
    
    companion object {
        // 预处理
        private val U = 15
        private val INF = Integer.MAX_VALUE
        private val set = HashSet<Int>()
        init {
            var x = 1
            while (x.toString(2).length <= U) {
                set.add(x)
                x *= 5
            }
        }
    }
    
    fun minimumBeautifulSubstrings(s: String): Int {
        return backTrack(s, HashMap<Int,Int>(), 0)
    }
    
    private fun backTrack(s: String, memo: MutableMap<Int, Int>, i: Int): Int {
        // 终止条件
        if (i == s.length) return 0
        // 剪枝(不允许前导零)
        if (s[i] == '0') return -1
        // 读备忘录
        if (memo.contains(i)) return memo[i]!!
        // 枚举
        var x = 0
        var ret = INF
        for (j in i until s.length) {
            x = x.shl(1)   (s[j] - '0')
            if (set.contains(x)) {
                // 递归
                val childRet = backTrack(s, memo, j   1)
                if (-1 != childRet) ret = Math.min(ret, childRet)
            }
        }
        val finalRet = if (INF == ret) -1 else ret   1
        memo[i] = finalRet
        return finalRet
    }
}

复杂度分析:

  • 时间复杂度:
O(n^2)

一共 n 个分割点,每个分割点有「选和不选」两种方案,看起来总共有

2^n

种子状态,其实并没有。我们的 backTrack(i) 的定义是以 [i] 为起点可以构造的最少美丽字符串数,因此总共只有 n 种状态,而每种状态需要检查

O(n)

种子状态,因此整体时间复杂度是

O(n^2)

  • 空间复杂度:
O(n)

备忘录空间。

题解二(动态规划)

可以把记忆化递归翻译为动态规划的版本:

代码语言:javascript复制
class Solution {
    
    companion object {
        // 预处理
        private val U = 15
        private val INF = Integer.MAX_VALUE
        private val set = HashSet<Int>()
        init {
            var x = 1
            while (x.toString(2).length <= U) {
                set.add(x)
                x *= 5
            }
        }
    }
    
    fun minimumBeautifulSubstrings(s: String): Int {
        val INF = 0x3F3F3F3F // 便于判断
        val n = s.length
        val dp = IntArray(n   1) { INF }
        dp[n] = 0
        // 倒序遍历(先求小问题)
        for (i in n - 1 downTo 0) {
            // 不允许前导零
            if (s[i] == '0') continue
            // 枚举
            var x = 0
            for (j in i until n) {
                x = x.shl(1)   (s[j] - '0')
                if (set.contains(x)) dp[i] = Math.min(dp[i], dp[j   1]   1)
            }
        }
        return if (dp[0] != INF) dp[0] else -1
    }
}

复杂度分析:

  • 时间复杂度:
O(n^2)

同上;

  • 空间复杂度:
O(n)

DP 数组空间。


T4. 黑格子的数目(Medium)

代码语言:javascript复制
https://leetcode.cn/problems/number-of-black-blocks/

题解(枚举黑格 贡献度)

直接枚举所有块的时间复杂度是 O(nm) 会超时,我们发现真正影响结果的是黑格格子,但是暴力枚举块的方法会枚举到那些完全是白色的块。

因此,我们将枚举维度从所有块调整到黑色格子附近的块,对于每一个黑色格子 [x, y] 最多仅会对 4 个块产生影响(贡献)。所以我们的算法是:枚举所有黑色格子,并记录黑色格子可以产生贡献的块,最后统计出所有可以被影响到的块以及的贡献度,这可以用散列表来记录。

剩下一个问题是怎么表示一个唯一的块,我们可以规定块中 4 个点中的其中一个点作为块的代表元(以右下角的点为例),然后将该点的行和列压缩到一个 Long 变量中来唯一标识不同的块。

代码语言:javascript复制

class Solution {
    fun countBlackBlocks(m: Int, n: Int, coordinates: Array<IntArray>): LongArray {
        val U = 100000
        val map = HashMap<Long, Int>()
        // 以右下角为代表元的块
        val blocks = arrayOf(intArrayOf(0,0), intArrayOf(0, 1), intArrayOf(1,1), intArrayOf(1,0))
        for (e in coordinates) {
            // 枚举 4 个块
            for (block in blocks) {
                val x = e[0]   block[0]
                val y = e[1]   block[1]
                // 检查块有效性
                if (x >= 1 && x < m && y >= 1 && y < n) {
                    // 记录贡献度
                    val key = 1L * x * U   y
                    map[key] = map.getOrDefault(key, 0)   1
                }
            }
        }
        val ret = LongArray(5)
        for ((_, cnt) in map) {  
            ret[cnt]   
        }
        ret[0] = 1L * (n - 1) * (m - 1) - map.size
        return ret
    }
}

复杂度分析:

  • 时间复杂度:
O(m)

其中 m 为黑格格子数

  • 空间复杂度:
O(m)

其中 m 为黑格格子

0 人点赞