链表与栈也是高频出现的面试题,这里将他们放在一篇讨论。
链表
链表最关键的在于边界条件的处理,这个只有在不断训练中熟悉与掌握。
[leetcode]24.Swap Nodes in Pairs
分别可以用递归和迭代来实现。对于迭代实现,还是需要建立dummy节点。要对头结点进行操作时,考虑创建哑节点dummy,使用dummy->next表示真正的头节点。这样可以避免处理头节点为空的边界问题。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def swapPairs(self, head: 'ListNode') -> 'ListNode':
if not head or not head.next:return head
new = head.next
head.next = self.swapPairs(new.next)
new.next = head
return new
[leetcode]25.Reverse Nodes in k-Group
递归。首先找到第k+1个节点,也就是这一段需要翻转的链表的尾部相连的那个节点。如果找不到第k+1个节点,说明这一段链表长度不足k,无需进行翻转。在找到第k+1个节点的情况下,首先递归求后面直接相连的链表翻转之后的的头结点。然后再将这个头结点之前的需要翻转的链表节点逐个重新连接,进行翻转。最终,返回翻转之后的链表的头结点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseKGroup(self, head: 'ListNode', k: 'int') -> 'ListNode':
# if not head or not head.next:return head
node = head
for i in range(k):
if not node:return head
node = node.next
new = self.reverse(head,node)
head.next = self.reverseKGroup(node,k)
return new
def reverse(self,start,end):
pre = end
while start!=end:
nextNode = start.next
start.next = pre
pre = start
start = nextNode
return pre
[leetcode]138.Copy List with Random Pointer
此题有两种方法,一种是按照原链表next的顺序依次创建节点,并处理好新链表的next指针,同时把原节点与新节点的对应关系保存到一个hash_map中,然后第二次循环将random指针处理好。这种方法的时间复杂度是O(n),空间复杂度也是O(n)。
第二种方法则是在原链表的每个节点之后插入一个新的节点,这样原节点与新节点的对应关系就已经明确了,因此不需要用hash_map保存,但是需要第三次循环将整个链表拆分成两个。这种方法的时间复杂度是O(n),空间复杂度是O(1)。
但是利用hash_map的方法具有其他的优点,如果在循环中加入一个判断,就可以检测出链表中是否有循环;而第二种方法则不行,会陷入死循环。
"""
# Definition for a Node.
class Node:
def __init__(self, val, next, random):
self.val = val
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:return
self.CloneNodes(head)
self.ConnectRandomNodes(head)
return self.ReconnectNodes(head)
def CloneNodes(self, pHead):
pNode = pHead
while pNode:
pCloned = Node(pNode.val,pNode.next,None)
# pCloned.random = None #不需要写这句话, 因为创建新的结点的时候,random自动指向None
pNode.next = pCloned
pNode = pCloned.next
# 将复制后的链表中的复制结点的random指针链接到被复制结点random指针的后一个结点
def ConnectRandomNodes(self, pHead):
pNode = pHead
while pNode:
pCloned = pNode.next
if pNode.random != None:
pCloned.random = pNode.random.next
pNode = pCloned.next
# 拆分链表, 将原始链表的结点组成新的链表, 复制结点组成复制后的链表
def ReconnectNodes(self, pHead):
pNode = pHead
pClonedHead = pClonedNode = pNode.next
pNode.next = pClonedHead.next
pNode = pNode.next
while pNode:
pClonedNode.next = pNode.next
pClonedNode = pClonedNode.next
pNode.next = pClonedNode.next
pNode = pNode.next
return pClonedHead
栈
栈除了解决计算器,括号优先级的问题,也常用于求左/右边第一个比选定索引大/小的问题,对于第二类问题,最常用的就算单调栈。
[leetcode]32.Longest Valid Parentheses
使用stack记录未匹配的括号位置。特别要注意当栈pop后为空的情况,即0-i满足条件时,res的更新。
class Solution(object):
def longestValidParentheses(self, s):
stack = []
res = 0
for i in range(len(s)):
if s[i] == ')' and stack!=[] and s[stack[-1]] == '(':
stack.pop()
if stack == []:res = i+1
else:res = max(res,i-stack[-1])
else:
stack.append(i)
return res
[leetcode]42.Trapping Rain Water
使用单调栈。我们对低洼的地方感兴趣,就可以使用一个单调递减栈,将递减的边界存进去,一旦发现当前的数字大于栈顶元素了,那么就有可能会有能装水的地方产生。此时我们当前的数字是右边界,我们从栈中至少需要有两个数字,才能形成一个坑槽,先取出的那个最小的数字,就是坑槽的最低点,再次取出的数字就是左边界,我们比较左右边界,取其中较小的值为装水的边界,然后此高度减去水槽最低点的高度,乘以左右边界间的距离就是装水量了。由于需要知道左右边界的位置,所以我们虽然维护的是递减栈,但是栈中数字并不是存递减的高度,而是递减的高度的坐标。
class Solution:
def trap(self, height):
stack = []
res = 0
for i in range(len(height)):
while stack and height[i]>height[stack[-1]]:
t = stack.pop()
if stack:
l = stack[-1]
h = min(height[i],height[l])-height[t]
w = i-l-1
res += w*h
stack.append(i)
return res
[leetcode]84.Largest Rectangle in Histogram
用栈来模拟,遍历heights数组,如果大于栈顶元素,就push进去;否则,持续弹栈来计算从栈顶点到降序点的矩阵大小。然后将这一部分全部替换为降序点的值,即做到了整体依然是有序非降的。
class Solution(object):
def largestRectangleArea(self, height):
"""
:type heights: List[int]
:rtype: int
"""
stack = [-1]
ans = 0
height.append(0)
for i in range(len(height)):
while height[stack[-1]] > height[i]: #若数组非递增
top = stack.pop()
w = i - (stack[-1]+1)
h = height[top]
ans = max(ans, w * h)
stack.append(i)
# height.pop()
return ans
[leetcode]224.Basic Calculator
对包括+,-,(,)的字符串,模拟计算器操作。
一般对于计算器的模拟,使用的都是栈的操作。这里主要用四步来做:
1.若为数字直接加到后面
2.若为'(',入符号栈
3.若为运算符,则将优先级大于等于它的运算符先弹出并记录带答案,再将其入栈,本题运算符只有+,-,优先级相同
4.若为')',弹出运算符直到遇到‘(’
class Solution:
def calculate(self, s: str) -> int:
res, num, sign = 0, 0, 1
stack = []
for c in s:
if c.isdigit():
num = 10 * num + int(c)
elif c == "+" or c == "-":
res = 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 = res + sign * num
num = 0
res *= stack.pop()
res += stack.pop()
res = res + sign * num
return res
[leetcode]227.Basic Calculator II
对包括+,-,*,/且包含正数的字符串,模拟计算器操作。
class Solution:
def calculate(self, s: str) -> int:
stack = []
pre_op = '+'
num = 0
for i, c in enumerate(s):
if c.isdigit():
num = 10 * num + int(c)
if i == len(s) - 1 or c in '+-*/':
if pre_op == '+':
stack.append(num)
elif pre_op == '-':
stack.append(-num)
elif pre_op == '*':
stack.append(stack.pop() * num)
elif pre_op == '/':
top = stack.pop()
stack.append(int(top / num))
pre_op = c
num = 0
return sum(stack)