面试手撕合集

82.删除排序链表中的重复元素II

定义单个指针 cur,指向虚拟头节点。如果 cur.next == cur.next.next,说明 cur 后面的两个节点重复,例如 节点2 后面存在 2个节点3。我们令 节点2 -> 节点4,实现删除两个节点3的操作。

class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:

        dummyhead = ListNode(val = 0, next = head)
        cur = dummyhead
        
        while cur.next and cur.next.next:
            if cur.next.val == cur.next.next.val:
                repeat_val = cur.next.val
                # 跳过某段重复
                while cur.next and cur.next.val == repeat_val:
                    cur.next = cur.next.next
            else:
                cur = cur.next
        return dummyhead.next

240.搜索二维矩阵II

利用矩阵的递增性质:从左下角元素开始搜索,同列元素都小于此元素,同行元素都大于此元素。因此和 target 比较后,我们可以立即排除一行或者一列的元素。如下图:从右上角开始同理,这是由于这两处的元素大小处在所在行和列的所有元素中间,可以利用二分思想缩减搜索规模。而左上和右下角的元素处在所在行和列的所有元素的两端。

 

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 起始点定在矩阵左下角
        i, j = len(matrix) - 1, 0
        
        while i >= 0 and j <= len(matrix[0]) - 1:
            if matrix[i][j] > target:
                i -= 1
            elif matrix[i][j] < target:
                j += 1
            else:
                return True
        return False

冒泡排序

  • 遍历数组,每次交换相邻两个元素,一次遍历后,最大的数将会被移至末尾。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
  • 时间复杂度为 O(n^2)
def bubble_sort(arr):
    n = len(arr)
    # 遍历所有数组元素
    for i in range(n):

        for j in range(0, n-i-1):
            # 遍历数组从0到n-i-1
            # 交换如果元素大于下一个元素
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
    return arr

快速排序

  • 选择基准值:从数组中选择一个元素作为基准值。选择方法有多种,如选择第一个元素、最后一个元素、中间元素等。

  • 分区操作:重新排列数组,使得所有小于基准值的元素都排在基准前面,而所有大于基准值的元素都排在基准的后面(相等的数可以到任一边)。在这个分区退出之后,该基准就处于数组的中间位置。

  • 递归排序:递归地将小于基准值的子数组和大于基准值的子数组排序。

  • 时间复杂度为O(n log n)

def quick_sort(arr):
    # 分区函数:根据pivot(基准值)将数组分为两部分
    def partition(left, right):
        pivot = arr[left]  # 选择左边界的元素作为pivot
        while left < right:
            # 从右向左扫描,找到第一个小于pivot的元素,移动到左边
            while left < right and arr[right] >= pivot:
                right -= 1
            arr[left] = arr[right]

            # 从左向右扫描,找到第一个大于pivot的元素,移动到右边
            while left < right and arr[left] <= pivot:
                left += 1
            arr[right] = arr[left]

        # 循环结束,left和right相遇,这是pivot的正确位置,将pivot放回
        arr[left] = pivot
        return left  # 返回pivot的位置

    # 递归排序的辅助函数
    def quick_sort_recursive(left, right):
        if left < right:  # 至少包含两个元素的区间才需要排序
            pi = partition(left, right)  # 对当前区间进行分区,得到pivot的位置
            quick_sort_recursive(left, pi - 1)  # 递归排序pivot左侧的子数组
            quick_sort_recursive(pi + 1, right)  # 递归排序pivot右侧的子数组

    quick_sort_recursive(0, len(arr) - 1)  # 对整个数组进行快速排序
    return arr  # 返回排序后的数组

538.把二叉搜索树转换为累加树 

累加树(Greater Sum Tree)常见于二叉搜索树(Binary Search Tree)的变体。在累加树中,每个节点的值被修改为原始二叉搜索树中所有大于或等于该节点值的节点值之和。

举例

有以下BST:

反向中序遍历(右中左)

我们遍历的顺序是:9 -> 8 -> 5 -> 4 -> 3 -> 2。遍历时,我们累计已经访问过的节点值的和,并将这个累积和赋给当前节点。

  • 访问节点 9:当前累积和 = 9,更新节点 9 的值为 9
  • 访问节点 8:当前累积和 = 9 + 8 = 17,更新节点 8 的值为 17
  • 访问节点 5:当前累积和 = 17 + 5 = 22,更新节点 5 的值为 22
  • 访问节点 4:当前累积和 = 22 + 4 = 26,更新节点 4 的值为 26
  • 访问节点 3:当前累积和 = 26 + 3 = 29,更新节点 3 的值为 29
  • 访问节点 2:当前累积和 = 29 + 2 = 31,更新节点 2 的值为 31

class Solution:
    def convertBST(self, root: Optional[TreeNode]) -> Optional[TreeNode]:

        self.sum = 0

        def dfs(node):
            # 递归终止条件
            if not node:
                return
            # 反向中序(右 -> 中 -> 左)
            # 右
            dfs(node.right)
            # 中
            self.sum += node.val
            node.val = self.sum  # 更新节点值
            # 左
            dfs(node.left)
        
        dfs(root)
        return root

1743.从相邻元素对还原数组

数组元素各不相同,有以下观察:

  • 每个元素(除了首尾元素)都会有两个相邻元素:前一个和后一个。
  • 数组的首元素 只有一个相邻元素,即它后面的元素。
  • 数组的尾元素 同样只有一个相邻元素,即它前面的元素。

因此,在从 adjacentPairs 构建的 adj_map 中,大多数键(代表 nums 中的元素)都会有两个条目在其对应的列表中,这两个条目分别表示它的前一个和后一个相邻元素。例外的是 nums 的首尾元素,它们在 adj_map 中的列表长度将是 1,因为它们各自只有一个相邻元素。

举例说明

考虑数组 nums = [1, 2, 3, 4],其相邻元素对可能包括:

  • [1, 2]
  • [2, 3]
  • [3, 4]

在通过这些对构建 adj_map 后,其内容将是:

  • 1 -> [2]
  • 2 -> [1, 3]
  • 3 -> [2, 4]
  • 4 -> [3]

由于顺序不限,我们随意选择其中一个长度为 1 的键作为起点,然后根据相邻关系依次添加元素到结果数组中,直到所有元素都被添加。

class Solution:
    def restoreArray(self, adjacentPairs: List[List[int]]) -> List[int]:
        n = len(adjacentPairs) + 1
        # 构建关系哈希表
        adj_map = defaultdict(list)
        for u, v in adjacentPairs:
            adj_map[u].append(v)
            adj_map[v].append(u)
        # 找到 nums 的起始元素
        for key, val in adj_map.items():
            if len(val) == 1:
                start = key
                break
        # 构建 nums
        nums = []
        nums.append(start)
        nums.append(adj_map[start][0])
        while len(nums) < n:
            cur_val = nums[-1]
            pre_val = nums[-2]
            for next_val in adj_map[cur_val]:
                # cur_val键对应的值包含cur_val的前一个和后一个元素
                # 对于cur_val的后一个元素的确定,我们需要避免重复选择cur_val的前一个元素(pre_val)
                if next_val != pre_val:
                    nums.append(next_val)
                else:
                    continue
        return nums

8.字符串转换整数(atoi)

本题的题意不是特别清晰,合法的字符串有如下特征:

  • 前置空格+正负号+数字+其他
  • 前置空格可有可无
  • 正负号可有可无,但如果有只能有一个
class Solution:
    def myAtoi(self, s: str) -> int:
        sign = 1
        index = 0
        res = 0
        n = len(s)
        # 跳过前导空格
        while index < n and s[index] == ' ':
            index += 1
        # 确定符号
        if index < n and (s[index] == '+' or s[index] == '-'):
            sign = 1 if s[index] == '+' else -1
            index += 1
        # 遍历数字
        while index < n and s[index].isdigit():
            # 字符串转整数
            res  = res * 10 + (ord(s[index]) - ord('0'))
            index += 1

            if res * sign < -2**31:
                return -2**31
            if res * sign > 2**31 - 1:
                return 2**31 - 1
        return res*sign if res else 0

932.漂亮数组

本题基于以下规则:

如果有一个漂亮数组,我们可以通过线性变换生成另一个漂亮数组。

在此规则下,对于每个漂亮数组 A[i],我们可以通过线性变换生成:

  • 偶数漂亮数组(2 * A[i])
  • 奇数漂亮数组(2 * A[i] - 1)

我们将两个数组拼接后获得 [偶漂亮数组 + 奇漂亮数组],对于题目要求的性质 2 * nums[k] == nums[i] + nums[j],𝑖 < 𝑘 < 𝑗。如果 nums[i] 与 nums[j] 分别来自偶数漂亮数组和奇数漂亮数组,由于 偶数 + 奇数 = 奇数,因此题目要求的性质也可以被满足。

我们以构造 n = 6 为例:

class Solution:
    def beautifulArray(self, n: int) -> List[int]:
        if n == 1:
            return [1]
        else:
            odd = self.beautifulArray((n + 1) // 2)
            even = self.beautifulArray(n // 2)

            return [2 * x - 1 for x in odd] + [2 * x for x in even]
面试写代码解决算法题是考察候选人编程能力和问题解决能力的重要环节。以下是针对写代码类算法题的常见解决方案和应对策略: ### 3.1 常见算法题类型及解决思路 #### 3.1.1 数组相关问题 数组是最基础的数据结构之一,常见的问题包括排序、查找、去重、统计等。例如冒泡排序、二分查找、两数之和、三数之和等。处理数组问题时,可以考虑使用双指针、哈希表等技巧来优化时间复杂度。 例如实现一个冒泡排序: ```javascript function bubbleSort(arr) { let n = arr.length; for (let i = 0; i < n - 1; i++) { for (let j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { let temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr; } ``` #### 3.1.2 字符串操作 字符串问题通常涉及格式化、分割、拼接、匹配等操作。例如实现数字千分位格式化函数,可以使用字符串操作结合正则表达式[^1]: ```javascript let format = n => { let num = n.toString(); let len = num.length; if (len <= 3) { return num; } else { let remainder = len % 3; if (remainder > 0) { return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(','); } else { return num.slice(0, len).match(/\d{3}/g).join(','); } } }; ``` #### 3.1.3 链表操作 链表问题通常涉及反转、查找中间节点、判断环等。处理链表问题时,常常使用快慢指针、递归等方法。 #### 3.1.4 树和图 树和图的问题通常包括遍历(前序、中序、后序)、查找路径、判断平衡等。对于树的倒置问题,可以使用递归或队列实现层次遍历[^3]: ```javascript function invertTree(root) { if (root === null) return null; let left = invertTree(root.left); let right = invertTree(root.right); root.left = right; root.right = left; return root; } ``` #### 3.1.5 动态规划 动态规划适用于最优化问题,如最长递增子序列、背包问题等。关键在于定义状态转移方程。 #### 3.1.6 递归与分治 递归常用于解决分治问题,例如快速排序、归并排序等。递归的关键在于定义终止条件和递归逻辑。 ### 3.2 写代码的注意事项 1. **边界条件处理**:例如空数组、只有一个元素的情况。 2. **代码简洁性**:避免冗余变量,合理使用内置函数(如 `slice`、`match`)。 3. **可读性**:命名清晰,适当注释。 4. **测试用例验证**:写完代码后应动验证几个测试用例。 ### 3.3 面试中常见的算法题示例 - **两数之和**:给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那两个整数,并返回它们的数组下标。 - **最长回文子串**:给定一个字符串 `s`,找到 `s` 中最长的回文子串。 - **合并两个有序链表**:将两个升序链表合并为一个新的升序链表。 - **二叉树的层序遍历**:从上到下按层访问二叉树的每个节点。 - **全排列**:给定一个没有重复数字的序列,返回其所有可能的全排列。 ### 3.4 提升写代码能力的建议 1. **多刷题**:通过 LeetCode、牛客网等平台练习常见算法题。 2. **模拟面试**:找朋友或使用在线工具模拟真实面试环境。 3. **总结模板**:归纳常见题型的解题模板,如双指针、动态规划等。 4. **理解原理**:不仅会写代码,还要理解算法的时间复杂度和空间复杂度。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值