链表与栈也是高频出现的面试题,这里将他们放在一篇讨论。
链表
链表最关键的在于边界条件的处理,这个只有在不断训练中熟悉与掌握。
[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)