一、数组
双指针,注意慢指针更新
二、链表
1.设置虚拟头结点(哨兵)dummy = ListNode(next=head)
2.滚动向前,两个指针分别指向node1 node2节点。然后每次
node1.next = node2.next node1 = node1.next,此时node1已更新到第三个node
node2.next = node1.next node2 = node2.next,此时node2已更新到第四个node
循环以上操作,完成滚动更新,最终将node1.next指向node2开始位置,即可。3.链表插新值:cur.next = ListNode(val)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is None :
return head
pre = dummy = ListNode(next=head)
while pre.next and pre.next.next:
first,second = pre.next,pre.next.next
pre.next,first.next = second,second.next
second.next = first
pre = pre.next.next
return dummy.next
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为0
listA
- 第一个链表listB
- 第二个链表skipA
- 在listA
中(从头节点开始)跳到交叉节点的节点数skipB
- 在listB
中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
解决idea
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
A, B = headA, headB
while A != B:
A = A.next if A else headB
B = B.next if B else headA
return A
给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5]
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
p0 = dummy = ListNode(next=head)
for _ in range(left-1):
p0 = p0.next
pre = None
cur = p0.next
for _ in range(right-left+1):
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
p0.next.next = cur
p0.next = pre
return dummy.next
最后循环结束后,p0.next指向2,再next(2.next)是指把尾巴续上
p0.next就是指向翻转后链表的pre。
三、栈
Python 中的栈直接以列表为主,增加使用append(),出栈为pop()
实现栈的思路分析:
- 使用数组来记性模拟栈
- 定义一个top来代表栈顶,初始化-1
- 入栈的操作,当有数据加入的时候,top++,stack[top] = data
- 出栈的操作,value = stack[top]
最小栈
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
MinStack()
初始化堆栈对象。void push(int val)
将元素val推入堆栈。void pop()
删除堆栈顶部的元素。int top()
获取堆栈顶部的元素。int getMin()
获取堆栈中的最小元素。
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, x: int) -> None:
self.stack.append(x)
if not self.min_stack or x <= self.min_stack[-1]:
self.min_stack.append(x)
def pop(self) -> None:
if self.stack.pop() == self.min_stack[-1]:
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
题目要求在常数时间内获得栈中的最小值,因此不能在 getMin() 的时候再去计算最小值,最好应该在 push 或者 pop 的时候就已经计算好了当前栈中的最小值。因此可以用一个栈,这个栈同时保存的是每个数字 x 进栈的时候的值 与 插入该值后的栈内最小值。即每次新元素 x 入栈的时候保存一个元组:(当前值 x,栈内最小值)。
这个元组是一个整体,同时进栈和出栈。即栈顶同时有值和栈内最小值,top()函数是获取栈顶的当前值,即栈顶元组的第一个值; getMin() 函数是获取栈内最小值,即栈顶元组的第二个值;pop() 函数时删除栈顶的元组。
每次新元素入栈时,要求新的栈内最小值:比较当前新插入元素 x 和 当前栈内最小值(即栈顶元组的第二个值)的大小。
新元素入栈:当栈为空,直接保存元组 (x, x);当栈不空,保存元组 (x, min(此前栈内最小值, x)))
出栈:删除栈顶的元组。
class MinStack(object):
def __init__(self):
self.stack = []
def push(self, x):
if not self.stack:
self.stack.append((x, x))
else:
self.stack.append((x, min(x, self.stack[-1][1])))
def pop(self):
self.stack.pop()
def top(self):
return self.stack[-1][0]
def getMin(self):
return self.stack[-1][1]
给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
class Solution:
def longestValidParentheses(self, s: str) -> int:
if not s: return 0
stack = []
ans = 0
for i in range(len(s)):
# 入栈条件
if not stack or s[i] == '(' or s[stack[-1]] == ')':
stack.append(i) # 下标入栈
else:
# 计算结果
stack.pop()
ans = max(ans, i - (stack[-1] if stack else -1))
return ans
给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()
。
preSign的作用:判断某个数的正负(括号内的求和结果,也可以看作是某个数)
什么时候数字入栈:
1.遇到")",弹出栈顶数字做求和运算后,再入栈
2.遇到"+"或"-",把当前数字加入栈顶
3.遍历到最后一位,无论是什么字符(空格,右括号或数字),当前数字num都要入栈
注意一:数字入栈的时候一定要根据preSign判断正负后,再入栈
注意二:数字入栈之后,需要把num重置为0(对于情况3而言可以不重置)
"+"或"-"符号何时会出现:
1.在左括号前:储存该符号至preSign中,且需要入栈,当遇到右括号时,将栈顶至该符号后的数字进行求和计算
2.在数字前:储存该符号至preSign中,
何时更新preSign:使用后立即更新
1.遇到左括号,把该preSign加入栈中,待会儿遇到右括号的时候会用到(这等价于已经被使用了),更新为默认的"+"
2.遇到下一个"+"或"-",此时需要把num加入栈中,preSign被使用后,更新
class Solution:
def calculate(self, s: str) -> int:
preSign = "+"
stack = list()
num = 0
for i, ch in enumerate(s):
if (ch == " " and i != len(s)-1):
continue
elif ch == "(":
stack.append(preSign)
preSign = "+" # 更新preSign情况1
elif ch == ")":
cur_sum = (num if preSign == "+" else -num) # 数字入栈情况1
while(type(stack[-1]) is int): # 对栈顶元素做求和
cur_sum += stack.pop()
stack.append(cur_sum if stack.pop() == "+" else -cur_sum)
num = 0 # 重置num情况1
elif ch.isdigit():
num = num*10 + int(ch)
elif ch == "+" or ch == "-":
stack.append(num if preSign == "+" else -num) # 数字入栈情况2
num = 0 # 重置num情况2
preSign = ch # 更新preSign情况2
stack.append(num if preSign == "+" else -num) # 数字入栈情况3
return sum(stack)
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string]
,表示其中方括号内部的 encoded_string
正好重复 k
次。注意 k
保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k
,例如不会出现像 3a
或 2[4]
的输入。
示例 1:输入:s = "3[a]2[bc]" 输出:"aaabcbc"
示例 2:输入:s = "3[a2[c]]" 输出:"accaccacc"
示例 3:输入:s = "2[abc]3[cd]ef" 输出:"abcabccdcdcdef"
示例 4:输入:s = "abc3[cd]xyz" 输出:"abccdcdcdxyz"
算法流程:
1.构建辅助栈 stack, 遍历字符串 s 中每个字符 c;
- 当 c 为数字时,将数字字符转化为数字 multi,用于后续倍数计算;
- 当 c 为字母时,在 res 尾部添加 c;
- 当 c 为 [ 时,将当前 multi 和 res 入栈,并分别置空置 0:
- 记录此 [ 前的临时结果 res 至栈,用于发现对应 ] 后的拼接操作;
- 记录此 [ 前的倍数 multi 至栈,用于发现对应 ] 后,获取 multi × [...] 字符串。
- 进入到新 [ 后,res 和 multi 重新记录。
- 当 c 为 ] 时,stack 出栈,拼接字符串 res = last_res + cur_multi * res,其中:
- last_res是上个 [ 到当前 [ 的字符串,例如 "3[a2[c]]" 中的 a;
- cur_multi是当前 [ 到 ] 内字符串的重复倍数,例如 "3[a2[c]]" 中的 2。
2.返回字符串 res。
class Solution:
def decodeString(self, s: str) -> str:
stack, res, multi = [], "", 0
for c in s:
if c == '[':
stack.append([multi, res])
res, multi = "", 0
elif c == ']':
cur_multi, last_res = stack.pop()
res = last_res + cur_multi * res
elif '0' <= c <= '9':
multi = multi * 10 + int(c)
else:
res += c
return res
四、单调栈
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
每个位置能接的水取决你他左右两边的高,因此找出它左右两边的最大值,然后减去它本身的高度即可知道当前位置可以盛多少水。
算法流程:首先分别计算每个位置的前后高度最大值,之后计算获取每个位置的前后高度中的最小值,然后减去当前位置高度就可以得到当前位置的盛水量
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
pre_max = [0] * n
pre_max[0] = height[0]
for i in range(1, n):
pre_max[i] = max(pre_max[i - 1], height[i])
suf_max = [0] * n
suf_max[-1] = height[-1]
for i in range(n - 2, -1, -1):
suf_max[i] = max(suf_max[i + 1], height[i])
ans = 0
for h, pre, suf in zip(height, pre_max, suf_max):
ans += min(pre, suf) - h
return ans
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
输入:heights = [2,1,5,6,2,3] 输出:10
依次找出当前位置左右的一个小于它的值,然后
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
res = 0
n = len(heights)
for i in range(n):
left_i = i
right_i = i
while left_i >= 0 and heights[left_i] >= heights[i]:
left_i -= 1
while right_i < n and heights[right_i] >= heights[i]:
right_i += 1
res = max(res, (right_i - left_i - 1) * heights[i])
return res
'''单调栈'''
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
stack = []
heights = [0] + heights + [0]
res = 0
for i in range(len(heights)):
#print(stack)
while stack and heights[stack[-1]] > heights[i]:
tmp = stack.pop()
res = max(res, (i - stack[-1] - 1) * heights[tmp])
stack.append(i)
return res
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中 answer[i]
是指对于第 i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0
来代替。
输入: temperatures= [73,74,75,71,69,72,76,73] 输出: [1,1,4,2,1,1,0,0]
当stack为空时,运行stack.append(idx),则stack=[0];然后仅当遍历元素 t 小于stack顶端的值时append进去,这会导致stack中idx代表的元素是单调递减的,如果此时遍历到一个 t,大于stack顶端的值,那这个t就是离stack顶端值最近的那个大值。然后pop出来,还是要注意stack.pop出来的是idx,这样res这一串0里对应位置的0就会被替换成应有的值。 再进入while循环判断t和stack.pop后的新的顶端值哪个大。
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
l = len(T)
stack = []
res = [0] * l
for idx, t in enumerate(T):
while stack and t > T[stack[-1]]:
res[stack.pop()] = idx-stack[-1]
stack.append(idx)
return res
五、队列、优先队列
栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
class MyQueue(object):
def __init__(self):
self.stack1 = [] # 输入栈
self.stack2 = [] # 输出栈
def push(self, x):
self.stack1.append(x) # 入栈
def pop(self):
if not self.stack2:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
def peek(self):
if not self.stack2:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2[-1]
def empty(self):
return not self.stack1 and not self.stack2
双端队列
设计实现双端队列。
实现 MyCircularDeque
类:
MyCircularDeque(int k)
:构造函数,双端队列最大为k
。boolean insertFront()
:将一个元素添加到双端队列头部。 如果操作成功返回true
,否则返回false
。boolean insertLast()
:将一个元素添加到双端队列尾部。如果操作成功返回true
,否则返回false
。boolean deleteFront()
:从双端队列头部删除一个元素。 如果操作成功返回true
,否则返回false
。boolean deleteLast()
:从双端队列尾部删除一个元素。如果操作成功返回true
,否则返回false
。int getFront()
):从双端队列头部获得一个元素。如果双端队列为空,返回-1
。int getRear()
:获得双端队列的最后一个元素。 如果双端队列为空,返回-1
。boolean isEmpty()
:若双端队列为空,则返回true
,否则返回false
。boolean isFull()
:若双端队列满了,则返回true
,否则返回false
。
链表
class MyCircularDeque:
def __init__(self, k: int):
self.k = k
self.len = 0
self.head = LinkedNode(-1)
self.head.next = self.head.prev = self.head
def insertFront(self, value: int) -> bool:
if self.len == self.k:
return False
self.head.next = LinkedNode(value, self.head, self.head.next)
self.head.next.next.prev = self.head.next # 新建的节点成为了头节点的下一个节点,而原先的头节点成为了新建节点的前一个节点,从而完成了节点的插入操作。
self.len += 1
return True
def insertLast(self, value: int) -> bool:
if self.len == self.k:
return False
self.head.prev = LinkedNode(value, self.head.prev, self.head)
self.head.prev.prev.next = self.head.prev # 新建的节点成为了头节点的前一个节点,而原先的头节点成为了新建节点的后一个节点,从而完成了节点的插入操作。
self.len += 1
return True
def deleteFront(self) -> bool:
if not self.len:
return False
self.head.next = self.head.next.next
self.head.next.prev = self.head
self.len -= 1
return True
def deleteLast(self) -> bool:
if not self.len:
return False
self.head.prev = self.head.prev.prev
self.head.prev.next = self.head
self.len -= 1
return True
def getFront(self) -> int:
return -1 if not self.len else self.head.next.val
def getRear(self) -> int:
return -1 if not self.len else self.head.prev.val
def isEmpty(self) -> bool:
return self.len == 0
def isFull(self) -> bool:
return self.len == self.k
# Your MyCircularDeque object will be instantiated and called as such:
# obj = MyCircularDeque(k)
# param_1 = obj.insertFront(value)
# param_2 = obj.insertLast(value)
# param_3 = obj.deleteFront()
# param_4 = obj.deleteLast()
# param_5 = obj.getFront()
# param_6 = obj.getRear()
# param_7 = obj.isEmpty()
# param_8 = obj.isFull()
class LinkedNode:
def __init__(self, val, prev=None, next=None):
self.val = val
self.prev = prev
self.next = next
列表
class MyCircularDeque:
def __init__(self, k: int):
self.lst = list()
self.k = k
def insertFront(self, value: int) -> bool:
if len(self.lst) < self.k:
self.lst.insert(0, value)
return True
return False
def insertLast(self, value: int) -> bool:
if len(self.lst) < self.k:
self.lst.append(value)
return True
return False
def deleteFront(self) -> bool:
if not self.lst:
return False
self.lst.pop(0)
return True
def deleteLast(self) -> bool:
if not self.lst:
return False
self.lst.pop(-1)
return True
def getFront(self) -> int:
if not self.lst:
return -1
return self.lst[0]
def getRear(self) -> int:
if not self.lst:
return -1
return self.lst[-1]
def isEmpty(self) -> bool:
return len(self.lst) == 0
def isFull(self) -> bool:
return len(self.lst) == self.k
六、哈希表和哈希集合
哈希集合
- hash = [ ]
- hash.git(i) # 查看是否在哈希集合中
- hash[i] = 1 # 把值加入到哈希集合中
把值存入哈希集合中
哈希表
- hash = { }
- hash[num[i]] = i # 值:索引
把 值 和 索引 存入哈希表中
res = {} for a in s: if a not in res: res[a] = 1 else: res[a] = res[a] + 1
frequency = collections.Counter(s)m # 统计字符个数 for i, ch in enumerate(s): # i 顺序 ch是字符 frequency[ch]次数
七、前缀和
给定一个整数数组 nums
,处理以下类型的多个查询:
- 计算索引
left
和right
(包含left
和right
)之间的nums
元素的 和 ,其中left <= right
实现 NumArray
类:
NumArray(int[] nums)
使用数组nums
初始化对象int sumRange(int i, int j)
返回数组nums
中索引left
和right
之间的元素的 总和 ,包含left
和right
两点(也就是nums[left] + nums[left + 1] + ... + nums[right]
)
class NumArray:
def __init__(self, nums: List[int]):
N = len(nums)
self.preSum = [0] * (N + 1)
for i in range(N):
self.preSum[i + 1] = self.preSum[i] + nums[i]
def sumRange(self, i: int, j: int) -> int:
return self.preSum[j + 1] - self.preSum[i]
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(left,right)
给你一个正整数数组 arr
,请你计算所有可能的奇数长度子数组的和。
子数组 定义为原数组中的一个连续子序列。
请你返回 arr
中 所有奇数长度子数组的和 。
# 注:求某段区间和时常用前缀和数组,这里只不过是只求奇数部分,故设k为奇数跨度,分别求1,3,5,…,n的区间和
class Solution:
def sumOddLengthSubarrays(self, arr: List[int]) -> int:
nums=[0]+list(accumulate(arr)) # 前缀和数组 accumulate函数对一个序列进行累加操作,并返回一个迭代器。因此,使用list()函数将返回的迭代器转化为列表类型。
n=len(nums)
res=0
k=1 # 奇数跨度:从1开始
while k<n:
for i in range(k,n):
res=res+nums[i]-nums[i-k] # 求相应的奇数长度数组和并累加
k=k+2
return res
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的连续子数组的个数 。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
dic={0:1}
sums,res=0,0
for num in nums:
sums+=num
res+=dic.get(sums-k,0)
dic[sums]=dic.get(sums,0)+1
return res
# 等价
# for i in range(len(nums)):
# summ += nums[i]
# if summ-k in dic.keys():
# res += dic[summ-k]
# if summ not in dic.keys():
# dic[summ] = 1
# else:
# dic[summ] += 1
# return res
利用字典存储前缀和以及对应个数。枚举前缀和数组,当枚举到sums[j]时,假设有键值对
sums[j]−k:v,可知存在v个(ai ,j)满足要求,把v加入最终答案即可,并将sums[i]更新到字典当中。
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
table = {}
for s in strs:
s_ = "".join(sorted(s))
if s_ not in table:
table[s_] = [s]
else:
table[s_].append(s)
return list(table.values())
238. 除自身以外数组的乘积
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请不要使用除法,且在 O(n)
时间复杂度内完成此题。
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
原数组: [1 2 3 4]
左部分的乘积: 1 1 1*2 1*2*3
右部分的乘积: 2*3*4 3*4 4 1
结果: 1*2*3*4 1*3*4 1*2*4 1*2*3*1
个人理解:当前位置 = 左边乘积*右边乘积。首先res依次计算前n-1项的累乘(左边乘积),然后从最后开始依次取数进行累乘(右边),
class Solution:
def productExceptSelf(self, nums: [int]) -> [int]:
res, p, q = [1], 1, 1
for i in range(len(nums) - 1): # bottom triangle
p *= nums[i]
res.append(p) # 等于计算当前数字左边的乘积
for i in range(len(nums) - 1, 0, -1): # top triangle
q *= nums[i] # 从右边依次取数累乘,等于计算当前数字右边乘积
res[i - 1] *= q # 当前乘积 = 左边 * 右边
return res
class Solution:
def constructArr(self, a: List[int]) -> List[int]:
b, tmp = [1] * len(a), 1
for i in range(1, len(a)):
b[i] = b[i - 1] * a[i - 1] # 下三角
for i in range(len(a) - 2, -1, -1):
tmp *= a[i + 1] # 上三角
b[i] *= tmp # 下三角 * 上三角
return b
304. 二维区域和检索 - 矩阵不可变
给定一个二维矩阵 matrix
,以下类型的多个请求:
- 计算其子矩形范围内元素的总和,该子矩阵的 左上角 为
(row1, col1)
,右下角 为(row2, col2)
。
实现 NumMatrix
类:
NumMatrix(int[][] matrix)
给定整数矩阵matrix
进行初始化int sumRegion(int row1, int col1, int row2, int col2)
返回 左上角(row1, col1)
、右下角(row2, col2)
所描述的子矩阵的元素 总和 。
class NumMatrix:
def __init__(self, matrix: List[List[int]]):
if not matrix or not matrix[0]:
M, N = 0, 0
else:
M, N = len(matrix), len(matrix[0])
self.preSum = [[0] * (N + 1) for _ in range(M + 1)]
for i in range(M):
for j in range(N):
self.preSum[i + 1][j + 1] = self.preSum[i][j + 1] + self.preSum[i + 1][j] - self.preSum[i][j] + matrix[i][j]
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
return self.preSum[row2 + 1][col2 + 1] - self.preSum[row2 + 1][col1] - self.preSum[row1][col2 + 1] + self.preSum[row1][col1]