目录
有效的数独
因为刷题的时候看答案通常官解给的看不懂,所以会去看别人的思路和解法。以下汇总了我所有刷的题能看懂捋顺的比较容易理解的代码答案,也方便我回顾以及二刷的时候回来看,有不明白的小伙伴欢迎私信或评论区交流
简化路径
names 中包含的字符串只能为以下几种:
空字符串。例如当出现多个连续的 /,就会分割出空字符串;
一个点 .;
两个点 ..;
只包含英文字母、数字或 _ 的目录名。
对于「空字符串」以及「一个点」,我们实际上无需对它们进行处理,因为「空字符串」没有任何含义,而「一个点」表示当前目录本身,我们无需切换目录。
对于「两个点」或者「目录名」,我们则可以用一个栈来维护路径中的每一个目录名。当我们遇到「两个点」时,需要将目录切换到上一级,因此只要栈不为空,我们就弹出栈顶的目录。当我们遇到「目录名」时,就把它放入栈。
这样一来,我们只需要遍历 names 中的每个字符串并进行上述操作即可。在所有的操作完成后,我们将从栈底到栈顶的字符串用 / 进行连接,再在最前面加上 / 表示根目录,就可以得到简化后的规范路径。
class Solution:
def simplifyPath(self, path: str) -> str:
names = path.split('/')
stack = []
for name in names:
if name == '..':
if stack:
stack.pop()
elif name and name != '.':
stack.append(name)
return '/' + '/'.join(stack)
基本运算器
题目描述:给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。
提示:
1 <= s.length <= 3 * 105
s 由数字、'+'、'-'、'('、')'、和 ' ' 组成
s 表示一个有效的表达式
'+' 不能用作一元运算(例如, "+1" 和 "+(2 + 3)" 无效)
'-' 可以用作一元运算(即 "-1" 和 "-(2 + 3)" 是有效的)
输入中不存在两个连续的操作符
每个数字和运行的计算将适合于一个有符号的 32位 整数
一个表达式分为三部分:
左边表达式①,运算符③,右边表达式②
操作的步骤是:
如果当前是数字,那么更新计算当前数字;
如果当前是操作符+或者-,那么需要更新计算当前计算的结果 res,并把当前数字 num 设为 0,sign 设为正负,重新开始;
如果当前是 ( ,那么说明遇到了右边的表达式,而后面的小括号里的内容需要优先计算,所以要把 res,sign 进栈,更新 res 和 sign 为新的开始;
如果当前是 ) ,那么说明右边的表达式结束,即当前括号里的内容已经计算完毕,所以要把之前的结果出栈,然后计算整个式子的结果;
最后,当所有数字结束的时候,需要把最后的一个 num 也更新到 res 中。
class Solution(object):
def calculate(self, s):
res, num, sign = 0, 0, 1
stack = []
for c in s:
if c.isdigit():
num = 10 * num + int(c)
elif c == "+" or c == "-":
res += sign * num
num = 0
sign = 1 if c == "+" else -1
elif c == "(":
stack.append(res)
stack.append(sign)
res = 0
sign = 1
elif c == ")":
res += sign * num
num = 0
res *= stack.pop()
res += stack.pop()
res += sign * num
return res
旋转链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head or not head.next:
return head
n = 0
ghead = ListNode(0)
ghead.next = head
node = ghead
while(node.next):
node = node.next
n += 1
k %= n
step = n-k
knode = ghead
for i in range(step):
knode = knode.next
node.next = ghead.next
ghead.next = knode.next
knode.next = None
return ghead.next
反转链表II
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
pre = ListNode(0)
pre.next = head
dummy = pre
n = left
while(n-1):
pre = pre.next
n -= 1
cur = pre.next
betwn = right-left
while betwn:
gnode = cur.next
cur.next = gnode.next
gnode.next = pre.next
pre.next = gnode
betwn -= 1
return dummy.next
随机链表的复制
解法1:可以先构建一个可以查询的字典dic,先遍历一遍,在dic中存入所有节点,然后再遍历一遍,将所有节点直接的关系连接起来:
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
# return copy.deepcopy(head)
if not head:
return
dic = {}
cur = head
while cur:
dic[cur] = Node(cur.val)
cur = cur.next
cur = head
while cur:
dic[cur].next = dic.get(cur.next)
dic[cur].random = dic.get(cur.random)
cur = cur.next
return dic[head]
解法2:加trick
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
return copy.deepcopy(head)
判断链表是否有环
方法1:使用set集合存储记录过的节点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
seen = set()
while head:
if head in seen:
return True
seen.add(head)
head = head.next
return False
方法2:使用快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast is slow:
return True
return False
链表两数相加
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode], carry = 0) -> Optional[ListNode]:
if l1 is None and l2 is None:
return ListNode(carry) if carry else None
if l1 is None:
l1, l2 = l2, l1
s = carry + l1.val + (l2.val if l2 else 0)
l1.val = s%10
l1.next = self.addTwoNumbers(l1.next, l2.next if l2 else None, s//10)
return l1
爬楼梯
加上@cache后可以有效解决超时报错的问题
class Solution:
def climbStairs(self, n: int) -> int:
@cache
def dfs(x: int) -> int :
if x <= 1:
return 1
return dfs(x-1) + dfs(x-2)
return dfs(n)
罗马数字转整数
class Solution:
SYMBOL ={
'I':1,
'V':5,
'X':10,
'L':50,
'C':100,
'D':500,
'M':1000
}
def romanToInt(self, s: str) -> int:
ans = 0
n = len(s)
for i, ch in enumerate(s):
value = Solution.SYMBOL[ch]
if i < n-1 and value < Solution.SYMBOL[s[i+1]]:
ans -= value
else:
ans += value
return ans
整数转罗马数字
class Solution:
SYMBOL = {
'M':1000,
'CM':900,
'D':500,
'CD':400,
'C':100,
'XC':90,
'L':50,
'XL':40,
'X':10,
'IX':9,
'V':5,
'IV':4,
'I':1
}
def intToRoman(self, num: int) -> str:
roman = list()
for i, ch in enumerate(Solution.SYMBOL):
while Solution.SYMBOL[ch] <= num:
num -= Solution.SYMBOL[ch]
roman.append(ch)
if num == 0:
break
return "".join(roman)
复原IP地址
边界条件:遍历完所有数字,且path中正好有4个数字
通过306累加数中,了解到了,可以在选择数的过程中,增加判断条件,避免都堆到最后进行判断,造成超时
对于每个选择数字的判断:1、不能有“01”类型数字的出现 2、path长度不能超过4,3、数值int在范围内
完成判断,解决问题
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
n = len(s)
ans = []
path = []
def dfs(i):
if i == n and len(path) == 4:
ans.append(".".join(str(x) for x in path))
return
for j in range(i, n):
t = s[i:j+1]
if str(int(t)) != t:
break
if len(path)<4 and 0<=int(t)<=255:
path.append(t)
dfs(j+1)
path.pop()
dfs(0)
return ans
存在重复元素 II
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
num_to_index = {}
for i, ch in enumerate(nums):
if ch in num_to_index and i-num_to_index[ch] <= k:
return True
num_to_index[ch] = i
return False
最长递增子序列
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
dp = [1] *len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
找出字符串中第一个匹配项的下标
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
for i in range(0, len(haystack) - len(needle) + 1):
if haystack[i:i+len(needle)] == needle:
return i
return -1
最长连续序列
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
longe = 0
num_set = set(nums)
for num in num_set:
if num-1 not in num_set:
current_num = num
current_streak = 1
while current_num+1 in num_set:
current_num += 1
current_streak += 1
longe = max(longe, current_streak)
return longe
快乐数
class Solution:
def isHappy(self, n: int) -> bool:
def get_next(n):
total_num = 0
while n > 0:
n, digit = divmod(n, 10)
total_num += digit ** 2
return total_num
seen = set()
while n != 1 and n not in seen:
seen.add(n)
n = get_next(n)
return n == 1
最长回文子串
class Solution(object):
def longestPalindrome(self, s):
res = ''
for i in range(len(s)):
start = max(i - len(res) -1, 0)
temp = s[start: i+1]
if temp == temp[::-1]:
res = temp
else:
temp = temp[1:]
if temp == temp[::-1]:
res = temp
return res
反转字符串中的单词
给你一个字符串 s
,请你反转字符串中 单词 的顺序。
class Solution(object):
def reverseWords(self, s):
"""
:type s: str
:rtype: str
"""
return ' '.join(reversed(s.split()))
搜索旋转排序数组
在二分查找的上下文中,if target > nums[mid] and target <= nums[right]:
这一行通常用于检查目标值 target
是否位于当前中间元素 nums[mid]
的右侧,但不超过数组右边界 nums[right]
的情况。这种检查方式通常用于处理数组是部分有序(如旋转排序数组)或者有其他特定排序规则的情况。
这里的 else
分支则对应了所有不满足 if
条件的情况。具体来说,它涵盖了以下几种情况:
- 目标值小于中间值 (
target < nums[mid]
):- 这意味着目标值可能位于当前中间元素的左侧。在标准的二分查找中,你会将
right
更新为mid - 1
来缩小搜索范围到左半部分。但在处理旋转排序数组或其他特殊情况下,可能需要根据具体情况决定是更新left
还是right
。
- 这意味着目标值可能位于当前中间元素的左侧。在标准的二分查找中,你会将
- 目标值大于右边界值 (
target > nums[right]
):- 这种情况表明目标值在当前搜索的右半部分之外,且由于数组已经排序(或部分排序),我们可以确定目标值不在数组中。不过,在某些特殊情况下(如数组可能包含重复元素),可能需要进一步处理来确保准确性。
- 目标值等于中间值 (
target == nums[mid]
):- 这种情况在
if
条件中没有直接处理,因为它既不属于target > nums[mid]
也不属于target <= nums[right]
。因此,它会被else
分支捕获。在二分查找中,如果找到目标值,通常会立即返回该值的位置。但是,如果你的逻辑是寻找第一个等于目标值的元素(例如,在有序数组中),则可能需要进一步处理来确保返回的索引是正确的。
- 这种情况在
- 目标值大于中间值但大于右边界值 (
target > nums[mid] && target > nums[right]
):- 这种情况在逻辑上是不可能的,因为
target <= nums[right]
已经在if
条件中排除了target > nums[right]
的情况。但是,它说明了if
条件与else
分支之间的逻辑关系。
- 这种情况在逻辑上是不可能的,因为
class Solution:
def search(self, nums: List[int], target: int) -> bool:
if not nums: return -1
left, right = 0, len(nums)-1
while left<=right:
mid = (left + right)// 2
if nums[mid] == target:
return True
elif nums[left] == nums[right]:
left += 1
continue
elif nums[mid] <= nums[right]:
if nums[mid]<target<= nums[right]:
left = mid+1
else:
right = mid-1
else:
if nums[left]<=target< nums[mid]:
right = mid-1
else:
left = mid+1
return False
全排列
解法一:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
# return list(permutations(nums))
def dfs(x):
if x==len(nums)-1:
res.append(list(nums))
return
for i in range(x, len(nums)):
nums[i], nums[x] = nums[x], nums[i]
dfs(x+1)
nums[i], nums[x] = nums[x], nums[i]
res = []
dfs(0)
return res
解法二:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
return list(permutations(nums))
x的平方根
class Solution:
def mySqrt(self, x: int) -> int:
# return int(x**0.5)
i, j = 1, x
while i<=j:
m = (i+j) // 2
if m*m==x:
return m
elif m*m < x:
i = m+1
else:
j = m-1
return j
73. 矩阵置零
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
zero = []
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if matrix[i][j] == 0:
zero.append((i,j))
while len(zero) > 0:
x, y = zero.pop()
for i in range(len(matrix)):
for j in range(len(matrix[i])):
if i == x or j == y:
matrix[i][j] = 0
return
编辑距离
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
n, m = len(word1), len(word2)
word1 = '#' + word1
word2 = '#' + word2
# 初始化动态规划表
dp = [[0] * (m + 1) for _ in range(n + 1)]
# 填充dp表
for i in range(n + 1):
for j in range(m + 1):
if i == 0 and j == 0:
dp[i][j] = 0
elif i == 0:
dp[i][j] = j
elif j == 0:
dp[i][j] = i
else:
if word1[i] == word2[j]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = 1 + min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1])
return dp[n][m]
# 示例使用
solution = Solution()
print(solution.minDistance("horse", "ros")) # 输出应为 3
排列序列
class Solution:
def getPermutation(self, n: int, k: int) -> str:
fact = n *[1]
for i in range(1,n):
fact[i] = fact[i-1] * i
ans = list()
k -= 1
valid = [1] * (n+1)
for i in range(1, n+1):
order = k // fact[n-i] + 1
for j in range(1, n+1):
order -= valid[j]
if order == 0:
ans.append(str(j))
valid[j] = 0
break
k %= fact[n-i]
return "".join(ans)
回文链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
vals = []
current_node = head
while current_node is not None:
vals.append(current_node.val)
current_node = current_node.next
return vals == vals[::-1]
二叉树的最大深度
树的后序遍历 / 深度优先搜索往往利用 递归 或 栈 实现,本文使用递归实现。
关键点: 此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度 与 右子树的深度中的 最大值 +1
# 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 maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
翻转二叉树
# 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 invertTree(self, root):
"""
:type root: TreeNode
:rtype: TreeNode
"""
if not root:
return
tmp = root.left
root.left = self.invertTree(root.right)
root.right = self.invertTree(tmp)
return root
相同的树
给你两棵二叉树的根节点 p
和 q
,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
# 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 isSameTree(self, p, q):
"""
:type p: TreeNode
:type q: TreeNode
:rtype: bool
"""
if p is None or q is None:
return p is q
return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
逆波兰表达式
今天学逆波兰表达式撰写还顺便复习了一下二叉树的前中后序遍历:
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
本题两个要点:
a.判断字符串中的元素是数字还是字符串
因为Python 中没有一个函数可以判断一个字符串是否为合理的整数(包括正、负数)。str.isdigit() 可以判断正数,但是无法判断负数。
解决方法:
使用 int() 函数,并做 try-except 。
如果是整数,那么可以用 int() 转成数字;
如果是运算符,那么 int() 会报错,从而进入 except 中。
b.python 的整数除法是向下取整,而不是向零取整。
python2 的除法 "/" 是整数除法, "-3 / 2 = -2" ;
python3 的地板除 "//" 是整数除法, "-3 // 2 = -2" ;
python3 的除法 "/" 是浮点除法, "-3 / 2 = -1.5" ;
而 C++/Java 中的整数除法是向零取整。C++/Java 中 "-3 / 2 = -1" .本题的题意(一般情况)都是要求向零取整的。
解决方法:
对 Python 的整数除法问题,可以用 int(num1 / float(num2)) 来做,即先用浮点数除法,然后取整。
无论如何,浮点数除法都会得到一个浮点数,比如 "-3 / 2.0 = 1.5" ;
此时再取整,就会得到整数部分,即 float(-1.5) = -1 。
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
try:
stack.append(int(token))
except:
num2 = stack.pop()
num1 = stack.pop()
stack.append(self.evaluate(num1,num2, token))
return stack[0]
def evaluate(self,num1,num2,op):
if op == "+":
return num1+num2
if op == "-":
return num1-num2
if op == "*":
return num1 *num2
if op == "/":
return int(num1/float(num2))
最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素
核心思想就是:
push 方法通过将一个元组 (x, min_value) 添加到栈中来同时保存新元素 x 和当前栈中的最小值 min_value。
class MinStack:
def __init__(self):
self.stack = []
def push(self, val: int) -> None:
if not self.stack:
self.stack.append((val,val))
else:
self.stack.append((val,min(val, self.stack[-1][1])))
def pop(self) -> None:
self.stack.pop()
def top(self) -> int:
if self.stack:
return self.stack[-1][0]
else:
return None
def getMin(self) -> int:
return self.stack[-1][1]
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(val)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
三数之和
这道题记得之前做过,但是想不起来了。。总结一下:
函数的主要步骤和关键点:
排序:对输入的整数数组nums进行排序。这是非常重要的,因为它允许我们使用双指针技巧来高效地找到满足条件的三元组。
初始化:定义ans列表来存储所有找到的三元组,并初始化三个指针first、second和third。
枚举第一个数:使用first指针遍历整个数组。为了避免重复的三元组(例如[-1, 0, 1]和[0, -1, 1]),我们需要跳过所有与前一个数相同的数。
设置目标和双指针:将目标和target设置为-nums[first],然后初始化third指针为数组的最后一个元素的索引。此时,我们需要找到两个数(nums[second]和nums[third]),它们的和等于target。
枚举第二个数:使用second指针从first + 1开始遍历数组。同样地,为了避免重复的三元组,我们需要跳过所有与前一个数相同的数。
双指针技巧:当nums[second] + nums[third] > target时,说明third指向的数太大了,我们需要将third向左移动;否则,我们检查是否找到了一个满足条件的三元组。
避免重复:当second和third相遇或nums[second] + nums[third] == target时,我们需要检查是否找到了一个有效的三元组,并将其添加到ans列表中。然后,我们继续移动second指针,但在这之前,我们需要跳过所有与当前nums[second]相同的数,以避免找到重复的三元组。
返回结果:返回存储了所有满足条件的三元组的ans列表。
改进点:这个算法的时间复杂度是O(n^2),其中n是数组nums的长度。
设 s = nums[first] + nums[first+1] + nums[first+2],如果 s > 0,由于数组已经排序,后面无论怎么选,选出的三个数的和不会比 s 还小,所以只要 s > 0 就可以直接 break 外层循环了。
如果 nums[first] + nums[n-2] + nums[n-1] < 0,由于数组已经排序,nums[first] 加上后面任意两个数都是小于 0 的,所以下面的双指针就不需要跑了。但是后面可能有更大的 nums[first],所以还需要继续枚举,continue 外层循环。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
ans = []
n = len(nums)
for i in range(n-2):
x = nums[i]
if i > 0 and x == nums[i-1]:
continue
if x + nums[i+1] + nums[i+2] > 0:
break
if x + nums[-1] + nums[-2] < 0:
continue
j = i+1
k = n-1
while j<k:
s = x + nums[j] + nums[k]
if s < 0:
j += 1
elif s > 0:
k -= 1
else:
ans.append([x,nums[j],nums[k]])
j += 1
while j < k and nums[j] == nums[j-1]:
j += 1
k -= 1
while k > j and nums[k] == nums[k+1]:
k -= 1
return ans
有效的括号
最近换实习很久不刷leetcode。。真的有点手生了,还是要坚持刷阿!
有效的括号这道题就是实现了一个相互匹配,那么基本上就是用字典,那么如何灵活的用字典,可以使用括号对应数字取加和判断,也可以就单独压入右括号的方法。
class Solution:
def isValid(self, s: str) -> bool:
dic = {'{':'}','(':')','[':']'}
stack = []
for c in s:
if c in dic:
stack.append(dic[c])
elif not stack or stack.pop()!= c:
return False
return len(stack) == 0
一开始觉得数字的简单,但是后面耐心了解了一下右括号的发现更简单!
数字加和C++版:
class Solution {
public:
bool isValid(string s) {
unordered_map<char, int> myMap = {
{'(', 1},
{'{', 2},
{'[', 3},
{']', 4},
{'}', 5},
{')', 6}
};
stack<char> mySk;
for(auto element:s)
{
if(myMap[element] < 4)
{
mySk.push(element);
}
else
{
if(mySk.empty() || myMap[element] + myMap[mySk.top()] != 7)
{
return false;
}
else
{
mySk.pop();
}
}
}
return mySk.empty();
}
};
当然也可以不用字典或者map,因为毕竟元素比较少,直接手敲也可以。
public boolean isValid(String s) {
if(s.isEmpty())
return true;
Stack<Character> stack=new Stack<Character>();
for(char c:s.toCharArray()){
if(c=='(')
stack.push(')');
else if(c=='{')
stack.push('}');
else if(c=='[')
stack.push(']');
else if(stack.empty()||c!=stack.pop())
return false;
}
if(stack.empty())
return true;
return false;
}
字母异位词分组
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
np = collections.defaultdict(list)
for st in strs:
name = "".join(sorted(st))
np[name].append(st)
return list(np.values())
单词规律
跟上一个字符串的思路一致,只是要进行单词的拆分,用.split()函数即可。
class Solution:
def wordPattern(self, pattern: str, s: str) -> bool:
word = s.split()
if(len(pattern) != len(word)):
return False
return len(set(pattern)) == len(set(word)) == len(set(zip(pattern,word)))
同构字符串
要求:判断两个字符串的形式是不是一致,即是不是AABC或者ABBBCC这种。
trick:使用set()结合zip()。
set()用法:用于创建一个不包含重复元素的集合
zip()用法:用于将可迭代的对象作为参数,将对象中的元素打包成一个个元组,然后返回这些元组组成的对象。
s="abc"
t="xyz"
zipped = zip(s,t)
list_1 = list(zipped)
print(list_1) #输出[('a','x'),('b','y'),('c','z')]
统计优美子数组
解题思路
一、滑动窗口
不断右移 right 指针来扩大滑动窗口,使其包含 k 个奇数;
若当前滑动窗口包含了 k 个奇数,则如下「计算当前窗口的优美子数组个数」:
统计第 1 个奇数左边的偶数个数 leftEvenCnt。 这 leftEvenCnt 个偶数都可以作为「优美子数组」的起点,因此起点的选择有 leftEvenCnt + 1 种(因为可以一个偶数都不取,因此别忘了 +1 )。
统计第 k 个奇数右边的偶数个数 rightEvenCnt 。 这 rightEvenCnt 个偶数都可以作为「优美子数组」的终点,因此终点的选择有 rightEvenCnt + 1 种(因为可以一个偶数都不取,因此别忘了 +1 )。
因此「优美子数组」左右起点的选择组合数为 (leftEvenCnt + 1) * (rightEvenCnt + 1)。
class Solution:
def numberOfSubarrays(self, nums: List[int], k: int) -> int:
left = right = odd_cnt = res = 0
while right < len(nums):
if nums[right] % 2 == 1:
odd_cnt += 1
if odd_cnt == k:
tmp = right
while right < len(nums) and nums[right] % 2 == 0:
right += 1
right_even_cnt = right - tmp
left_even_cnt = 0
while left < len(nums) and nums[left] % 2 == 0:
left_even_cnt += 1
left += 1
res += (left_even_cnt + 1) * (right_even_cnt + 1)
left += 1
odd_cnt -= 1
right += 1
return res
爱生气的书店老板
解题思路
重点:
不生气时顾客会留下,生气时会赶走顾客。
「秘密技巧」可以使老板在窗口大小 X 的时间内不生气。我们使用「秘密技巧」的原则是:寻找一个时间长度为 X 的窗口,能留住更多的原本因为老板生气而被赶走顾客。
使用「秘密技巧」能得到的最终的顾客数 = 所有不生气时间内的顾客总数 + 在窗口 X 内使用「秘密技巧」挽留住的原本因为生气而被赶走顾客数。
因此,可以把题目分为以下两部分求解:
所有不生气时间内的顾客总数:使用 iii 遍历[0,customers.length)[0, customers.length)[0,customers.length),累加grumpy[i]==0grumpy[i] == 0grumpy[i]==0时的customers[i]customers[i]customers[i]。
在窗口 X 内因为生气而被赶走的顾客数:使用大小为 X 的滑动窗口,计算滑动窗口内的grumpy[i]==1grumpy[i] == 1grumpy[i]==1时的customers[i]customers[i]customers[i],得到在滑动窗口内老板生气时对应的顾客数。
滑动窗口遍历:
从第x个元素开始遍历到最后一个元素,每一步中,我们执行以下操作:
如果新进入窗口的元素(即customers[i]和grumpy[i])对应的员工是生气的就将其服务的顾客数加到curValue中。
如果离开窗口的元素(即customers[i-X]和grumpy[i-X]对应的员工是生气的,则从其服务的顾客数中减去该值(从curValue中))
class Solution:
def maxSatisfied(self, customers: List[int], grumpy: List[int], X: int) -> int:
N = len(customers)
sum_ = 0
# 所有不生气时间内的顾客总数
for i in range(N):
sum_ += customers[i] * (1 - grumpy[i])
# 生气的 X 分钟内,会让多少顾客不满意
curValue = 0
# 先计算起始的 [0, X) 区间
for i in range(X):
curValue += customers[i] * grumpy[i]
resValue = curValue
# 然后利用滑动窗口,每次向右移动一步
for i in range(X, N):
# 如果新进入窗口的元素是生气的,累加不满意的顾客到滑动窗口中
# 如果离开窗口的元素是生气的,则从滑动窗口中减去该不满意的顾客数
curValue = curValue + customers[i] * grumpy[i] - customers[i - X] * grumpy[i - X]
# 求所有窗口内不满意顾客的最大值
resValue = max(resValue, curValue)
# 最终结果是:不生气时的顾客总数 + 窗口X内挽留的因为生气被赶走的顾客数
return sum_ + resValue
水果成篮(最大滑窗)
白话题意:求满足某个条件(数组值最多就两类的连续数组,例如[1,2,2,1,2])的最长数组长度
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
# 初始化
i, j = 0, 0
res = 0
classMap = defaultdict(int)
classCnt = 0
# 移动滑窗右边界
while j < len(fruits):
# 判断当前是否满足条件
if classMap[fruits[j]] == 0:
classCnt += 1
classMap[fruits[j]] += 1
# 若不满足条件,移动i
while classCnt > 2:
if classMap[fruits[i]] == 1:
classCnt -= 1
classMap[fruits[i]] -= 1
i += 1
# 一旦满足条件,更新结果
res = max(res, j - i + 1)
j += 1
return res
串联所有单词的子串
有没有一样喜欢看示例的,,看题目就觉得很难懂。大致就是words要进行排列组合,返回s中所有包含这个排列组合的首标。
顺完逻辑蛮好懂的,应该不算困难题,只是不知道用什么模块实现。
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
if not s or not words: return []
one_word = len(words[0])
all_len = one_word * len(words)
n = len(s)
words = Counter(words)
res = []
for i in range(0, n-all_len+1):
tmp = s[i:i+all_len]
c_tmp = []
for j in range(0, all_len, one_word):
c_tmp.append(tmp[j:j+one_word])
if Counter(c_tmp) == words:
res.append(i)
return res
盛水最多的容器
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
写出来了一半,想到用双指针,没想好怎么移动,后面看懂啦,小的先移
class Solution:
def maxArea(self, height: List[int]) -> int:
i,n = 0,len(height)-1
max1pool = 0
while i != n:
min2 = min(height[i], height[n])
max1pool = max(max1pool, (n-i) * min2)
if height[i] < height[n]:
i += 1
else:
n -= 1
return max1pool
两数之和(输入有序数组)
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
思路:双指针!发现双指针频率真的很高
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
low,high = 0,len(numbers) -1
while low<high:
total = numbers[high] + numbers[low]
if total == target:
return [low+1,high+1]
elif total < target:
low+=1
else:
high-=1
return [-1,-1]
判断子序列
写了一版,发现这个记录的顺序不对,又去调试才看出来的,逻辑写错了,最近脑子真的不转。。。
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
r = []
for i in range(len(s)):
if s[i] not in t:
return False
else:
r.append(s[i])
if "".join(r) == s:
return True
else:
return False
改进(双指针):
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
n,m = len(s),len(t)
i = j = 0
while i < n and j < m:
if s[i] == t[j]:
i += 1
j += 1
return i == n
最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀,如果不存在返回空字符串“”。
一开始没有思路,但看完答案就懂了,简单题
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs:
return ""
common_lm = strs[0]
for i in range(len(strs)):
common_lm = self.get(common_lm, strs[i])
if common_lm == "":
return ""
return common_lm
def get(self, str1,str2):
i = 0
while(i < len(str1) and i < len(str2) and str1[i] == str2[i]):
i += 1
return str1[:i]
文本左右对齐
给定一个单词数组 words
和一个长度 maxWidth
,重新排版单词,使其成为每行恰好有 maxWidth
个字符,且左右两端对齐的文本。
你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' '
填充,使得每行恰好有 maxWidth 个字符。
要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。
文本的最后一行应为左对齐,且单词之间不插入额外的空格。
注意:
- 单词是指由非空格字符组成的字符序列。
- 每个单词的长度大于 0,小于等于 maxWidth。
- 输入单词数组
words
至少包含一个单词。
class Solution(object):
def fullJustify(self, words, maxWidth):
res, cur, num_of_letters = [], [], 0
for w in words:
if num_of_letters + len(w) + len(cur) > maxWidth:
for i in range(maxWidth - num_of_letters):
cur[i%(len(cur)-1 or 1)] += ' '
res.append(''.join(cur))
cur, num_of_letters = [], 0
cur += [w]
num_of_letters += len(w)
return res + [' '.join(cur).ljust(maxWidth)]
[' '.join(cur).ljust(maxWidth)]
这个表达式的意思是:首先,将cur
中的元素以空格为分隔符连接成一个字符串;然后,将这个字符串在右侧填充空格(如果需要的话),直到它的长度达到maxWidth
指定的宽度;最后,将这个处理后的字符串作为列表中唯一的元素返回。
cur[i%(len(cur)-1 or 1)] += ' '这行代码实现了根据空格的数量来实现遍历字符加空格。
最后一个单词的长度
给你一个字符串s,由若干单词组成,单词前后用一些空格字符隔开,返回字符串中最后一个单词的长度。
第一次解法:
class Solution:
def lengthOfLastWord(self, s: str) -> int:
n = len(s)
j = 0
for i in range(n-1, -1, -1):
if s[i] != ' ':
j += 1
elif j==0 and s[i] == ' ':
continue
else:
break
return j
然后看见别人的一行成功。。。果然还是加trick香啊
class Solution:
def lengthOfLastWord(self, s: str) -> int:
return len(s.split()[-1])
除自身之外数组乘积
题意理解还是蛮容易的,但是果然超时了。。。
第一次解答:
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
result = []
lens = len(nums)
lun = 0
for i in range(lens):
j = 0
total = 1
while(j < lens):
if j != i and nums[j] != 0:
total *= nums[j]
elif j != i and nums[j] == 0:
total = 0
break
else:
total *= 1
j += 1
result.append(total)
return result
改进:看到评论有一个双指针,写的很好TAT,
第一遍从前往后迭代,第二遍for从后往前乘。
注意:先将beforesum(当前元素左边的所有元素的乘积)乘ans[i]更新ans[i]的值,
再更新beforesum的值,将当前元素乘进去,方便下一个元素计算。
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
ans = [1] * n # 初始化ans列表,所有元素都是1
before_num = 1
for i in range(n):
ans[i] = before_num # 更新ans[i]为当前的before_num
before_num *= nums[i] # 更新before_num
after_num = 1
for j in range(n-1, -1, -1):
ans[j] *= after_num # 更新ans[j],乘以当前的after_num
after_num *= nums[j] # 更新after_num
return ans
O(1) 时间插入、删除和获取随机元素
这道题要求实现一个类,满足插入、删除和获取随机元素操作的平均时间复杂度为 O(1)。
变长数组可以在 O(1) 的时间内完成获取随机元素操作,但是由于无法在 O(1)的时间内判断元素是否存在,因此不能在 O(1) 的时间内完成插入和删除操作。哈希表可以在 O(1) 的时间内完成插入和删除操作,但是由于无法根据下标定位到特定元素,因此不能在 O(1) 的时间内完成获取随机元素操作。为了满足插入、删除和获取随机元素操作的时间复杂度都是 O(1),需要将变长数组和哈希表结合,变长数组中存储元素,哈希表中存储每个元素在变长数组中的下标。
总括:数组提供快速访问,哈希表提供快速查找。
首先创建两个实例化对象:
self.nums 是一个列表,用于存储插入的数字。
self.indices 是一个字典,用于存储插入的数字以及其在 self.nums 列表中的索引。
创建RandomizedSet类的实例时,会自动创建一个空的nums列表和一个空的indices字典,为后续插入删除和随机获取做准备。
choice 是 Python 标准库中的 random 模块中的函数,用于从列表中随机选择一个元素并返回。
比较难理解的是remove这个类,过程:
首先检查给定的值是否存在indices字典中,如果不在,就返回False,表示删除失败
如果值存在,他会取出该值在nums列表中的索引id,然后他将nums列表中索引为id的元素替换为nums列表中的最后一个元素的值,这样就删除了原始的val,更新indices字典最后一个元素的索引值为id。删除nums列表中的最后一个元素,然后从indices列表中删除给定值。
这如果还不理解的话,可以自己画一个图就理解了。
例如nums:2,1,3
indices:(2:0),(1:1),(3:2)
举例:
(1)insert,插入4:
在字典里增加值为“4”的点indices[4]的值为len(nums)= 3, nums增加4。
(2)remove。删除1:
获取“1”的值(序号),id为1。在nums找到序号为1的元素,将值更改为最后一个元素的值,即3.
变为:nums:2,3,3
indices:(2:0),(1:1),(3:2)
nums[1]值为1,indice里面“3”对应的值改为id=1
变为:nums:2,3,3
indices:(2:0),(1:1),(3:1)
然后弹出nums最后一个元素,将indices中元素“1”删除。
变为:nums:2,3
indices:(2:0),(3:1)
class RandomizedSet:
def __init__(self):
self.nums = []
self.indices = {}
def insert(self, val: int) -> bool:
if val in self.indices:
return False
self.indices[val] = len(self.nums)
self.nums.append(val)
return True
def remove(self, val: int) -> bool:
if val not in self.indices:
return False
id = self.indices[val]
self.nums[id] = self.nums[-1]
self.indices[self.nums[id]] = id
self.nums.pop()
del self.indices[val]
return True
def getRandom(self) -> int:
return choice(self.nums)
跳跃游戏
思路:贪心算法,尽可能达到最远的位置(因为如果可以达到某个位置,一定可以达到之前的位置)
class Solution:
def canJump(self, nums: List[int]) -> bool:
max_i = 0
for i ,jump in enumerate(nums):
if max_i>=i and (i + jump) > max_i:
max_i = i + jump
return max_i >= i
这里的return max_i >= i,注意可以换成return max_i >= len(nums)-1.
首先初始化当前能到达的最远距离,i为当前位置,jump是当前位置的跳数
if判断如果当前位置到达,并且当前位置+跳数>最远位置
然后更新最远能到达位置
买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
思路:暴力破解,,超出时间限制了
class Solution:
def maxProfit(self, prices: List[int]) -> int:
lens = len(prices)
max1 = 0
for i in range(lens):
for j in range(i, lens):
if prices[j] > prices[i]:
max1 = max(max1, prices[j] - prices[i])
return max1
改进:
双重循环换成ifelse加单循环,这个思路很好理解就不巴巴了
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices:
return 0
max_profit = 0
min_profit = prices[0]
for price in prices:
if price < min_profit:
min_profit = price
else:
max_profit = max(max_profit, price - min_profit)
return max_profit
多数元素
返回数组中超过长度半数数量的元素的值
一开始想的是进行sort排序,然后取中间值与两头的分别比对一下,如果一致就返回。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort(reverse = True)
n = len(nums)
mid = int(n // 2)
if nums[n-1] == nums[mid] or nums[0] == nums[mid]:
return nums[mid]
else:
return 0
但是提交的时候发现还有这种情况:
于是调整一下思路:
因为排序后的数组,如果存在一个数超过数组长度一半,中位数一定是那个数,所以使用Counter计数,只需要判断中位数的次数有没有超过长度一半即可
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort(reverse = True)
n = len(nums)
mid = int(n // 2)
counts = Counter(nums)
if counts[nums[mid]]>= mid :
return nums[mid]
else:
return 0
删除有序数组中的重复项I + II
删除有序数组中的重复项I
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
思路:双指针(不要被名字吓到,其实就是两个ij变量)
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
i = 0
for j in range(1, len(nums)):
if nums[j] != nums[i]:
i += 1
nums[i] = nums[j]
return i + 1
定义两个指针 i 和 j,其中 i 是慢指针,而 j 是快指针。当 nums[i] == nums[j] 时,递增 j 以跳过重复项;当 nums[i] != nums[j] 时,将 nums[j] 的值复制到 nums[i+1] 处,并递增 i。最后返回 i+1 即为去重后数组的新长度。
最后应该只会检查前i+1个元素是否符合,如果系统不是这样检测,可以更改一下nums截取
即
nums[:] = nums[i+1:]
删除有序数组中的重复项II
题目省略了,就是现在可以最多重复两次。
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
len1=0
for i in range(len(nums)):
if(len1<2 or nums[len1 - 2] != nums[i]):
nums[len1] = nums[i]
len1 += 1
return len1
数组--移除元素+合并两个有序数组
移除元素:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
nums[:] = [num for num in nums if num!=val]
return len(nums)
nums[:]实现对nums元素的复制。
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
nums1[m:] = nums2
nums1.sort()
随机链表的复制
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
疑问:为什么不能写成dic[cur] = cur.val,若这样实际上将cur.val赋值给了dic的键cur,而不是将一个新的Node(cur.val)对象赋给dic的键cur。这就意味着若cur.val的值一旦改变,字典dic中的值也会发生改变,因为是一个对象的引用。
实现哈希表可以有以下两种方式:
数组 + 链表
数组 + 二叉树
所以,哈希表本质上就是一个数组。只不过数组存放的是单一的数据,而哈希表中存放的是键值对。
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return
dic = {}
cur = head
while cur:
dic[cur] = Node(cur.val)
cur = cur.next
cur = head
while cur:
dic[cur].next = dic.get(cur.next)
dic[cur].random = dic.get(cur.random)
cur = cur.next
return dic[head]
第一个while循环作用:遍历原链表,将原链表中的每个节点cur复制为一个新节点,并将这个新节点存储到字典dic中。
第二个while循环作用:处理复制链表中的next和random指针,在原链表中,每个节点的next和random指针可能指向其他节点,需要在复制链表中正确指向对应的新节点。
使用dic.get(cur.random)是为了处理可能存在空指针的情况。若原指针指向的是空,则返回None,就避免了直接使用dic.get(cur.random)可能导致的keyerror错误。
最长交替子数组
要求:给定一个数组,找出符合【x, x+1,x,x-1】这样循环的最大交替数组长度。
思路:用两层while循环,第一个while用来找到符合这个循环的开头位置,第二个用来找到该循环的结束位置,并比较一下max进行记录。
易错:要进行减一,因为上一个字符串最后一个结束的数字可能是下一个字符串的开头。
class Solution:
def alternatingSubarray(self, nums: List[int]) -> int:
ans = 0
i ,n = 0, len(nums)
while i < n-1:
if nums[i+1]-nums[i] != 1:
i += 1
continue
i0 = i
i += 2
while i < n and nums[i] == nums[i - 2]:
i += 1
ans = max(ans, i-i0)
i -= 1
return ans
自己重写的时候出现的写错句子:
队列中可以看到的人数
有n人排成一个队列,从左到右编号为0到n-1,height数组记录每个人的身高,返回一个数组,记录每个人能看到几个人。
类比:山峰问题,高的后面的矮的看不见。
从后往前,最后一个元素入栈,若前面的比他小,加入,元素自增一,若比他大,将栈顶元素出栈,大的元素加入,循环判断出栈一个加一。
找出缺失的观测数据
给你一个长度为 m 的整数数组 rolls ,其中 rolls[i] 是第 i 次观测的值。同时给你两个整数 mean 和 n 。返回一个长度为 n 的数组,包含所有缺失的观测数据,且满足这 n + m 次投掷的 平均值 是 mean 。如果存在多组符合要求的答案,只需要返回其中任意一组即可。如果不存在答案,返回一个空数组。
第一反应就是对剩余总和取平均,写完看了下题解确实是这样,只不过我的时间有些长。。。
可以加一些trick,例如不用一个一个加,使用sum()函数计算总和,使用divmod()计算平均和取mod,return的时候直接返回x*n这样的格式。
自写版:
最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
这道题思路有点没搞懂,滑动窗口问题. 逐行解释:
cnt_s用于统计滑动窗口中字符出现字数,cnt_t用于统计字符串t中每个字符出现的次数。
for循环中,是遍历字符串s,right是当前字符的索引,c是当前字符。
cnt_s[c]将当前字符c在滑动窗口中的计数加1
while代表当滑动窗口中的字符至少和t中的字符计数相等时,进入循环。
if right-left < ans_right-ans_left;如果当前窗口的长度小于已知的最短窗口长度,则更新最短窗口的边界。
ans_right、ans_left更新最短窗口的左右边界。cns_s[s[left]]将滑动窗口左端点的字符计数减一,因为窗口要向右滑动。
return 最后返回找到的最短子串。如果没有找到任何子串(ans_left仍为-1),则返回空字符串。
class Solution:
def minWindow(self, s: str, t: str) -> str:
ans_left,ans_right = -1,len(s)
left = 0
cnt_s = Counter()
cnt_t = Counter(t)
for right,c in enumerate(s):
cnt_s[c] += 1
while cnt_s >= cnt_t:
if right-left < ans_right-ans_left:
ans_left,ans_right = left,right
cnt_s[s[left]] -= 1
left += 1
return "" if ans_left<0 else s[ans_left:ans_right+1]
无重复字符的最长子串
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
ans = left = 0
window = set() # 维护从下标 left 到下标 right 的字符
for right, c in enumerate(s):
# 如果窗口内已经包含 c,那么再加入一个 c 会导致窗口内有重复元素
# 所以要在加入 c 之前,先移出窗口内的 c
while c in window: # 窗口内有 c
window.remove(s[left])
left += 1 # 缩小窗口
window.add(c) # 加入 c
ans = max(ans, right - left + 1) # 更新窗口长度最大值
return ans
这题二刷的时候想错了思路,要记得是滑动窗口问题,二刷思路(错误):
# if len(s) == 0:
# return 0
# d = s[0]
# ans = 1
# max_ans = 1
# dt = set()
# dt.add(d)
# for e,i in enumerate(s):
# if e == 0:
# continue
# if i != d and i not in dt:
# ans += 1
# d = i
# dt.add(i)
# else:
# ans = 1
# dt = set()
# dt.add(i)
# max_ans = max(max_ans, ans)
# return max_ans
长度最小的子数组
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
if not nums:
return 0 # 如果数组为空,返回0或根据需求处理
sum1 = 0
left = 0
min_len = float('inf') # 初始化为无穷大,方便找到最小长度
for right, c in enumerate(nums):
sum1 += c # 累加当前元素
while sum1 >= target:
min_len = min(min_len, right - left + 1) # 更新最小长度
sum1 -= nums[left] # 缩小窗口
left += 1 # 移动左边界
# 如果min_len没有被更新过,说明没有找到符合条件的子数组
return min_len if min_len != float('inf') else 0
二叉树中的最大路径和
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
题目理解与分析:就是在二叉树中找到一条和最大的线。
解题思路:从上往下使用递归,1.迭代计算最大的左孩子长度,迭代计算最大的右孩子长度 2.计算每个节点加上左右孩子的最大长度作为最大值,并每个计算完与最大值比较更新。3. 判断左节点和右节点孰大孰小,更新节点的最大路径。
因为最长的线可能出现在:以叶节点为根的单个路径、以叶节点的父节点为根的回旋路径、以根节点为父节点的回旋路径/单个路径。所以归根到底是记录以每个节点为根的最大路径。
class TreeNode(object):
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def __init__(self):
self.maxSum = float("-inf")
def maxPathSum(self, root: TreeNode) -> int:
def maxGain(node):
if not node:
return 0
leftGain = max(maxGain(node.left), 0)
rightGain = max(maxGain(node.right), 0)
priceNewPath = node.val + leftGain + rightGain
self.maxSum = max(self.maxSum, priceNewPath)
return node.val + max(leftGain, rightGain)
maxGain(root)
return self.maxSum
k个一组翻转
涉及指针以及单向链表,主要思路应该是建立一个新链表,每次从旧链表拿出来一个next指向之前进来的。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head:
return None
p = head
i = 0
while p and i < k:
p = p.next
i += 1
if i < k:
return head
prev,curr = None, head
for _ in range(k):
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
head.next = self.reverseKGroup(curr,k)
return prev
举例:
输入链表:1->2->3->4->5->6->7
输入k值:3
第一次for循环之后的链表:
1->NULL
2->3->4->5->6->7->NULL
head.next递归调用函数,继续翻转剩余的k个节点。并将反转后的链表连接到当前反转的k个节点后。
第二次for循环之后的链表:
2->1->NULL
3->4->5->6->7->NULL
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
n = 0
cur = head
while cur:
n += 1
cur = cur.next
p0 = dummy = ListNode(next=head)
pre = None
cur = head
while n>=k:
n-=k
for _ in range(k):
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
nxt = p0.next
nxt.next = cur
p0.next = pre
p0 = nxt
return dummy.next
合并区间+插入区间+汇总区间+射出最少的箭
合并区间
第一步应该想到要按区间的第一个元素进行排序,然后若后一个区间的起始元素小于前一个区间的结尾元素,判断一下两个区间的结尾元素孰大孰小,选择大的作为新区间的结尾元素。
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key = lambda x: x[0])
merged = []
for interval in intervals:
if not merged or merged[-1][1] < interval[0]:
merged.append(interval)
else:
merged[-1][1] = max(merged[-1][1], interval[1])
return merged
插入区间
和合并区间一样,先把插入的区间加入,后面在sort和合并区间套路一样。
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
res = []
intervals.append(newInterval)
intervals.sort(key = lambda x:x[0])
for interval in intervals:
if not res or res[-1][1] < interval[0]:
res.append(interval)
else:
res[-1][1] = max(res[-1][1], interval[1])
return res
汇总区间
class Solution:
def summaryRanges(self, nums: List[int]) -> List[str]:
def f(i:int,j:int)->str:
return str(nums[i]) if i==j else f'{nums[i]}->{nums[j]}'
i = 0
n = len(nums)
ans = []
while i < n:
# for i in range(n-1):
j = i
while j + 1 < n and nums[j+1] == nums[j]+1:
j += 1
ans.append(f(i,j))
i = j+1
return ans
射出最少的箭
相比较合并区间是合并取最大,那么射箭就是合并取最小。并集改为交集,左区间找最大,右区间找最小。
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
points.sort(key = lambda x : x[0])
merged = []
for point in points:
if not merged or merged[-1][1] < point[0]:
merged.append(point)
else:
merged[-1][0] = max(merged[-1][0], point[0])
merged[-1][1] = min(merged[-1][1], point[1])
y = len(merged)
return y
买卖股票问题
还有很多类似的题,实际上就是一个折线图问题,出现增加才进行计算:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
ans = 0
n = len(prices)
for i in range(1, n):
if prices[i] > prices[i-1]:
ans += prices[i] - prices[i-1]
# ans += max(0, prices[i] - prices[i-1])
return ans
轮转数组+翻转数组(中等难度)
也就是输入k值,让数组实现轮转k次,类似于队列出去k次再进。
用python简单截断,
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
ans = n - k%n
nums[:] = nums[ans:] + nums[:ans]
类似的题:实现数组翻转
找到中位数进行左右互换
from typing import List
class Solution:
def reverse_array(self, nums: List[int]) -> None:
n = len(nums)
mid = n // 2
for i in range(mid):
nums[i], nums[n - 1 - i] = nums[n - 1 - i], nums[i]
# 测试示例
nums = [1, 2, 3, 4, 5]
sol = Solution()
sol.reverse_array(nums)
print(nums)
腐烂的橘子
在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1
r是x轴坐标,c是y轴坐标
class Solution:
def orangesRotting(self, grid: List[List[int]]) -> int:
x = len(grid)
y = len(grid[0])
queue = []
count = 0
for i in range(x):
for j in range(y):
if grid[i][j] == 1:
count += 1
elif grid[i][j] == 2:
queue.append([i,j])
round = 0
while count > 0 and len(queue) > 0:
round += 1
n = len(queue)
for i in range(n):
r, c = queue.pop(0)
if r-1>=0 and grid[r-1][c] == 1:
grid[r-1][c] = 2
count -= 1
queue.append((r-1,c))
if r+1<x and grid[r+1][c] == 1:
grid[r+1][c] = 2
count -= 1
queue.append((r+1,c))
if c-1>=0 and grid[r][c-1] == 1:
grid[r][c-1] = 2
count -= 1
queue.append((r,c-1))
if c+1<y and grid[r][c+1] == 1:
grid[r][c+1] = 2
count -= 1
queue.append((r,c+1))
if count > 0:
return -1
else:
return round
H指数
给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。
一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数 是其中最大的那个。
灵活应用while循环和排序:
reverse – 排序规则,reverse = True 降序, reverse = False 升序(默认)。
注意的是while中的语句不能互换,否则出现超出范围报错。
class Solution:
def hIndex(self, citations: List[int]) -> int:
sorted_citations = sorted(citations, reverse= True)
n = len(citations)
a = 0
while a < n and sorted_citations[a] > a:
a += 1
return a
接雨水
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
具体步骤如下:
如果输入数组为空,直接返回 0。
初始化左右指针 left 和 right,分别指向数组的左右端点。
初始化 left_max 和 right_max 分别记录左右两侧的最大高度。
初始化 result 变量,用来累积存储的雨水单位。
当 left 小于 right 时,执行以下操作:
如果 height[left] 小于 height[right],说明左侧是瓶颈,更新 left_max 并计算左侧能存储的雨水单位,然后将 left 指针右移。
否则,说明右侧是瓶颈,更新 right_max 并计算右侧能存储的雨水单位,然后将 right 指针左移。
最终返回 result 作为答案。
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
if not height:
return 0
left, right = 0, len(height) - 1
left_max, right_max = 0, 0
result = 0
while left < right:
if height[left] < height[right]:
if height[left] >= left_max:
left_max = height[left]
else:
result += left_max - height[left]
left += 1
else:
if height[right] >= right_max:
right_max = height[right]
else:
result += right_max - height[right]
right -= 1
return result
# 测试代码
sol = Solution()
height = [0,1,0,2,1,0,1,3,2,1,2,1]
result = sol.trap(height)
print(result)
最大正方形
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
dp = [[0] * (n + 1) for _ in range(m + 1)] # 初始化dp数组,大小为(m+1)x(n+1)
ans = 0
for i in range(1, m + 1):
for j in range(1, n + 1):
if matrix[i - 1][j - 1] == '1':
dp[i][j] = 1
if i > 1 and j > 1:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
ans = max(ans, dp[i][j])
return ans # 直接返回最大正方形的边长
注释版:
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
# 检查输入矩阵是否为空或第一行是否为空(未定义),如果是,则无法形成正方形,直接返回0
if not matrix or not matrix[0]:
return 0
# 获取矩阵的行数和列数
m, n = len(matrix), len(matrix[0])
# 初始化一个二维动态规划数组dp,大小为(m+1)x(n+1)。
# 多加的一行一列用于简化边界条件的处理,使得dp[i][j]可以直接对应matrix[i-1][j-1]
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化最大正方形的边长为0
ans = 0
# 遍历矩阵中的每个元素(除了dp的第一行和第一列,它们保持为0)
for i in range(1, m + 1):
for j in range(1, n + 1):
# 如果当前元素是'1',则进行动态规划的计算
if matrix[i - 1][j - 1] == '1':
# 初始化当前位置的最小可能边长为1
dp[i][j] = 1
# 如果当前位置的上方、左方和左上方都有'1',则计算这三个方向上正方形的最小边长并加1
if i > 1 and j > 1:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
# 更新最大正方形的边长
ans = max(ans, dp[i][j])
# 返回最大正方形的边长
return ans
使用了动态规划的思想来解决“最大正方形”问题。它维护了一个二维数组dp
,其中dp[i][j]
表示以(i-1, j-1)
为右下角的最大正方形的边长(注意,这里的索引转换是因为dp
数组比matrix
多了一行一列)。
在遍历matrix
时,对于每个值为'1'的元素,代码会检查其上方、左方和左上方的元素是否也为'1'。如果是,则这三个方向上可能存在一个更大的正方形,其边长可以通过这三个方向上已有的正方形的最小边长加1来得到。这个过程会一直进行,直到遍历完整个matrix
。
打家劫舍
需要考虑两种情况:
1.如果偷窃第i个房屋,那就不能偷窃第i-1个房屋,所以最大金额是dp[i-2]+nums[i](即偷窃到第i-2个房屋时的最大金额加上第i个房屋的金额)
2.如果不偷窃第i个房屋,那么最大金额就是偷窃到第i-1个房屋时的最大金额,即dp[i-1]
class Solution:
def rob(self, nums: List[int]) -> int:
f = [0] * (len(nums) + 2)
for i, x in enumerate(nums):
f[i+2] = max(f[i+1], f[i] + x)
return f[-1]
打家劫舍II
由于是环形的, 第一个和最后一个只能选一个,所以考虑两种情况:
class Solution:
def rob(self, nums: List[int]) -> int:
prev = nums[1:]
back = nums[:-1]
len1 = len(nums)-1
f1 = [0] * (len1+2)
f2 = [0] * (len1+2)
if len1 == 0:
return nums[0]
else:
for i, x in enumerate(prev):
f1[i+2] = max(f1[i+1], f1[i]+x)
for i, x in enumerate(back):
f2[i+2] = max(f2[i+1], f2[i]+x)
return f1[-1] if f1[-1]>f2[-1] else f2[-1]
有效的数独
请你判断一个 9 x 9
的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
# 检查行
for i in range(9):
row_set = set()
for j in range(9):
num = board[i][j]
if num != '.':
num = int(num)
if num in row_set:
return False
row_set.add(num)
# 检查列
for j in range(9):
col_set = set()
for i in range(9):
num = board[i][j]
if num != '.':
num = int(num)
if num in col_set:
return False
col_set.add(num)
# 检查九宫格
for block_i in range(3):
for block_j in range(3):
block_set = set()
for i in range(block_i * 3, block_i * 3 + 3):
for j in range(block_j * 3, block_j * 3 + 3):
num = board[i][j]
if num != '.':
num = int(num)
if num in block_set:
return False
block_set.add(num)
return True