面试遇到的代码题

1. 遇到的题

69. x 的平方根

【快手日常一面,腾讯暑期二面】
在这里插入图片描述

class Solution:
    def mySqrt(self, x: int) -> int:
        l = 0
        r = x
        ans = -1

        while l <= r:
            mid = l + r >> 1
            if mid * mid <= x:
                ans = mid
                l = mid + 1
            else:
                r = mid - 1
            
        return ans

152.乘积最大子数组

【美团暑期一面】
在这里插入图片描述

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        n = len(nums)
        f_min = [0] * n  # f_min[i], 从nums 0~i 能取到的最小乘积
        f_max = [0] * n  # f_max[i], 从nums 0~i 能取到的最大乘积

        f_min[0] = f_max[0] = nums[0]

        for i in range(1, n):
            x = nums[i]
            f_max[i] = max(x, f_max[i-1] * x, f_min[i-1] * x)
            f_min[i] = min(x, f_max[i-1] * x, f_min[i-1] * x)
        
        return max(f_max)

5. 最长回文子串

【阿里国际暑期一面】
在这里插入图片描述

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        left, right = 0, 0

        # 奇数回文串
        for i in range(n):
            l, r = i, i
            while l >= 0 and r < n and s[l] == s[r]:
                l -= 1 
                r += 1
            if l + 1 <= r - 1:  # 循环终止前的状态
                if r - l - 2 > right - left:  # 长度大于历史长度
                    left, right = l + 1, r - 1
        
        # 偶数回文串
        for i in range(n-1):
            l, r = i, i + 1
            while l >= 0 and r < n and s[l] == s[r]:
                l -= 1
                r += 1
            if l + 1 <= r -1:
                if r - l - 2 > right - left:
                    left, right = l + 1, r - 1

        return s[left:right+1]

6.Z字变换

【字节暑期一面】
在这里插入图片描述

  • 顺序遍历字符串,然后把字符串存到对应层中
  • 第一次,按 0 -> numRows - 1 的顺序
  • 遇到 0 或 numRows - 1 需要翻转,numRows - 1 -> 1
  • 最终存储的顺序为:0,1,2, 3, 2, 1, 0, 1, 2, 3
    • 保证 i 的变换顺序是这样子
class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows < 2:
            return s 

        res = ["" for i in range(numRows)]

        i = 0
        flag = -1

        for c in s:
            res[i] += c
            if i == 0 or i == numRows - 1:
                flag = -flag
            i += flag
        
        return "".join(res)

17.09. 第 k 个数

【字节暑期一面】
在这里插入图片描述

在这里插入图片描述

维护一个堆,每次取出最小的元素来乘 3,5,7 获取新元素加到堆中

import heapq
class Solution:
    def getKthMagicNumber(self, k: int) -> int:
        primes = [3, 5, 7]
        arr = [1]
        seen = {1}

        for _ in range(k):
            # 取出堆顶(最小堆)
            cur = heapq.heappop(arr)

            for p in primes:
                num = p * cur
                if num not in seen:
                    seen.add(num)
                    heapq.heappush(arr, num)
            
        return cur

20. 有效的括号

【阿里云暑期一面】
在这里插入图片描述

        d = {')':'(', ']':'[', '}':'{'}
        stack = []
        for i in s:
            # 遇到左括号,入栈
            if i in '([{':
                stack.append(i)
            # 遇到右括号,出栈左括号,看是否有匹配的左括号
            else:
                if not stack:  # 栈空的时候遇到右括号,则一定不能匹配
                    return False
                if d[i] == stack[-1]:  # 右括号与前面的左括号匹配
                    stack.pop()
                else:
                    return False

        if not stack:  # 栈为空, 说明全部都匹配成功
            return True
        return False

91. 解码方法

【快手x-star 一面】
a,b,c,d……z,对应 1,2,3,4……26。求:输入一串数字能有几种组合数?

  • f[i] 表示 0~i 个字符解码的组合数
  • 对于 s[i],我们只关心:(1)能否独立解码(2)能否和 s[i-1] 一起解码
  • 于是就存在有三种情况
    1. s[i] 只能单独解码为 a ∈ \in [1, 9],f[i] = f[i-1]
    2. s[i] 只能和 i-1 组合起来解码为 b ∈ \in [10, 26],f[i] = f[i-2]
    3. s[i] 可以单独解码也可以组合解码,f[i] = f[i-1] + f[i-2]
      在这里插入图片描述
  • 最后初始条件:f = [1] + [0] * n
class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        f = [1] + [0] * n

        for i in range(n):
            # 1.可以单独解码
            if 1 <= int(s[i]) <= 9:
                f[i+1] += f[i]

            # 2.如果大于两个数 且 可以和 i-1 组合解码
            if i >= 1 and 10 <= int(s[i-1:i+1]) <= 26:
                f[i+1] += f[i-1]
            
        return f[n]

2. 高频题

215. 数组中的第K个最大元素

要求 O(n),使用快排
设 N 为数组长度。根据快速排序原理,如果某次哨兵划分后,基准数的索引正好是 N−k ,则意味着它就是第 k 大的数字
在这里插入图片描述

# 已经确定的 topn 的元素数少于k个, 说明第 k 大的元素在 small 区间中
 if len(big) + len(equal) < k:
     # 前 len(big) + len(equal) 大的元素已经确定了
     # 在 small 区间只需要确定 k - (len(big) + len(equal))
     return quick_sort(small, k - (len(big) + len(equal)))
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:

        def quick_sort(arr, k):
            # 随机选择基准
            privot = random.choice(arr)
            big, equal, small = [], [], []  # >, =, < 基准的元素

            for x in arr:
                if x > privot:
                    big.append(x)
                elif x < privot:
                    small.append(x)
                else:
                    equal.append(x)
            
            # 说明第 k 大的元素在 big 区间中
            if k <= len(big):
                return quick_sort(big, k)
            
            # 已经确定的最大的元素数少于k个, 说明第 k 大的元素在 small 区间中
            if len(big) + len(equal) < k:
                # 前 len(big) + len(equal) 大的元素已经确定了
                # 在 small 区间只需要确定 k - (len(big) + len(equal))
                return quick_sort(small, k - (len(big) + len(equal)))
            
            # 如果不在 big 也不在 small, 那就在 equal
            return privot
        
        return quick_sort(nums, k)

求最小的k个数

import random
from typing import List

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def quick_select(arr, k):
            if k == 0 or not arr:
                return []
            
            pivot = random.choice(arr)
            less, equal, greater = [], [], []

            for num in arr:
                if num < pivot:
                    less.append(num)
                elif num > pivot:
                    greater.append(num)
                else:
                    equal.append(num)

            if k <= len(less):
                return quick_select(less, k)
            elif k <= len(less) + len(equal):
                return less + equal[:k - len(less)]
            else:
                return less + equal + quick_select(greater, k - len(less) - len(equal))

        result = quick_select(arr, k)
        return result

15. 三数之和

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # 给定 i, 然后[j, k]在剩余区间求 两数之和
        # 且不包含重复三元组
        nums = sorted(nums)

        ans = []
        n = len(nums)

        for i in range(n - 2):
            if i > 0 and nums[i] == nums[i-1]:
                continue

            target = -nums[i]

            j = i + 1
            k = n - 1
            
            while j < k:
                if nums[j] + nums[k] < target:
                    j += 1
                elif nums[j] + nums[k] > target:
                    k -= 1
                else:
                    ans.append([nums[i], nums[j], nums[k]])
                    j += 1
                    k -= 1
                    while j < k and nums[j] == nums[j-1]:
                        j += 1
                    while j < k and nums[k] == nums[k+1]:
                        k -= 1
        return ans

2.1 二叉树

236. 二叉树的最近公共祖先

  1. none, p, q 的时候都 return 自身
  2. 只在单边, 就只返回单边的结果
  3. 在两边都有, 则返回当前结点
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root or root is p or root is q:
            return root
        
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)

        if left and right:
            return root
        if left:
            return left
        if right:
            return right

124. 二叉树中的最大路径和

前置题目:543. 二叉树的直径

  • 二叉树的直径:任意两个节点之间最长路径的长度
class Solution:
    def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
        # 在求最大深度的时候更新答案
        global ans
        ans = 0

        def dfs(node):
            if not node:
            	# 保证叶子节点的直径是 0 (左右空节点都是 -1, 再+2 = 0)
                return -1

            left = dfs(node.left)   # 左侧的最大深度
            right = dfs(node.right) # 右侧的最大深度
            global ans
            ans = max(ans, left + right + 2)  # +2 是左右节点到当前节点的距离

            return max(left, right) + 1
        
        dfs(root)

        return ans

  • 左边最大路径和 + 右边最大路径和 + 当前拐点的值
    • 路径和可能是负数,如果是负数则取 0,表示不走这段
  • 相当于是把求最大直径改为求最大和,并在求最大和的过程中更新答案
class Solution:
    def maxPathSum(self, root: Optional[TreeNode]) -> int:
        global ans 
        ans = -inf

        def dfs(node):
            if not node:
                return 0
            
            left = max(0, dfs(node.left))
            right = max(0, dfs(node.right))
            global ans
            ans = max(ans, left + right + node.val)

            return max(left, right) + node.val
        
        dfs(root)

        return ans

102. 二叉树的层序遍历

要判断特殊情况,root 为 None 时

from collections import deque
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []

        ans = []
        q = deque()
        q.append(root)

        while q:
            cur = len(q)  # 本层的节点数
            val = []
            for _ in range(cur):
                node = q.popleft()
                val.append(node.val)

                if node.left: q.append(node.left)
                if node.right: q.append(node.right)
            ans.append(val)
        return ans

2.2 贪心

121. 买卖股票的最佳时机

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # 记录之前的最低股价
        # 使用第 i 天之前的最低股价来计算第 i 天的最大利润

        min_price = 1e5
        ans = 0
        for x in prices:
            ans = max(ans, x - min_price)
            min_price = min(min_price, x)
        
        return ans

122. 买卖股票的最佳时机 II

  • 对于连续上涨的股价,设为 p1, p2, p3……pn。它的最大收益为 pn - p1,等价于每天都买卖pn - p1 = (pn - pn-1) + …… (p3 - p2) + (p2 - p1)
  • 对于连续下降的股价,不交易是最赚的
  • 所以只需要计算 昨天买, 今天卖 是不是赚的 tmp = prices[i] - prices[i-1]
    • 是赚的,就加入收益
    • 否则就不操作
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        ans = 0
        for i in range(1, len(prices)):
            tmp = prices[i] - prices[i-1]  # 昨天买, 今天卖
            if tmp > 0:
                ans += tmp
        return ans

2.3 DP

动态规划,用空间换时间
所以一般状态数组 f 都是反应一个区间的结果

  • f[i]:A[0:i]要求的结果
  • f[i][j]:A[0:i]B[0:j] 要求的结果

198. 打家劫舍

  • 不能连续偷
  • 第i间房的最大利润为: f[i] = max(f[i-2]+num[i], f[i-1])
  • f 同时平移+2, f[i+2] = max(f[i]+x, f[i+1])
  • nums不用加,因为这个平移只作用在 f 数组
class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        f = [0] * (n+2)

        for i, x in enumerate(nums):
            f[i+2] = max(f[i]+x, f[i+1])
        
        return f[-1]

213. 打家劫舍 II

  • 判断边界nums[0]是否要选
  • 如果选,nums[0], 则 1-1 不能选
  • 如果不选,则相当于是在剩余的 nums[1:] 中选
class Solution:
    def rob1(self, nums):
        n = len(nums)
        f = [0] * (n + 2)

        for i, x in enumerate(nums):
            f[i+2] = max(f[i]+x, f[i+1])
        
        return f[-1]

    def rob(self, nums: List[int]) -> int:
        rob0 = nums[0] + self.rob1(nums[2:-1])
        no_rob0 = self.rob1(nums[1:])

        return max(rob0, no_rob0)

1143. 最长公共子序列

子序列(不连续)

  • f[i][j],表示 text1 的前 i 个字符text2 的前 j 个字符 的最长公共子序列的长度
  • x == y 时, f[i][j] = f[i-1][j-1] + 1
  • x != y 时,f[i][j] = max(f[i-1][j], f[i][j-1])
  • 不相等的时候只能取上一个状态的结果, 而上一个状态只可能是 f[i-1][j], f[i][j-1]
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        l1 = len(text1)
        l2 = len(text2)

        f = [ [0]*(l2+1) for _ in range(l1+1) ]

        for i, x in enumerate(text1):
            for j, y in enumerate(text2):
                if x == y:
                    f[i+1][j+1] = f[i][j] + 1
                else:
                    f[i+1][j+1] = max(f[i][j+1], f[i+1][j])
        
        return f[l1][l2]

72. 编辑距离

在这里插入图片描述

  • f[i][j],表示把 word1 的前 i 个字符word[0:i]) 替换为 word2 的前 j 个字符 的最少操作数
  • x == y 时, f[i][j] = f[i-1][j-1],无需操作
  • x != y 时,需要编辑(操作数+1),从插入,删除,替换中选一个操作最少的
    • 插入,肯定是要插入与 word2 的第 j 个字符 相等的符号
      • 即元素 i+1 == j
      • 此时的状态就相当于在删除 j 的状态下再操作—— f[i][j-1]
    • 删除,相当于去掉 word1[i],所以状态回到了 —— f[i-1][j] + 1
    • 替换f[i-1][j-1] + 1
  • 边界条件
    • word2 为空时,word1 必须删除 i 个元素才可相等
    • word1 为空时,word1 必须插入 i 个元素才可相等
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        # 删除 f[i-1][j], 插入f[i][j-1], 替换f[i-1][j-1]
        # x == y: f[i][j] = f[i-1][j-1]
        # x != y: f[i][j] = min(f[i][j-1], f[i-1][j], f[i-1][j-1]) + 1
        # f 矩阵同时平移 1 

        l1 = len(word1)
        l2 = len(word2)

        f = [ [0] * (l2 + 1) for _ in range(l1 + 1) ]

        # 边界条件 f[i][0] 和 f[0][j]
        for i in range(1, l1 + 1):
            # 即 word1 有i个元素, word2 为空时, 必须删除 i 次, 才可以 word1==word2
            f[i][0] = i  
        for j in range(1, l2 + 1):
            # 即 word1 为空时, word2 有i个元素, 必须插入 i 次, 才可以 word1==word2
            f[0][j] = j

        for i, x in enumerate(word1):
            for j, y in enumerate(word2):
                if x == y:
                    f[i + 1][j + 1] = f[i][j]
                else:
                    f[i + 1][j + 1] = min(f[i+1][j], f[i][j+1], f[i][j]) + 1
        
        return f[l1][l2]

5. 最长回文子串

子串(连续)

  • 方法一:双指针,从最外侧开始遍历(两头),只有两头相等时才判断是不是回文串
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        ans = ""
        max_lens = 0

        for right in range(n-1, -1, -1):
            for left in range(right + 1):
                if s[left] == s[right]:
                    tmp = s[left:right+1]
                    lens = right - left + 1
                    if lens > max_lens:
                        if tmp == tmp[::-1]:
                            ans = tmp
                            max_lens = lens
                            break
        
        return ans
  • 方法二:中心扩散
    • 判断 abcba,只需要从c开始,
    • 判断左右两边是否相等,如果相等则继续扩散
    • if l + 1 <= r - 1: 是循环终止前的状态
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        left, right = 0, 0

        # 奇回文串, 以每个 i 为中心, 计算每个的最长回文串
        for i in range(n):
            l = r = i  # 奇数中心只有一个
            while l >= 0 and r < n and s[l] == s[r]:
                l -= 1
                r += 1
            if l + 1 <= r - 1:  # s[l+1:r] 是回文串
                if r - l - 2 > right - left:
                    left, right = l + 1, r - 1
        

        # 偶数回文串, 以 (i,i+1) 为中心
        for i in range(n - 1):
            l, r = i, i + 1
            while l >= 0 and r < n and s[l] == s[r]:
                l -= 1
                r += 1
            if l + 1 <= r - 1:
                if r - l - 2 > right - left:
                    left, right = l + 1, r - 1

        return s[ left : right + 1 ]  # 要取到 right 需要加 1

2.4 双指针

209. 长度最小的子数组

在这里插入图片描述

  • 由于全是正整数
  • 计算每个右端点,对应的最短子数组(左端点不断移动,l += 1
  • [l, r] 内的和小于 target 时,r += 1 再计算当前右端点的最短子数组
  • 时间复杂度 O(n),l 移动 n 次,r 移动 n 次
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        ans = inf 
        n = len(nums)
        s = 0  # 当前的和

        left = 0
        for right, x in enumerate(nums):
            s += x
            # 找当前 right 的最短的满足条件的子数组
            while s >= target:
                ans = min(ans, right-left+1)
                s -= nums[left]
                left += 1
        
        return ans if ans <= n else 0

76. 最小覆盖子串

和 209.长度最小的子数组 一样

  • 求每个 right 的最小覆盖
from collections import Counter
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        target = Counter(t)
        cnt = Counter()
        n = len(s)
        ans_left, ans_right = 0, n

        left = 0
        for right, x in enumerate(s):
            cnt[x] += 1
            while cnt >= target:
                if right - left < ans_right - ans_left:
                    ans_left, ans_right = left, right
                cnt[s[left]] -= 1
                left += 1
        
        return s[ans_left:ans_right+1] if ans_right < n else ""

2.5 链表

25. K 个一组翻转链表

  • p0:总是指向,待翻转链表的前一个节点
    1. tmp = p0.next,保存 p0 的下一个位置
    2. p0.next.next = cur,与后半段相接
    3. p0.next = pre,与前半段相接
    4. p0=tmp,开启下一次
      在这里插入图片描述
class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        n = 0
        cur = head
        while cur:
            n += 1
            cur = cur.next
        
        p0 = dummy = ListNode(next=head)

        while n >= k:
            n -= k
            pre = None
            cur = p0.next

            for _ in range(k):
                tmp = cur.next
                cur.next = pre
                pre = cur
                cur = tmp
                
            tmp = p0.next  # 下一段待翻转链表的前一个位置
            p0.next.next = cur  # 与后半段相接
            p0.next = pre  # 与前半段相接
            p0 = tmp  # 指向下一段待翻转链表的前一个位置
        
        return dummy.next

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值