LeetCode 双周赛 101,DP/中心位贪心/裴蜀定理/Dijkstra/最小环

Posted pengxurui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 双周赛 101,DP/中心位贪心/裴蜀定理/Dijkstra/最小环相关的知识,希望对你有一定的参考价值。

本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。

大家好,我是小彭。

这周比较忙,上周末的双周赛题解现在才更新,虽迟但到哈。上周末这场是 LeetCode 第 101 场双周赛,整体有点难度,第 3 题似乎比第 4 题还难一些。


周赛大纲

2605. 从两个数字数组里生成最小数字(Easy)

  • 题解一:散列表 $O(n + m)$ 空间
  • 题解二:位运算 $O(1)$ 空间

2606. 找到最大开销的子字符串(Medium)

  • 动态规划 O(n)

2607. 使子数组元素和相等(Medium)

  • 题解 1:拼接数组 + 中位数贪心 · 错误
  • 题解 2:数组分组 + 中位数贪心 $O(nlgn)$
  • 题解 3:裴蜀定理 + 中位数贪心 $O(nlgn)$
  • 题解 4:裴蜀定理 + 中位数贪心 + 快速选择 $O(n)$

2608. 图中的最短环(Hard)

  • 题解 1:枚举边 + Dijkstra 最短路 + 最小堆 $O(m + m^2·lgn)$
  • 题解 2:枚举边 + BFS $O(m + m^2)$

2605. 从两个数字数组里生成最小数字(Easy)

题目地址

https://leetcode.cn/problems/form-smallest-number-from-two-digit-arrays/description/

题目描述

给你两个只包含 1 到 9 之间数字的数组 nums1 和 nums2 ,每个数组中的元素 互不相同 ,请你返回 最小 的数字,两个数组都 至少 包含这个数字的某个数位。

题解一(散列表)

简单模拟题,需要对 API 比较熟悉才能写出精炼的代码。

思路:优先选择两个数组交集的最小值,否则取两个数组的最小值再拼接。

class Solution 
    fun minNumber(nums1: IntArray, nums2: IntArray): Int 
        val set1 = nums1.toHashSet()
        val set2 = nums2.toHashSet()
        // 优先选择交集
        val set = set1.intersect(set2)
        if (!set.isEmpty()) return Collections.min(set)
        // 选择最小值
        val min1 = Collections.min(set1)
        val min2 = Collections.min(set2)
        // 拼接
        return Math.min(10 * min1 + min2, 10 * min2 + min1)
    

复杂度分析:

  • 时间复杂度:$O(n + m)$ 其中 $n$ 是 $nums1$ 数组的长度,$m$ 是 $nums2$ 数组的长度;
  • 空间复杂度:$O(n + m)$ 散列表空间

题解二(位运算)

使用二进制位标记代替散列表

class Solution 
    fun minNumber(nums1: IntArray, nums2: IntArray): Int 
        var flag1 = 0
        var flag2 = 0
        for (num in nums1) 
            flag1 = flag1 or (1 shl num)
        
        for (num in nums2) 
            flag2 = flag2 or (1 shl num)
        
        // numberOfTrailingZeros:最低位连续 0 的个数
        // 交集
        val flag = flag1 and flag2
        if (flag > 0) return Integer.numberOfTrailingZeros(flag)
        // 最小值
        val min1 = Integer.numberOfTrailingZeros(flag1)
        val min2 = Integer.numberOfTrailingZeros(flag2)
        // 拼接
        return Math.min(10 * min1 + min2, 10 * min2 + min1)
    

复杂度分析:

  • 时间复杂度:$O(n + m)$ 其中 $n$ 是 $nums1$ 数组的长度,$m$ 是 $nums2$ 数组的长度;
  • 空间复杂度:$O(1)$ 散列表空间

2606. 找到最大开销的子字符串(Medium)

题目地址

https://leetcode.cn/problems/find-the-substring-with-maximum-cost/

题目描述

给你一个字符串 s ,一个字符 互不相同 的字符串 chars 和一个长度与 chars 相同的整数数组 vals 。

子字符串的开销 是一个子字符串中所有字符对应价值之和。空字符串的开销是 0 。

字符的价值 定义如下:

  • 如果字符不在字符串 chars 中,那么它的价值是它在字母表中的位置(下标从 1 开始)。
    • 比方说,\'a\' 的价值为 1 ,\'b\' 的价值为 2 ,以此类推,\'z\' 的价值为 26 。
  • 否则,如果这个字符在 chars 中的位置为 i ,那么它的价值就是 vals[i] 。

请你返回字符串 s 的所有子字符串中的最大开销。

题解(动态规划)

简单动态规划问题。

先根据题意维护 a-z 每个字母的开销,再求 53. 最长子数组和 问题。

定义 dp[i] 表示以 [i] 为结尾的最大子数组和,则有

  • 与 $a[0, i - 1]$ 拼接:$dp[i] = dp[i - 1] + vals[i]$
  • 不与 $a[i - 1]$ 拼接(单独作为子数组):$dp[i] = vals[i]$
class Solution 
    fun maximumCostSubstring(s: String, chars: String, vals: IntArray): Int 
        // 初值
        val fullVals = IntArray(26)  it + 1 
        // 更新
        for ((i, c) in chars.withIndex()) 
            fullVals[c - \'a\'] = vals[i]
        
        // 动态规划
        val n = s.length
        var max = 0
        val dp = IntArray(n + 1)
        for (i in 1..n) 
            val curValue = fullVals[s[i - 1] - \'a\']
            dp[i] = Math.max(curValue, dp[i - 1] + curValue)
            max = Math.max(max, dp[i])
        
        return max
    

滚动数组优化:

class Solution 
    fun maximumCostSubstring(s: String, chars: String, vals: IntArray): Int 
        // 初值
        val fullVals = IntArray(26)  it + 1 
        // 更新
        for ((i, c) in chars.withIndex()) 
            fullVals[c - \'a\'] = vals[i]
        
        // 动态规划
        val n = s.length
        var max = 0
        var pre = 0
        for (i in 1..n) 
            val curValue = fullVals[s[i - 1] - \'a\']
            pre = Math.max(curValue, pre + curValue)
            max = Math.max(max, pre)
        
        return max
    

另一种理解,视为 vals[i] 总与前序子数组拼接,而前序子数组的权值不低于 0:

  • $dp[i] = Math.max(dp[i - 1], 0) + vals[i]$
class Solution 
    fun maximumCostSubstring(s: String, chars: String, vals: IntArray): Int 
        // 初值
        val fullVals = IntArray(26)  it + 1
        // 更新
        for ((i, c) in chars.withIndex()) 
            fullVals[c - \'a\'] = vals[i]
        
        // 动态规划
        val n = s.length
        var max = 0
        var pre = 0
        for (i in 1..n) 
            pre = Math.max(pre, 0) + fullVals[s[i - 1] - \'a\']
            max = Math.max(max, pre)
        
        return max
    


2607. 使子数组元素和相等(Medium)

题目地址

https://leetcode.cn/problems/make-k-subarray-sums-equal/

题目描述

给你一个下标从 0 开始的整数数组 arr 和一个整数 k 。数组 arr 是一个循环数组。换句话说,数组中的最后一个元素的下一个元素是数组中的第一个元素,数组中第一个元素的前一个元素是数组中的最后一个元素。

你可以执行下述运算任意次:

  • 选中 arr 中任意一个元素,并使其值加上 1 或减去 1 。

执行运算使每个长度为 k 的 子数组 的元素总和都相等,返回所需要的最少运算次数。

子数组 是数组的一个连续部分。

问题分析

分析 1: 先不考虑循环数组的前提,分析数据约束 “对于满足每个长度为 k 的子数组的和相等”,那么

$a[i]+a[i+1] +…+a[i+k-1] == a[i+1]+a[i+2]+…+a[i+k-1]+a[i+k]$

等式两边化简得:

$a[i]=a[i+k]$

也就是说,数组上每间隔 k 的元素要相等。因此我们需要将每间隔 k 的元素分为一组,再将组内元素调整为相等值;

分析 2: 如何将组内元素调整为相等值呢?可以证明选择中位数的贪心做法是最优的。

分析 3: 考虑循环数组的前提,对于 i + k ≥ len(arr) 的情况,需要对数组下标取模来模拟循环

题解一(拼接数组 + 中位数贪心 · 错误)

循环数组有拼接一倍数组的模拟做法,我们模拟出 2*n 长度的数组,在访问每个位置时,将所有同组的数组分为一组,再排序取中位数。

不过,这个思路在这道题里是不对的,因为同一个分组有可能循环多轮才会遇到。即使不考虑错误,在这道题的数据范围上也会内存溢出。

错误测试用例:$arr = [1, 5, 8, 10], k = 3$

class Solution 
    fun makeSubKSumEqual(arr: IntArray, k: Int): Long 
        val n = arr.size
        var ret = 0L
        // 延长一倍数组
        val visited = BooleanArray(2 * n)
        for (i in 0 until 2 * n) 
            if (visited[i]) continue
            // 分组
            val bucket = ArrayList<Int>()
            for (j in i until 2 * n step k) 
                bucket.add(arr[j % n])
                visited[j] = true
            
            // 排序
            Collections.sort(bucket)
            // println(bucket.joinToString())
            // 中位数贪心
            val midVal = bucket[bucket.size / 2]
            for (element in bucket) 
                ret += Math.abs(element - midVal)
            
        
        return ret / 2 // 扩充了一倍数组,所以操作数也翻倍了
    

题解二(数组分组 + 中位数贪心)

既然不能使用数组,那么可以在内存循环中一直循环取同分组为止,直到出现回环后退出:

class Solution 
    fun makeSubKSumEqual(arr: IntArray, k: Int): Long 
        val n = arr.size
        var ret = 0L
        val visited = BooleanArray(n)
        for (i in 0 until n) 
            if (visited[i]) continue
            // 分组
            val bucket = ArrayList<Int>()
            var j = i
            while (!visited[j]) 
                bucket.add(arr[j % n])
                visited[j] = true
                j = (j + k) % n
            
            // 排序
            Collections.sort(bucket)
            // 中位数贪心
            val midVal = bucket[bucket.size / 2]
            for (element in bucket) 
                ret += Math.abs(element - midVal)
            
        
        return ret
    

复杂度分析:

  • 时间复杂度:$O(nlgn)$ 其中 $n$ 为 $arr$ 数组长度,每个元素最多访问一次,且排序一次,所以整体时间是 $O(nlgn)$;
  • 空间复杂度:$O(n + lgn)$ 标记数组空间 + 排序递归栈空间。

题解三(裴蜀定理 + 中位数贪心)

根据前文分析,我们需要保证最终数组是以 $k$ 为循环周期的,而循环数组本身又是以 $n$ 为循环周期的。根据 裴蜀定理 ,如果一个数组存在周期 $k$ 和周期 $n$,那么必然存在周期 $gcb(k, n)$,而 $gcb(k, n)$ 必然小于 $n$,我们就将问题变成非循环数组问题。

  • 裴蜀定理:设 a,b 是不全为零的整数,则存在整数 x , y,使得 ax + by = gcb(a,b)
class Solution 
    fun makeSubKSumEqual(arr: IntArray, k: Int): Long 
        val n = arr.size
        // 最大公约数
        val m = gcb(n, k)
        var ret = 0L
        // 最多只有 m 组
        for (i in 0 until m) 
            // 分组
            val bucket = ArrayList<Int>()
            for (j in i until n step m) 
                bucket.add(arr[j])
            
            // 排序
            Collections.sort(bucket)
            val midVal = bucket[bucket.size / 2]
            for (element in bucket) 
                ret += Math.abs(element - midVal)
            
        

        return ret
    

    private fun gcb(a: Int, b: Int): Int 
        if (b == 0) return a
        return gcb(b, a % b)
    

复杂度分析:

  • 时间复杂度:$O(nlgn)$ 其中 $n$ 为 $arr$ 数组长度,每个元素最多访问一次,且排序一次,所以整体时间是 $O(nlgn)$;
  • 空间复杂度:$O(n + lgn)$ 分组空间 + 排序递归栈空间,分组空间最大为 $n$;

题解四(裴蜀定理 + 中位数贪心 + 快速选择)

排序是为了寻找中位数,没必要对整个分组排序,可以优化为快速选择,时间复杂度优化到 $O(n)$,Nice!

class Solution 
    fun makeSubKSumEqual(arr: IntArray, k: Int): Long 
        val n = arr.size
        // 最大公约数
        val m = gcb(n, k)
        var ret = 0L
        // 最多只有 m 组
        for (i in 0 until m) 
            // 分组
            val bucket = ArrayList<Int>()
            for (j in i until n step m) 
                bucket.add(arr[j])
            
            // 快速选择
            quickSelect(bucket)
            val midVal = bucket[bucket.size / 2]
            for (element in bucket) 
                ret += Math.abs(element - midVal)
            
        
        return ret
    

    // 快速选择中位数
    private fun quickSelect(bucket: ArrayList<Int>) 
        val mid = bucket.size / 2
        var left = 0
        var right = bucket.size - 1
        while (true) 
            val pivot = partition(bucket, left, right)
            if (mid == pivot) 
                break
             else if (pivot < mid) 
                left = pivot + 1
             else 
                right = pivot - 1
            
        
    

    // return:分区
    private fun partition(bucket: ArrayList<Int>, left: Int, right: Int): Int 
        var p = left
        for (i in left until right) 
            if (bucket[i] < bucket[right]) 
                bucket.swap(p++, i)
            
        
        bucket.swap(p, right)
        return p
    

    private fun <T> ArrayList<T>.swap(first: Int, second: Int) 
        val temp = this[first]
        this[first] = this[second]
        this[second] = temp
    

    // 迭代写法
    private fun gcb(a: Int, b: Int): Int 
        var x = a
        var y = b
        while (y != 0) 
            val temp = x % y
            x = y
            y = temp
        
        return x
    

复杂度分析:

  • 时间复杂度:$O(n)$ 其中 $n$ 为 $arr$ 数组长度,每个元素最多访问一次;
  • 空间复杂度:$O(n)$ 分组空间,分组空间最大为 $n$;

相似题目:


2608. 图中的最短环(Hard)

题目地址

https://leetcode.cn/problems/shortest-cycle-in-a-graph/

题目描述

现有一个含 n 个顶点的 双向 图,每个顶点按从 0 到 n - 1 标记。图中的边由二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和 vi 之间存在一条边。每对顶点最多通过一条边连接,并且不存在与自身相连的顶点。

返回图中 最短 环的长度。如果不存在环,则返回 -1 。

 是指以同一节点开始和结束,并且路径中的每条边仅使用一次。

题解一(枚举边 + Dijkstra 最短路 + 最小堆)

这道题是 最小环 模板题:给出一个图,问图中边权和最小的环是多大,图的最小环也称围长。

暴力解法:对于每条边 $(u, v)$,求不经过 $(u,v)$ 边从 $u$ 到 $v$ 的最短路 $len$,那么包含 $(u,v)$ 的最短环就是 $len + 1$。枚举所有边,则所有答案的最小值就是图的最小环。

class Solution 

    private val INF = Integer.MAX_VALUE

    fun findShortestCycle(n: Int, edges: Array<IntArray>): Int 
        // 建图
        val graph = Array(n)  ArrayList<Int>() .apply 
            for (edge in edges) 
                this[edge[0]].add(edge[1])
                this[edge[1]].add(edge[0])
            
        
        // 枚举边
        var ret = INF
        for (edge in edges) 
            ret = Math.min(ret, dijkstra(graph, edge[0], edge[1]))
        
        return if (INF == ret) -1 else ret
    

    private fun dijkstra(graph: Array<ArrayList<Int>>, u: Int, v: Int): Int 
        // 最短路长度
        val dis = IntArray(graph.size)  INF .apply 
            this[u] = 0
        
        // 最小堆
        val heap = PriorityQueue<Int>()  e1, e2 ->
            dis[e1] - dis[e2]
        .apply 
            this.offer(u)
        
        // BFS
        outer@ while (!heap.isEmpty()) 
            // 使用 O(lgn) 找出已选集中最短路长度最小的节点
            val x = heap.poll()
            // 松弛相邻点
            for (y in graph[x]) 
                // 忽略 (u, v) 边
                if (x == u && y == v) continue
                if (dis[x] + 1 /* 边权为 1 */ < dis[y]) 
                    dis[y] = dis[x] + 1
                    heap.offer(y)
                
                // 找到 u -> v 的最短路
                if (y == v) break@outer
            
        
        return if(INF == dis[v]) INF else dis[v] + 1
    

复杂度分析:

  • 时间复杂度:$O(m + m^2·lgn)$ 其中 $n$ 为顶点个数,$m$ 为边个数,每条边跑 Dijkstra 最短路每轮迭代以 $O(lgn)$ 取出已选集中最短路长度最小的节点,每次 Dijkstra 的时间是 $O(m·lgn)$;
  • 空间复杂度:$O(m + n)$ 图空间 + 最小堆空间,使用邻接表可以降低空间到 $O(m + n)$。

题解二(枚举边 + BFS)

由于这道题的边权是 1,所以不需要使用高级的图论算法也能做。

为什么呢,因为每个边权的长度是 1,所以已经访问过的节点是不会存在更短路径的。所以我们不需要使用堆,直接使用队列,最先进入队列中的节点一定是最短路长度最短的节点。

class Solution 

    private val INF = Integer.MAX_VALUE

    fun findShortestCycle(n: Int, edges: Array<IntArray>): Int 
        // 建图
        val graph = Array(n)  ArrayList<Int>() .apply 
            for (edge in edges) 
                this[edge[0]].add(edge[1])
                this[edge[1]].add(edge[0])
            
        
        // 枚举边
        var ret = INF
        for (edge in edges) 
            ret = Math.min(ret, bfs(graph, edge[0], edge[1]))
        
        return if (INF == ret) -1 else ret
    

    private fun bfs(graph: Array<ArrayList<Int>>, u: Int, v: Int): Int 
        // 最短路长度
        val dis = IntArray(graph.size)  INF .apply 
            this[u] = 0
        
        // 最小堆
        val queue = LinkedList<Int>().apply 
            this.offer(u)
        
        // BFS
        outer@ while (!queue.isEmpty()) 
            // 取队头
            val x = queue.poll()
            // 松弛相邻点
            for (y in graph[x]) 
                // 忽略 (u, v) 边
                if (x == u && y == v) continue
                // 已经访问过的节点不会存在更短路
                if (INF != dis[y]) continue
                dis[y] = dis[x] + 1
                queue.offer(y)
                // 找到 u -> v 的最短路
                if (y == v) break@outer
            
        
        return if (INF == dis[v]) INF else dis[v] + 1
    

复杂度分析:

  • 时间复杂度:$O(m + m^2)$ 在每轮 BFS 中,每条边最多访问 2 次,因此每轮 BFS 的时间复杂度是 $O(m)$;
  • 空间复杂度:$O(m + n)$。

LeetCode 第63场双周赛复盘

LeetCode 第63场双周赛复盘

背景

2022秋招以0 offer基本结束,没有收到很满意的offer,基本上大厂全挂。对自己这样一个结果并不满意,为了能在春招上收获到满意的offer,于是决定继续参加周赛,提高自己的算法能力。

结果

前面二道过了,第二题wa了9次。全球排名4603 / 9524,全国排名1499/2828,妥妥的中位数。下次的目标是进前50%

复盘

第一题[简单贪心]:

传送门

题目描述:

一个房间里有 n 个座位和 n 名学生,房间用一个数轴表示。给你一个长度为 n 的数组 seats ,其中 seats[i] 是第 i 个座位的位置。同时给你一个长度为 n 的数组 students ,其中 students[j] 是第 j 位学生的位置。

你可以执行以下操作任意次:

  • 增加或者减少第 i 位学生的位置,每次变化量为 1 (也就是将第 i 位学生从位置 x 移动到 x + 1 或者 x - 1)

请你返回使所有学生都有座位坐的 最少移动次数 ,并确保没有两位学生的座位相同。

请注意,初始时有可能有多个座位或者多位学生在 同一 位置。

示例1 :

输入:seats = [3,1,5], students = [2,7,4]
输出:4
解释:学生移动方式如下:

  • 第一位学生从位置 2 移动到位置 1 ,移动 1 次。
  • 第二位学生从位置 7 移动到位置 5 ,移动 2 次。
  • 第三位学生从位置 4 移动到位置 3 ,移动 1 次。
    总共 1 + 2 + 1 = 4 次移动。

题目解答:

翻译翻译就是,给这些懒得多走路的学生找位置坐,使得他们走的路是最少的。

当时竞赛的解答:

我的想法非常朴素,是给每个懒学生找离他们最近的位子坐。

主要是靠经验,因为周赛一般来说第一题都很简单,通常就是贪心题。比较常见的贪心做法就是先排序。

class Solution {
    public int minMovesToSeat(int[] seats, int[] students) {
        Arrays.sort(seats);
        Arrays.sort(students);
        int n = seats.length;
        int res = 0;
        for(int i=0;i<n;i++){
            res+=Math.abs(seats[i]-students[i]);
        }
        return res;
    }
}

贪心题的证明[想要提高必须要吃的苦]

证明上述的贪心做法是正确的,别看写的多,其实就是去绝对值

  • n=1时,只能有去做那个位置

  • n=2时,设两个座位位置分别是a,b,满足a<b;设两个学生c,d,满足c<d;

    • 方案一,按照大小关系一一对应,即cost1=|a-c|+|b-d|
    • 方案二,不按照方案一,那么选择的代价cost2=|a-d|+|b-c|;

    要证明方案一的代价要比方案二的代价小,即cost1<cost2,也就是cost1-cost2<0

    要求绝对值,就得判断大小关系,题设有a<b;c<d;

    情况一:a<b<c<d;
    ∣ a − c ∣ + ∣ b − d ∣ − ( ∣ a − d ∣ + ∣ b − c ∣ ) = c − a + d − b − ( d − a + c − b ) |a-c|+|b-d|-(|a-d|+|b-c|)=c-a+d-b-(d-a+c-b) ac+bd(ad+bc)=ca+db(da+cb)

    = c − a + d − b − d + a − b − c = 0 =c-a+d-b-d+a-b-c=0 =ca+dbd+abc=0

    情况二:c<d<a<b;两种方案一样

∣ a − c ∣ + ∣ b − d ∣ − ( ∣ a − d ∣ + ∣ b − c ∣ ) = 0 |a-c|+|b-d|-(|a-d|+|b-c|)=0 ac+bd(ad+bc)=0

​ 情况三:a<c<b<d;
∣ a − c ∣ + ∣ b − d ∣ − ( ∣ a − d ∣ + ∣ b − c ∣ ) = 2 ( c − b ) < 0 |a-c|+|b-d|-(|a-d|+|b-c|)=2(c-b)<0 ac+bd(ad+bc)=2(cb)<0
​ 情况四:c<a<d<b;
∣ a − c ∣ + ∣ b − d ∣ − ( ∣ a − d ∣ + ∣ b − c ∣ ) = 2 ( c − d ) < 0 |a-c|+|b-d|-(|a-d|+|b-c|)=2(c-d)<0 ac+bd(ad+bc)=2(cd)<0

​ 情况五:a<c<d<b:
∣ a − c ∣ + ∣ b − d ∣ − ( ∣ a − d ∣ + ∣ b − c ∣ ) = c − a + b − d − ( d − a + b − c ) = 2 ( c − d ) < 0 |a-c|+|b-d|-(|a-d|+|b-c|)=c-a+b-d-(d-a+b-c)=2(c-d)<0 ac+bd(ad+bc)=ca+bd(da+bc)=2(cd)<0

  • n>2时,用数学归纳法证明

第二题Alice和Bob

传送门

题目描述

总共有 n 个颜色片段排成一列,每个颜色片段要么是 ‘A’ 要么是 ‘B’ 。给你一个长度为 n 的字符串 colors ,其中 colors[i] 表示第 i 个颜色片段的颜色。

Alice 和 Bob 在玩一个游戏,他们 轮流 从这个字符串中删除颜色。Alice 先手 。

如果一个颜色片段为 ‘A’ 且 相邻两个颜色 都是颜色 ‘A’ ,那么 Alice 可以删除该颜色片段。Alice 不可以 删除任何颜色 ‘B’ 片段。
如果一个颜色片段为 ‘B’ 且 相邻两个颜色 都是颜色 ‘B’ ,那么 Bob 可以删除该颜色片段。Bob 不可以 删除任何颜色 ‘A’ 片段。
Alice 和 Bob 不能 从字符串两端删除颜色片段。
如果其中一人无法继续操作,则该玩家 输 掉游戏且另一玩家 获胜 。
假设 Alice 和 Bob 都采用最优策略,如果 Alice 获胜,请返回 true,否则 Bob 获胜,返回 false。

示例 1:

输入:colors = “AAABABB”
输出:true
解释:
AAABABB -> AABABB
Alice 先操作。
她删除从左数第二个 ‘A’ ,这也是唯一一个相邻颜色片段都是 ‘A’ 的 ‘A’ 。

现在轮到 Bob 操作。
Bob 无法执行任何操作,因为没有相邻位置都是 ‘B’ 的颜色片段 ‘B’ 。
因此,Alice 获胜,返回 true 。

题目解答

就是找出两个人可以操作的次数,因为alice先开始,所以如果alice的操作次数大于bob的操作次数+1的话,那么alice就赢了

wa了9次,才想出的,可以看出写出的代码也很烂。看别人的代码也有用字符串写的

class Solution {
    public boolean winnerOfGame(String colors) {
        int n = colors.length();
        int prevA = -1;
        int cnt1 = 0;
        int cnt2 = 0;
        int lenA = 0;
        int prevB = -1;
        int lenB = 0;
        for(int i=0;i<n;i++){
            if(colors.charAt(i)=='A'){
                prevB = i;
                if(lenB>=3){
                    cnt2+=lenB-2;
                }
                lenB=0;
                lenA = Math.max(lenA,i-prevA);//统计当前连续的A
            }else{
                prevA = i;
                if(lenA>=3){
                    cnt1+=lenA-2;
                  //如果A的连续断了就将这一段可以操作的次数加上去
                }
                lenA = 0;
                lenB = Math.max(lenB,i-prevB);
            }
        }
       //统计末尾的情况
        if(lenA>=3){
            cnt1+=lenA-2;
        }
        if(lenB>=3){
            cnt2+=lenB-2;
        }
        if(cnt1>=cnt2+1){
            return true;
        }else{
            return false;
        }
    }
}

字符串分割写法

参考链接

    public static boolean winnerOfGame(String colors) {
        String A[] = colors.split("B");
      	//比如"AAABABB",划分出来的就是[AAA,A]
        String B[] = colors.split("A");
      	//划分出来的就是[ , , ,B,BB]
        int cnt1 = 0, cnt2 = 0;
        for (String str : A) {
            if (str.length() > 2) {
                cnt1 += (str.length() - 2);//只有大于三才能操作
            }
        }
        for (String str : B) {
            if (str.length() > 2) {
                cnt2 += (str.length() - 2);
            }
        }
        if (cnt1 == 0) return false;
        if (cnt1 <= cnt2) return false;
        else return true;
    }

第三题[无向图的最短路径]

传送门

题目描述:

给你一个有 n 个服务器的计算机网络,服务器编号为 0 到 n - 1 。同时给你一个二维整数数组 edges ,其中 edges[i] = [ui, vi] 表示服务器 ui 和 vi 之间有一条信息线路,在 一秒 内它们之间可以传输 任意 数目的信息。再给你一个长度为 n 且下标从 0 开始的整数数组 patience 。

题目保证所有服务器都是 相通 的,也就是说一个信息从任意服务器出发,都可以通过这些信息线路直接或间接地到达任何其他服务器。

编号为 0 的服务器是 主 服务器,其他服务器为 数据 服务器。每个数据服务器都要向主服务器发送信息,并等待回复。信息在服务器之间按 最优 线路传输,也就是说每个信息都会以 最少时间 到达主服务器。主服务器会处理 所有 新到达的信息并 立即 按照每条信息来时的路线 反方向 发送回复信息。

在 0 秒的开始,所有数据服务器都会发送各自需要处理的信息。从第 1 秒开始,每 一秒最 开始 时,每个数据服务器都会检查它是否收到了主服务器的回复信息(包括新发出信息的回复信息):

如果还没收到任何回复信息,那么该服务器会周期性 重发 信息。数据服务器 i 每 patience[i] 秒都会重发一条信息,也就是说,数据服务器 i 在上一次发送信息给主服务器后的 patience[i] 秒 后 会重发一条信息给主服务器。
否则,该数据服务器 不会重发 信息。
当没有任何信息在线路上传输或者到达某服务器时,该计算机网络变为 空闲 状态。

请返回计算机网络变为 空闲 状态的 最早秒数 。

题目解答:

图这一块还是老样子,老是做不出来

下面是参考借鉴的大佬思路

T i T_i Ti表示 i i i号服务器完成所有传输需要的最短时间, d i s t i dist_i disti表示 i i i号服务器与0号服务器(也叫主服务器)的距离, p a t i e n c e i patience_i patiencei表示 i i i号服务器对应的重发等待时间

从i号服务器发送到主服务器,再从主服务器传回给i号服务器
T i = 2 ∗ d i s t i + [ ( 2 ∗ d i s t i − 1 ) / p a t i e n c e i ] ∗ p a t i e n c e i T_i=2*dist_i+[(2*dist_i-1)/patience_i]*patience_i Ti=2disti+[(2disti以上是关于LeetCode 双周赛 101,DP/中心位贪心/裴蜀定理/Dijkstra/最小环的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 第 58 场力扣夜喵双周赛(动态规划马拉车算法,前后缀处理)/ 第 253 场力扣周赛(贪心,LIS)

[状态压缩dp]Leetcode5.02双周赛 每个人戴不同帽子的方案数

LeetCode 双周赛 104(2023/05/13)流水的动态规划,铁打的结构化思考

leetcode-12双周赛-1246-删除回文子数组

leetcode-第14周双周赛-1273-删除树节点

leetcode-26双周赛-5399-数位成本和为目标值的最大数字