leetcode热题系列13

18 篇文章 0 订阅
13 篇文章 0 订阅

862. 和至少为 K 的最短子数组

要求是连续的子数组,所以自然就想到了双指针法,也就是寻找一个连续的区间。但是细思之后没有理清左右边界移动的逻辑,遂弃。
暴力解
反正先暴力解一下吧:
前缀和数组:构建数组sum[i]表示原数组前 i 个元素之和,比如sum[2] = A[0] + A[1] 。

例子3:
Input: A = [2,-1,2], K = 3
Output: 3
数组A对应的前缀和数组:
sum = [0, 2, 1, 3]

from collections import deque

def shortestSubarray(nums, k):
    # 在数组中,找到一个和>=k的子数组,并且求出最短的长度
    # 对于子数组求和的问题,可以用前缀和来简化

    # 前缀和
    n = len(nums)
    sum_ =  * (n + 1)
    for i in range(n):
        sum_[i + 1] = sum_[i] + nums[i]

    # 得出前缀和之后,可以写出暴力解,计算sum[j]-sum[i] >= k, 找到最小的j-i
    # 暴力解会超时
    # 考虑简化,用一个队列记录一下已经遍历的前缀和,(j > i,j是新遍历的前缀和)

    # 1.当 sum[j]-sum[i]>=k,对于所有以i为左端点的子数组,即使存在其他的x>j满足条件,x都不是最优解
    # 遍历找到第一组满足条件的(i,j),就是以i为左端点时的最优解,不用考虑其他以i为左端点的情况
    # 这个i可以排除掉,从队列里踢出去,同时要记录这个j-i 是否是全局最小的

    # 2.当 sum[j]<sum[i],假设后面出现x>j,满足了 sum[x]-sum[i] >= k,那么 sum[x]-sum[j]必定>=k
    # 对于可能存在的x,j是更好的左端点,这个i可以去掉,然后将这个j添加到队列中

    # 用双端队列,记录的是下标
    min_ = n + 1
    q = deque()
    for j in range(n + 1):
        while q and sum_[j] - sum_[q] >= k:
            min_ = min(min_, j - q.popleft())
        while q and sum_[j] < sum_[q[-1]]:
            q.pop()
        q.append(j)
    
    return min_ if min_ <= n else -1

680. 验证回文字符串 Ⅱ

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: “aba”
输出: True

示例 2:
输入: “abca”
输出: True
解释: 你可以删除c字符。

注意:

字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
解题思路
思路:双指针

题目中说明,允许【最多删除一个字符】。先抛开这个,说一下在不删除的情况下,如果去判断一个字符串是否是回文字符串?
同样的,常见的方法是双指针。定义双指针,一个指向开始,一个指向末尾。这时,判断指针所对应的字符是否相同,如果不同,则不是回文字符串;如果相同,则指针都往中间移动,再次判断,直到指针相遇,这时就可以确认这个字符是回文字符串。
现在题目中说明,可以允许删除一个字符,这里我们同样使用双指针的方法。双指针的初始化也同样,一个指向开始,一个指向末尾。这个时候同样是判断指针对应的字符是否相同,如果相同,跟前面的步骤一样,指针往中间移动继续判断,直至指针相遇。
不同的地方在于,如果当前双指针对应的字符不同时,可以考虑删除一个元素,在这里可以分为两种情况:

删除左指针对应的字符,移动左指针到后一位,再次判断这个时候左指针所对应字符到右指针所对应字符这个范围的字符串是否是回文字符串
删除右指针对应的字符,移动右指针到前一位,再次判断左指针所对应字符到这时的右指针所对应字符这个范围的字符串是否是回文字符串
根据前面的两种情况,假设定义双指针为 left, right。如果此时 left 和 right 指针对应的字符不同时,上面的两种情况就应该表示为:

移动左指针到后一位,即是判断:s[left+1]…s[right] 之间的字符串是否是回文字符串
移动有指针到前一位,即是判断:s[left]…s[right-1] 之间的字符串是否是回文字符串
具体的代码实现如下。

class Solution:
    def validPalindrome(self, s: str) -> bool:
        def check(left, right):
            # 判断是否是回文数
            while left < right:
                if s[left] != s[right]:
                    return False
                left += 1
                right -= 1
            return True
        
        # 定义指针,一个指向开始,一个指向末尾
        left, right = 0, len(s) - 1
        while left < right:
            # 指针所对应的字符相同时,指针往中间移动
            if s[left] == s[right]:
                left += 1
                right -= 1
            # 指针所对应的字符不同,考虑删除一个字符
            # 1. 删除当前左指针的字符,移动至后一位
            # 2. 删除当前右指针的字符,移动至前一位
            # 重新判断删除字符后,字符串是否是回文字符串
            else:
                return check(left + 1, right) or check(left, right - 1)
        return True

剑指 Offer 31. 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1}是该压栈序列对应的一个弹出序列,但{4,3,5,1,2}就不可能是该压栈序列的弹出序列。
示例 1:

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true

解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

示例 2:

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false

解释:1 不能在 2 之前弹出。
在这里插入图片描述
在这里插入图片描述

class Solution(object):
    def validateStackSequences(self, pushed, popped):
        """
        :type pushed: List[int]
        :type popped: List[int]
        :rtype: bool
        """
        stack = []
        i = 0

        for num in pushed:
            stack.append(num) # num 入栈
            while stack and stack[-1] == popped[i]: # 循环判断与出栈
                stack.pop()
                i += 1
                
        return not stack

面试题 10.03. 搜索旋转数组

386. 字典序排数

解题思路
DFS
1-9的数字可以构建九颗树,字典序相当于顺序的对每一颗树进行前序遍历。
每个节点下一层的元素就是当前数值后面加上[0, 9]的数值,eg:1的下一层就是 10、11、12…19
在这里插入图片描述

在这里插入图片描述

class Solution:
    def lexicalOrder(self, n: int):
        self.res = []
        def DFS(i):
            i = int(i)
            if i > n:
                return
            self.res.append(i)
            for j in range(10):
                # 构建下一层节点
                DFS(str(i)+str(j))
        # 构建1~9的树
        for i in range(1, 10):
            DFS(i)
        return self.res

from functools import cmp_to_key
class Solution:
    def lexicalOrder(self, n: int):
        # 每一位进行比较,10和10x这种情况,谁长谁大
        def check(a, b):
            a, b = str(a), str(b)
            la = lb = 0
            while la < len(a) and lb < len(b):
                if a[la] > b[lb]:
                    return 1
                if a[la] < b[lb]:
                    return -1
                la += 1
                lb += 1
            if lb != len(b):
                return -1
            return 1
        tmp = [i for i in range(1, n+1)]
        return sorted(tmp, key=cmp_to_key(check))

617. 合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
在这里插入图片描述
注意: 合并必须从两个树的根节点开始。

思路:
递归,深度优先遍历
终止条件就是当有一个节点为空时,直接返回另一个子树即可。整体过程是先合并根节点,然后连接根节点的左右子树,左子树来自递归调用合并函数,将两个根节点的整个左子树合并,内部递归实现,右子树来自递归调用合并函数,将两个根节点的整个右子树合并,同样内部递归实现。最后返回合并后的二叉树根节点。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def mergeTrees(self, root1, root2):
        """
        :type root1: TreeNode
        :type root2: TreeNode
        :rtype: TreeNode
        """
        # 递归,整体是怎么执行的,里边就怎么执行
        if not root1: return root2
        if not root2: return root1
        root = TreeNode(root1.val + root2.val)
        root.left = self.mergeTrees(root1.left,root2.left)
        root.right = self.mergeTrees(root1.right,root2.right)
        return root 

剑指 Offer 07. 重建二叉树

题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。(事实上,返回的是二叉树的根节点)

LeetCode
类比LeetCode题目有105. 从前序与中序遍历序列构造二叉树

思路
前序 [1,2,4,7,3,5,6,8]
后序 [4,7,2,15,3,8,6]

前序遍历的第一个是根节点1,扫描中序遍历结果,就可以找到根节点1的位置。在中序遍历中,根节点1左边的数字位于左子树,1右边的节点位于右子树。这样就可以划分出根节点和对应的孩子。
同样地,在左子树和右子树当中,前序遍历的第一个节点是根节点。可以用上面同样的方法去构建,这样也就使用递归的方法来构建二叉树。

    def buildTree(self, preorder, inorder):
        """
        :type preorder: List[int]
        :type inorder: List[int]
        :rtype: TreeNode
        """
        #递归终止条件 任何一种遍历为空
        if not preorder or not inorder:
            return None
        #前序第一个为root
        root = TreeNode(preorder[0])
        #找到中序遍历中root的位置
        index = inorder.index(preorder[0])
        # 递归调用 求左子树 右子树
        # 左子树为 前序中根节点后一个后面index个数 中序中从开始到根节点
        root.left = self.buildTree(preorder[1:index+1], inorder[:index])
        # 右子树为 前序中从index+1后面的 中序从根节点后一个开始到最后
        root.right = self.buildTree(preorder[index+1:], inorder[index+1:])
        return root

扩展2 前序后序建立二叉树

在这里插入图片描述
需要注意的是:前序和后序并不能够唯一确定一颗二叉树。

def constructFromPrePost(self, pre, post):
    if not pre: return None
    root = TreeNode(pre[0])
    if len(pre) == 1: return root

    L = post.index(pre[1]) + 1
    root.left = self.constructFromPrePost(pre[1:L+1], post[:L])
    root.right = self.constructFromPrePost(pre[L+1:], post[L:-1])
    return root

17. 电话号码的字母组合

706. 设计哈希映射

使用任何内建的哈希表库设计一个哈希映射

具体地说,你的设计应该包含以下的功能

put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
remove(key):如果映射中存在这个键,删除这个数值对。

示例:

MyHashMap hashMap = new MyHashMap();
hashMap.put(1, 1);
hashMap.put(2, 2);
hashMap.get(1); // 返回 1
hashMap.get(3); // 返回 -1 (未找到)
hashMap.put(2, 1); // 更新已有的值
hashMap.get(2); // 返回 1
hashMap.remove(2); // 删除键为2的数据
hashMap.get(2); // 返回 -1 (未找到)

注意:

所有的值都在 [1, 1000000]的范围内。
操作的总数目在[1, 10000]范围内。
不要使用内建的哈希库。
思路:

给定了值的范围,所以可以用桶排序的思想实现hashmap。

class MyHashMap(object):
 
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.hashmap = [-99999 for _ in range(1000005)]        
 
    def put(self, key, value):
        """
        value will always be non-negative.
        :type key: int
        :type value: int
        :rtype: None
        """
        self.hashmap[key] = value
 
    def get(self, key):
        """
        Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key
        :type key: int
        :rtype: int
        """
        if self.hashmap[key] != -99999:
            return self.hashmap[key]
        return -1
 
    def remove(self, key):
        """
        Removes the mapping of the specified value key if this map contains a mapping for the key
        :type key: int
        :rtype: None
        """
        self.hashmap[key] = -99999

1095. 山脉数组中查找目标值

给你一个 山脉数组 mountainArr,请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。

如果不存在这样的下标 index,就请返回 -1。
何为山脉数组?如果数组 A 是一个山脉数组的话,那它满足如下条件:
在这里插入图片描述
注意:
对 MountainArray.get 发起超过 100 次调用的提交将被视为错误答案。
在这里插入图片描述
题目分析:
  本题有点长,本文所给出的自定义的山峰数组数据类型,然后只留个两个接口,一个是长度,一个通过索引值返回对应值。然后山峰数组需要满足的条件就是,需要大于3,先升后降这样的,借用leetcode一张图,画出来就是下面这样。
在这里插入图片描述
调用get函数超过一百次就自动报错,其实间接说明了你的时间复杂度为o(logn),很容易想到二分,如果我们知道mountaintop山峰就好,对左边二分搜索一次,如果没有,对右边二分搜索一次,问题解决,所以接下来从怎么找峰值的索引开始吧。 常规二分搜索在这篇写过,想了解的请移步
leetcode153. 寻找旋转排序数组中的最小值

解题思路:
  峰值有什么特点呢,首先峰值的左边值和右边值都小于自己,这个特点和其他都不一样。依旧采用二分搜索,如果mid值小于mid+1呢,说明峰值在mid+1和r之间,如果大于呢,说明在[l,mid]之间,通过不断缩小方位,最终的l就对应峰值。
  解决峰值后,在使用常规的二分就可以快速找到target是否存在,如果存在对应位置。代码如下:

#class MountainArray:
#    def get(self, index: int) -> int:
#    def length(self) -> int:

class Solution:
	#先找峰值,然后二分搜索左边,如果有直接返回结果,如果返回-1,再搜右边,直接返回结果即可
    def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
        mid = self.findInMountainTop(mountain_arr)
        l_idx = self.left_index(mid, mountain_arr,target)
        if l_idx==-1:
            return self.right_index(mid, mountain_arr,target)
        else:
            return l_idx

    #二分搜索峰值的下标位置
    def findInMountainTop(self,mountain_arr):
        l = 0
        r = mountain_arr.length()-1
        while(l<r):
            mid = (l+r)//2
            if mountain_arr.get(mid)<mountain_arr.get(mid+1):
                l = mid+1
            else:
                r = mid
        return l
	 #二分搜索左边空间函数
    def left_index(self, mid, mountain_arr,target):
        l = 0
        r = mid
        while(l<r):
            mid = (l+r)//2
            if mountain_arr.get(mid)==target:
                return mid
            elif mountain_arr.get(mid)>target:
                r = mid
            else:
                l = mid+1
        return -1
    #二分搜索右边空间函数
    def right_index(self,mid,mountain_arr,target):
        l = mid
        r = mountain_arr.length()
        while(l<r):
            mid = (l+r)//2
            if mountain_arr.get(mid)==target:
                return mid
            elif mountain_arr.get(mid)<target:
                r = mid
            else:
                l = mid+1
        return -1

1312. 让字符串成为回文串的最少插入次数

547. 省份数量(原朋友圈)

题目:

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。
在这里插入图片描述

示例 1:
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

示例 2:

输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3
在这里插入图片描述

class Solution:
    def findCircleNum(self, M):
        visited = [False] * len(M)
        res = 0
        for i in range(len(M)):
            if not visited[i]:
                self.dfs(M, visited, i)
                res += 1
        return res

    def dfs(self, M, visited, i):
        for j in range(len(M)):
            if M[i][j] == 1 and not visited[j]:
                visited[j] = True
                self.dfs(M, visited, j)

107. 二叉树的层次遍历 II

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

例如:

给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回其自底向上的层次遍历为:

[
  [15,7],
  [9,20],
  [3]
]
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

from collections import deque

class Solution:
    def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []
        
        ans = []

        stack = []

        queue = deque()
        queue.append(root)

        while queue:
            cnt = len(queue)
            
            tmp = []

            for _ in range(cnt):
                node = queue.popleft()
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
                tmp.append(node.val)
            
            stack.append(tmp)
        
        while stack:
            ans.append(stack.pop())
        
        return ans

407. 接雨水 II

752. 打开转盘锁

你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。

锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。

列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。

示例 1:

输入:deadends = [“0201”,“0101”,“0102”,“1212”,“2002”], target = “0202”
输出:6
解释:
可能的移动序列为 “0000” -> “1000” -> “1100” -> “1200” -> “1201” -> “1202” -> “0202”。
注意 “0000” -> “0001” -> “0002” -> “0102” -> “0202” 这样的序列是不能解锁的,
因为当拨动到 “0102” 时这个锁就会被锁定。
示例 2:

输入: deadends = [“8888”], target = “0009”
输出:1
解释:
把最后一位反向旋转一次即可 “0000” -> “0009”。
示例 3:

输入: deadends = [“8887”,“8889”,“8878”,“8898”,“8788”,“8988”,“7888”,“9888”], target = “8888”
输出:-1
解释:
无法旋转到目标数字且不被锁定。
示例 4:

输入: deadends = [“0000”], target = “8888”
输出:-1

class Solution:
    def openLock(self, deadends: List[str], target: str) -> int:
        if target == "0000":
            return 0
        q1, q2, visited = set(), set(), set()
        q1.add("0000")
        q2.add(target)
        visited.update(deadends)
        step = 0
        while q1 and q2:
            temp = set()
            for cur in q1:
                if cur in visited:
                    continue
                if cur in q2:
                    return step
                visited.add(cur)
                for j in range(4):
                    up = cur[:j] + str((int(cur[j]) + 1) % 10) + cur[j+1:]
                    if up not in visited:
                        temp.add(up)
                    down = cur[:j] + str((int(cur[j]) - 1) % 10) + cur[j+1:]
                    if down not in visited:
                        temp.add(down)
            step += 1
            q1, q2 = q2, temp
        return -1

13. 罗马数字转整数

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。

示例 1:

输入: “III”
输出: 3

class Solution:
    def romanToInt(self, s):
        """
        :type s: str
        :rtype: int
        """
        valuelist = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000}        
        ans = 0        
        for i in range(0,len(s)-1):            
            if valuelist[s[i]] >= valuelist[s[i+1]]:
                ans += valuelist[s[i]]
            else :
                ans -= valuelist[s[i]]
        ans += valuelist[s[len(s)-1]]
        return ans

354. 俄罗斯套娃信封问题

题目描述
给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。

当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

注意:不允许旋转信封。

from typing import List

def max_envelopes(envelopes: List[List[int]]) -> int:
    n = len(envelopes)
    if n < 1:
        return 0
    dp = [1] * n
    res = 1
    envelopes.sort(key=lambda x: (x, -x[1]))  # Sorting envelopes based on width and then in reverse order by height

    for i in range(1, n):
        for j in range(i):
            if envelopes[i] > envelopes[j] and envelopes[i][1] > envelopes[j][1]:
                dp[i] = max(dp[i], dp[j] + 1)
        res = max(res, dp[i])
    return res
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值