📜  常见数据结构面试问题套装1(1)

📅  最后修改于: 2023-12-03 15:09:46.320000             🧑  作者: Mango

常见数据结构面试问题套装1

在程序员面试中,常会遇到有关数据结构的问题,因此,针对常见的数据结构面试问题,本篇文章将为大家整理一套面试问题套装,希望对大家在面试中有所帮助。

数组
问题1:如何在数组中查找一个元素?

可以使用线性搜索或二分查找。线性搜索是在数组中依次遍历查找,时间复杂度为 O(n)。二分查找则是通过将数组分成左右两个部分,每次比较中间值与目标值,并且根据结果调整查询方向,时间复杂度为 O(log n)。

def linear_search(arr, target):
	for i in range(len(arr)):
		if arr[i] == target:
			return i
	return -1

def binary_search(arr, target):
	left, right = 0, len(arr) - 1
	while left <= right:
		mid = left + (right - left) // 2
		if arr[mid] == target:
			return mid
		elif arr[mid] < target:
			left = mid + 1
		else:
			right = mid - 1
	return -1

问题2:如何对一个已排序的数组进行去重?

可以通过双指针法来实现。设置两个指针 p 和 q,p 用于指向上一个不重复的元素,q 用于指向当前正在遍历的元素。遍历过程中,若 arr[p] == arr[q],则将 q 向后移动;否则将 arr[p+1] 的值修改为 arr[q],并将 p 向后移动。最终,数组中前 p+1 个元素即为不重复元素。

def remove_duplicates(arr):
	if not arr:
		return 0
	p = 0
	for q in range(1, len(arr)):
		if arr[p] != arr[q]:
			p += 1
			arr[p] = arr[q]
	return p + 1
问题3:如何将一个数组反转?

可以通过双指针法来实现。设置一个指针 left,一个指针 right,分别指向数组的首尾元素。遍历过程中,不断交换 left 和 right 所指向的元素,并将 left 后移,将 right 前移。直到 left ≥ right。

def reverse_arr(arr):
	left, right = 0, len(arr) - 1
	while left < right:
		arr[left], arr[right] = arr[right], arr[left]
		left += 1
		right -= 1
链表
问题1:如何反转一个单链表?

可以通过设置三个指针来实现,分别指向前驱节点、当前节点和后继节点。遍历过程中,将当前节点的指针指向前驱节点,然后将三个指针向后移动。

def reverse_list(head):
	prev, curr = None, head
	while curr:
		tmp = curr.next
		curr.next = prev
		prev = curr
		curr = tmp
	return prev
问题2:如何判断一个单链表是否有环?

可以用快慢指针法。设置两个指针 slow 和 fast,初始时都指向链表头节点。慢指针每次向后移动一步,快指针每次向后移动两步。如果链表有环,则快指针最终会追上慢指针。

def has_cycle(head):
	if not head or not head.next:
		return False
	slow, fast = head, head.next
	while slow != fast:
		if not fast or not fast.next:
			return False
		slow = slow.next
		fast = fast.next.next
	return True
问题3:如何删除一个链表的倒数第 n 个节点?

可以用双指针法。设置两个指针 p1 和 p2,初始时都指向链表头节点。p1 先移动 n 个位置,然后 p1 和 p2 同时向后移动,直到 p1 到达链表尾部。此时,p2 所指向的节点即为要删除的节点。

def remove_nth_from_end(head, n):
	if not head:
		return None
	dummy = ListNode(0, head)
	p1, p2 = dummy, head
	for i in range(n):
		if not p1:
			return None
		p1 = p1.next
	while p1.next:
		p1 = p1.next
		p2 = p2.next
	p2.next = p2.next.next
	return dummy.next
堆栈和队列
问题1:如何使用栈进行括号匹配?

可以使用栈来进行括号匹配。遍历表达式,当遇到左括号时压栈,当遇到右括号时弹出栈顶元素,并检查其是否与当前右括号匹配。

def is_valid_parentheses(s):
	stack, match = [], {')': '(', ']': '[', '}': '{'}
	for ch in s:
		if ch in match:
			if not stack or match[ch] != stack[-1]:
				return False
			stack.pop()
		else:
			stack.append(ch)
	return not stack
问题2:如何使用队列实现栈?

可以使用两个队列来实现一个栈。每次插入元素时,将其加入到非空队列中;每次弹出元素时,将非空队列中的所有元素移动到另一个空队列中,直到队列中只剩下一个元素为止。

class MyStack:

	def __init__(self):
		self.q1 = collections.deque()
		self.q2 = collections.deque()

	def push(self, x: int) -> None:
		if not self.q1:
			self.q1.append(x)
			while self.q2:
				self.q1.append(self.q2.popleft())
		else:
			self.q2.append(x)
			while self.q1:
				self.q2.append(self.q1.popleft())

	def pop(self) -> int:
		if self.q1:
			return self.q1.popleft()
		else:
			return self.q2.popleft()

	def top(self) -> int:
		if self.q1:
			return self.q1[0]
		else:
			return self.q2[0]

	def empty(self) -> bool:
		return not self.q1 and not self.q2
问题3:如何使用栈实现队列?

可以使用两个栈来实现一个队列。每次插入元素时,将其加入到栈 1 中;每次弹出元素时,先检查栈 2 是否为空,若不为空,则弹出栈 2 的栈顶元素,否则,将栈 1 中的所有元素弹出并加入到栈 2 中,再弹出栈 2 的栈顶元素。

class MyQueue:

	def __init__(self):
		self.stack1 = []
		self.stack2 = []

	def push(self, x: int) -> None:
		self.stack1.append(x)

	def pop(self) -> int:
		if not self.stack2:
			while self.stack1:
				self.stack2.append(self.stack1.pop())
		return self.stack2.pop()

	def peek(self) -> int:
		if not self.stack2:
			while self.stack1:
				self.stack2.append(self.stack1.pop())
		return self.stack2[-1]

	def empty(self) -> bool:
		return not self.stack1 and not self.stack2
问题1:如何遍历一棵二叉树?

可以使用递归法或迭代法遍历二叉树。其中,一棵二叉树可以分别进行先序遍历、中序遍历和后序遍历。

class TreeNode:
	def __init__(self, val=0, left=None, right=None):
		self.val = val
		self.left = left
		self.right = right

def preorder_traversal(root: TreeNode):
	if not root:
		return []
	return [root.val] + preorder_traversal(root.left) + preorder_traversal(root.right)

def inorder_traversal(root: TreeNode):
	if not root:
		return []
	return inorder_traversal(root.left) + [root.val] + inorder_traversal(root.right)

def postorder_traversal(root: TreeNode):
	if not root:
		return []
	return postorder_traversal(root.left) + postorder_traversal(root.right) + [root.val]
问题2:如何判断一棵二叉树是否为平衡二叉树?

平衡二叉树的特点是,其任意节点的两个子树的高度差不超过 1。因此,可以采用递归计算左子树和右子树的高度,并判断其高度差的绝对值是否超过 1。若超过,该二叉树即为不平衡二叉树。

def is_balanced(root: TreeNode) -> bool:
	def helper(node):
		if not node:
			return 0
		left = helper(node.left)
		if left == -1:
			return -1
		right = helper(node.right)
		if right == -1:
			return -1
		return max(left, right) + 1 if abs(left - right) <= 1 else -1

	return helper(root) != -1
问题3:如何在一棵二叉树中找到指定和的路径?

可以采用深度优先搜索(DFS)遍历二叉树。当访问到一个节点时,将当前节点的值加入路径中,并计算路径中所有节点的和。如果该节点是叶子节点,并且路径中所有节点的和等于指定和,则将该路径加入到结果列表中。否则,遍历当前节点的左子树和右子树。

def path_sum(root: TreeNode, target: int) -> List[List[int]]:
	def dfs(node, path, sum):
		if not node:
			return
		sum += node.val
		path.append(node.val)
		if not node.left and not node.right:
			if sum == target:
				res.append(path[:])
		else:
			dfs(node.left, path, sum)
			dfs(node.right, path, sum)
		path.pop()

	res = []
	dfs(root, [], 0)
	return res