leetcode第148场周赛总结

leetcode第148场周赛总结

第一题:5147. 递减元素使数组呈锯齿状

给你一个整数数组 nums,每次操作会从中选择一个元素并将该元素的值减少 1。
如果符合下列情况之一,则数组 A 就是锯齿数组:
每个偶数索引对应的元素都大于相邻的元素,即 A[0] < A[1] > A[2] < A[3] > A[4] < …
或者,每个奇数索引对应的元素都大于相邻的元素,即 A[0] > A[1] < A[2] > A[3] < A[4] > …
返回将数组 nums 转换为锯齿数组所需的最小操作次数。

示例1:

输入:nums = [1,2,3]
输出:2
解释:我们可以把 2 递减到 0,或把 3 递减到 1。

示例2:

输入:nums = [9,6,1,6,2]
输出:4

提示:

1 <= nums.length <= 1000
1 <= nums[i] <= 1000

分析:
题中定义的锯齿状数组有两种形式,我们可分别求出将给定数组变换为这两种形式的锯齿数组需要的最小操作数,然后返回其中更小的那个作为最终答案。
第一种形式,即A[0] < A[1] > A[2] < A[3] > A[4] < …时,由题意可知每次操作可将某元素值减少1,也即我们只能将元素值减少,所以我们只能对本该为波谷的元素做操作,将它减小为目前满足要求的最大的数。在第一种形式中,下标为偶数的元素该为波谷,所以在遍历的时候如果发现当前的相邻的两元素不满足要求的时候将这两个元素中下标为偶数的元素减小为目前满足要求的最大的数。并且如果这个元素在上一次操作中已经被减小过,在下一次操作中又将它减小的话并不会影响之前的结果。所以易知这样就能求出变化为某种锯齿形式时所需的最小操作次数。(其实这题容易让人想的复杂…)
对于第二种形式同理。代码如下:

class Solution:
    def movesToMakeZigzag(self, nums: List[int]) -> int:
        tmp = copy.copy(nums)
        s = 0
        for i in range(len(nums) - 1):
            if i % 2 == 0:
                if nums[i] >= nums[i+1]:
                    s += nums[i] - nums[i+1] + 1
                    nums[i] = nums[i+1]-1
            else:
                if nums[i] <= nums[i+1]:
                    s += nums[i+1] - nums[i] + 1
                    nums[i+1] = nums[i] - 1
        ans = s
        s = 0
        nums = tmp
        for i in range(len(nums) - 1):
            if i % 2 == 1:
                if nums[i] >= nums[i+1]:
                    s += nums[i] - nums[i+ 1] + 1
                    nums[i] = nums[i+1]-1
            else:
                if nums[i] <= nums[i+1]:
                    s += nums[i+1] - nums[i] + 1
                    nums[i+1] = nums[i] - 1
        ans = min(ans, s)
        return ans
第二题:5148. 二叉树着色游戏

有两位极客玩家参与了一场「二叉树着色」的游戏。游戏中,给出二叉树的根节点 root,树上总共有 n 个节点,且 n 为奇数,其中每个节点上的值从 1 到 n 各不相同。
游戏从「一号」玩家开始(「一号」玩家为红色,「二号」玩家为蓝色),最开始时,
「一号」玩家从 [1, n] 中取一个值 x(1 <= x <= n);
「二号」玩家也从 [1, n] 中取一个值 y(1 <= y <= n)且 y != x。
「一号」玩家给值为 x 的节点染上红色,而「二号」玩家给值为 y 的节点染上蓝色。
之后两位玩家轮流进行操作,每一回合,玩家选择一个他之前涂好颜色的节点,将所选节点一个 未着色 的邻节点(即左右子节点、或父节点)进行染色。
如果当前玩家无法找到这样的节点来染色时,他的回合就会被跳过。
若两个玩家都没有可以染色的节点时,游戏结束。着色节点最多的那位玩家获得胜利 ✌️。
现在,假设你是「二号」玩家,根据所给出的输入,假如存在一个 y 值可以确保你赢得这场游戏,则返回 true;若无法获胜,就请返回 false。

示例1:在这里插入图片描述

输入:root = [1,2,3,4,5,6,7,8,9,10,11], n = 11, x = 3
输出:True
解释:第二个玩家可以选择值为 2 的节点。

提示:

二叉树的根节点为 root,树上由 n 个节点,节点上的值从 1 到 n 各不相同。
n 为奇数。
1 <= x <= n <= 100

分析:
小小吐槽一下这个示例,其实二号玩家直接旋转值为1的节点能使对手的着色点数更少。(也许是出题者不想太直接提示我们怎么做吧哈哈…)
其实仔细分析这题的游戏规则可以发现这个游戏是可以一步就决定最终结果的(当然前提是两个选手都充分理解游戏规则并且每次都选择最好的走法)。规则的关键在于除了第一次着色位置可以随意选择外,两位选手之后都只能选择他们已经着色过的节点的直接相连节点着色(即父节点或者左右子节点),当然他们不能为已经着色过的节点再次着色。所以关键在于第一次选择,第一次选择的节点能直接决定之后的胜负。因为如果一号选手选择了某节点之后,二号选手可以用封堵策略,即选择这个节点的父节点或者左右子节点进行着色,从而使一号选手之后只能在某一片区域进行着色,因为总的节点数为奇数,所以必然能分出胜负。具体来说,一号选手第一次选择的节点可将整颗树划分为三个区域:1、这个节点的左子树;2、这个节点的右子树;3、剩下的其他节点。所以二号选手可以选择这三个区域中节点数最多的那个区域,比如:对于1二号选手可以着色这棵左子树的根节点从而将这个区域占为己有;对于2则着色这个右子树的根节点;对于3则着色一号选手选择的这个节点的父节点。只要二号选手选择的这个区域的节点数大于其他两个区域的节点数之和+1(一号选手已经选择的那个节点),那么二号选手就可以获胜,否则,他将无论如何也不能获胜。代码如下:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def btreeGameWinningMove(self, root: TreeNode, n: int, x: int) -> bool:
        self.p1 = None
        def dfs(root):
            if root == None:
                return
            if root.val == x:
                self.p1 = root
                return
            dfs(root.left)
            dfs(root.right)
        def count(root):
            if root == None:
                return 0
            return 1 + count(root.left) + count(root.right)
        dfs(root)
        l = count(self.p1.left)
        r = count(self.p1.right)
        top = n - l - r - 1
        if top > l+r+1 or l > top+r+1 or r > top+l+1:
            return True
        return False
第三题:5149. 快照数组

实现支持下列接口的「快照数组」- SnapshotArray:
SnapshotArray(int length) - 初始化一个与指定长度相等的 类数组 的数据结构。初始时,每个元素都等于 0。
void set(index, val) - 会将指定索引 index 处的元素设置为 val。
int snap() - 获取该数组的快照,并返回快照的编号 snap_id(快照号是调用 snap() 的总次数减去 1)。
int get(index, snap_id) - 根据指定的 snap_id 选择快照,并返回该快照指定索引 index 的值。

示例:

输入:[“SnapshotArray”,“set”,“snap”,“set”,“get”]
[[3],[0,5],[],[0,6],[0,0]]
输出:[null,null,0,null,5]
解释:
SnapshotArray snapshotArr = new SnapshotArray(3); // 初始化一个长度为 3 的快照数组
snapshotArr.set(0,5); // 令 array[0] = 5
snapshotArr.snap(); // 获取快照,返回 snap_id = 0
snapshotArr.set(0,6);
snapshotArr.get(0,0); // 获取 snap_id = 0 的快照中 array[0] 的值,返回 5

提示:

1 <= length <= 50000
题目最多进行50000 次set,snap,和 get的调用 。
0 <= index < length
0 <= snap_id < 我们调用 snap() 的总次数
0 <= val <= 10^9

分析:
这是一个设计题。关键在于怎么处理快照的问题,看这数组的最大规模可为50000,且快照次数最大也为50000,所以我们不可能每次快照的时候都复制保存一次,这样的话时间和空间都会超限,所以我们得想办法处理快照的问题。
首先这个快照数组中的值是可以被更新的,并且每次快照的时候需保存这个快照数组的所有数以支持对某次快照的结果查询,所以我们可将快照数组设计为嵌套的list,这样如果某个元素的值有更新的话可以直接在list中追加。相应的对于快照的处理方法,也可以用一个嵌套的list来记录对应的元素是第几次快照时更新的。具体实现细节见代码。
代码如下:

class SnapshotArray:

    def __init__(self, length: int):
        self.arr = [[0] for _ in range(length)]
        self.time = [[0] for _ in range(length)]
        self.count = 0

    def set(self, index: int, val: int) -> None:
        if self.count == self.time[index][-1]:
            self.arr[index][-1] = val
            return
        self.arr[index].append(val)
        self.time[index].append(self.count)

    def snap(self) -> int:
        self.count += 1
        return self.count-1

    def get(self, index: int, snap_id: int) -> int:
        pick = bisect.bisect_left(self.time[index], snap_id)
        if pick == len(self.arr[index]):
            return self.arr[index][-1]
        if self.time[index][pick] > snap_id:
            pick -= 1
        return self.arr[index][pick]
第四题:5150. 段式回文

段式回文 其实与 一般回文 类似,只不过是最小的单位是 一段字符 而不是 单个字母。
举个例子,对于一般回文 “abcba” 是回文,而 “volvo” 不是,但如果我们把 “volvo” 分为 “vo”、“l”、“vo” 三段,则可以认为 “(vo)(l)(vo)” 是段式回文(分为 3 段)。
给你一个字符串 text,在确保它满足段式回文的前提下,请你返回 段 的 最大数量 k。
如果段的最大数量为 k,那么存在满足以下条件的 a_1, a_2, …, a_k:
每个 a_i 都是一个非空字符串;
将这些字符串首位相连的结果 a_1 + a_2 + … + a_k 和原始字符串 text 相同;
对于所有1 <= i <= k,都有 a_i = a_{k+1 - i}。

示例1:

输入:text = “ghiabcdefhelloadamhelloabcdefghi”
输出:7
解释:我们可以把字符串拆分成 “(ghi)(abcdef)(hello)(adam)(hello)(abcdef)(ghi)”。

示例2:

输入:text = “merchant”
输出:1
解释:我们可以把字符串拆分成 “(merchant)”。

示例3:

输入:text = “antaprezatepzapreanta”
输出:11
解释:我们可以把字符串拆分成 “(a)(nt)(a)(pre)(za)(tpe)(za)(pre)(a)(nt)(a)”。

示例4:

输入:text = “aaa”
输出:3
解释:我们可以把字符串拆分成 “(a)(a)(a)”。

提示:

text 仅由小写英文字符组成。
1 <= text.length <= 1000

分析:
其实这个困难题并不困难,看来我们有时候不应该畏难被困难这个标签吓倒啊。
就是一个dfs回溯的问题,用了贪心的思想,因为是要我们求段式回文的最大的段的数量。所以用两个指针指向字符串的首尾,分别从一个字符开始匹配,若能匹配上则将结果加上2,然后继续向下dfs,若不能匹配上,则扩大范围,每次首位各增加一个字符进行匹配。重复如上操作,即可求出最终结果。
代码如下:

class Solution:
    def longestDecomposition(self, text: str) -> int:
        mem = {}
        def dfs(l, r):
            if l > r:
                return 0
            if l == r:
                return 1
            if (l, r) in mem:
                return mem[(l,r)]
            i = 1
            res = 1
            while l+i<=r-i+1:
                if text[l:l+i] == text[r-i+1:r+1]:
                    res = max(res, 2+dfs(l+i, r-i))
                    break
                i += 1
            mem[(l, r)] = res
            return mem[(l, r)]
        return dfs(0, len(text) -1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值