算法题归纳整理-python


先问清题目,各方面各种问,题目是什么意思,确认方法的输入输出,希望收到什么样的参数?如果不是这种参数怎么处理,输出什么样的结果?结果的范围是?
和面试官确认边界条件,上限是什么?下限是什么?corner case要充分讨论
写代码时最好不断交流,嘴巴里要说,别就只顾着写
最后要给面试官算法复杂度,注意,这里一定要说清楚是最好、平均、最坏,用词要严谨,这些都是细节

快排

最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。
输入:arr = [0, 8, 0, 3, 8, 2, 5], k=4
输出:[0, 0, 2, 3]
输入:arr = [0, 0, 1, 3, 4, 5, 0, 7, 6, 7], k=9
输出:[0, 0, 0, 1, 3, 4, 5, 6, 7]

若第k小的值出现在左侧向左递归,出现在右侧向右递归。
时间复杂度,N + N/2 + N/4 + ... + N/N = 2N, 最好O(N),最坏O(n^2)

def smallestK(arr: list, k: int):
    if k == 0:
        return []
    if k == len(arr):
        return arr

    left, right = 0, len(arr) - 1
    while left < right:
        pivot = partition(arr, left, right)
        if pivot < k - 1:
            left = pivot + 1
        elif pivot > k - 1:
            right = pivot - 1
        else:
            return arr[:k]
    return arr[:left + 1]


def partition(array, left, right):
    pivot = array[right]
    i = left
    for j in range(left, right):
        if array[j] < pivot:
            array[j], array[i] = array[i], array[j]
            i += 1
    array[right], array[i] = array[i], array[right]
    return i

最接近原点的 K 个点

我们有一个由平面上的点组成的列表 points。需要从中找出 K 个距离原点 (0, 0) 最近的点。
输入:points = [[3,3],[5,-1],[-2,4]], K = 2
输出:[[3,3],[-2,4]]

复杂度分析:
时间复杂度 O(N) 
空间复杂度 O(N)

def kClosest(points, K):
    def dist(i):
        return points[i][0] ** 2 + points[i][1] ** 2

    def work(i, j, K):
        if i >= j:
            return
        oi, oj = i, j
        pivot = dist(i)
        basePoint = points[i]

        while i < j:
            while i < j and dist(j) >= pivot:
                j -= 1
            points[i] = points[j]
            while i < j and dist(i) <= pivot:
                i += 1
            points[j] = points[i]
        points[i] = basePoint

        if K < i + 1 - oi:
            work(oi, i - 1, K)
        else:
            work(i + 1, oj, K - (i + 1 - oi))

    work(0, len(points) - 1, K)
    return points[:K]


回溯

全排列

输入:s = “abc”
输出:[‘abc’, ‘acb’, ‘bac’, ‘bca’, ‘cab’, ‘cba’]

复杂度分析:
时间复杂度 O(N!) : N 为字符串 s 的长度;时间复杂度和字符串排列的方案数成
线性关系,方案数为 N * (N-1) * (N-2) … *2 * 1, ,因此复杂度为 O(N!) 。
空间复杂度 O(N^2): 全排列的递归深度为 N ,系统累计使用栈空间大小为 O(N) ;
递归中累计存储的字符数量最多为 N + (N-1) + ... + 2 + 1 = (N+1)N/2,占用 
O(N^2) 的额外空间。

def permutation(s):
    s_list = sorted(s)
    res = []

    def backtrack(s_list, tmp):
        if not s_list:
            res.append("".join(tmp))
        for i in range(len(s_list)):
            if i > 0 and s_list[i] == s_list[i - 1]:
                continue
            backtrack(s_list[:i] + s_list[i + 1:], tmp + [s_list[i]])

    backtrack(s_list, [])
    return res

组合总和

给定一个无重复元素的数组 nums 和一个目标数 target ,找出 nums 中所有可以使数字和为 target 的组合。
nums 中的数字可以无限制重复被选取。
输入:nums=[2, 3, 6, 7], target=7
输出:[[2, 2, 3], [7]]


def combinationSum(nums, target: int):
    nums = sorted(nums)
    res = []

    def backtrack(s, use, remain):
        for i in range(s, len(nums)):
            if nums[i] == remain:
                res.append(use + [nums[i]])
            elif nums[i] < remain:
                backtrack(i, use + [nums[i]], remain - nums[i])
            else:
                return

    backtrack(0, [], target)

    return res

动态规划

股票的最大利润

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
输入: [7, 1, 5, 3, 6, 4]
输出: 5

复杂度分析:
时间复杂度 O(N) : 其中 N 为 prices 列表长度,动态规划需遍历 prices 。
空间复杂度 O(1) : 变量 cost 和 profit 使用常数大小的额外空间。

def maxProfit(prices):
    cost, profit = prices[0], 0
    for price in prices:
        cost = min(cost, price)
        profit = max(profit, price - cost)
    return profit
股票的最大利润II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
你可以尽可能地完成更多的交易(多次买卖一支股票)。设计一个算法来计算你所能获取的最大利润。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入: [7,1,5,3,6,4]
输出: 7

复杂度分析:
时间复杂度 O(N) : 其中 N 为 prices 列表长度,动态规划需遍历 prices 。
空间复杂度 O(1) : 变量 cost 和 profit 使用常数大小的额外空间。

def maxProfit(prices):
    profit = 0
    for i in range(1, len(prices)):
        tmp = prices[i] - prices[i - 1]
        if tmp > 0:
            profit += tmp
    return profit
礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物
输入:
[
[1, 3, 1],
[1, 5, 1],
[4, 2, 1]
]
输出: 12

"""
解题思路:每个位置的最大值等于左侧和上方的最大值加当前值,最后返回右下角的结果即可。
复杂度分析:
时间复杂度 O(MN) : M,N分别为矩阵行高列宽,动态规划需遍历整个grid矩阵,使用 O(MN)时间。
空间复杂度 O(1) : 原地修改使用常数大小的额外空间。
"""
def maxValue(grid) -> int:
    for i in range(len(grid)):
        for j in range(len(grid[0])):
            if i == 0 and j == 0:
                continue
            if i == 0:
                grid[i][j] += grid[i][j - 1]
            elif j == 0:
                grid[i][j] += grid[i - 1][j]
            else:
                grid[i][j] += max(grid[i][j - 1], grid[i - 1][j])
    return grid[-1][-1]
    
青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

复杂度分析:
时间复杂度 O(N) : 计算 f(n) 需循环 n 次,每轮循环内计算操作使用 O(1) 。
空间复杂度 O(1) : 几个标志变量使用常数大小的额外空间。
由于 Python 中整形数字的大小限制取决计算机的内存 (可理解为无限大),因此可不考虑大数
越界问题。

def numWays(n: int) -> int:
        a, b = 1, 1
        for _ in range(n):
            a, b = b, a + b
        return a% 1000000007
        
PS : 为什么要模1000000007(跟我念,一,八个零,七)。
大数相乘,大数的排列组合等为什么要取模
1000000007是一个质数(素数),对质数取余能最大程度避免结果冲突/重复

三步问题

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。
输入:n = 5
输出:13

状态转移方程 f(i)=f(i-1)+f(i-2)+f(i-3)
时间复杂度 O(N)  
空间复杂度 O(1) 

def waysToStep(n: int):
    a, b, c = 1, 2, 4
    if n < 3:
        return n
    if n == 3:
        return 4
    for i in range(n - 3):
        a, b, c = b, c, (a + b + c) % 1000000007
    return c
最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
输入: “abcabcbb”
输出: 3

复杂度分析:
时间复杂度 O(N): 其中 N 为字符串长度,动态规划需遍历计算 dp 列表。
空间复杂度 O(1) : 字符的 ASCII 码范围为 0 ~ 127 ,哈希表 dic 最多使用 
O(128) = O(1)大小的额外空间
def lengthOfLongestSubstring(s: str):
    dic, res, tmp = {}, 0, 0
    for j in range(len(s)):
        i = dic.get(s[j], -1)  # 获取索引 i
        dic[s[j]] = j  # 更新哈希表
        tmp = tmp + 1 if tmp < j - i else j - i  # dp[j - 1] -> dp[j]
        res = max(res, tmp)  # max(dp[j - 1], dp[j])
    return res


复杂度分析:
时间复杂度 O(N): 其中 N 为字符串长度,遍历计算 dp 列表。
空间复杂度 O(1) 
# 双指针
def lengthOfLongestSubstring(s: str):
    dic, res, i = {}, 0, -1
    for j in range(len(s)):
        if s[j] in dic:
            i = max(dic[s[j]], i)  # 更新左指针 i
        dic[s[j]] = j  # 哈希表记录
        res = max(res, j - i)  # 更新结果
    return res
按摩师

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

输入:[2, 7, 9, 3, 1]
输出: 12
解释: 选择 1 号、 3 号和 5 号预约,总时长 = 2 + 9 + 1 = 12。

输入: [2, 1, 4, 5, 3, 1, 1, 3]
输出: 12
解释: 选择 1 号、 3 号、 5 号和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。

时间复杂度:O(n),其中 n 为预约的个数。我们有 2n 个状态需要计算,每次状态
转移需要 O(1) 的时间,所以一共需要 O(2n)=O(n) 的时间复杂度。
空间复杂度:O(1),只需要常数的空间存放若干变量。

def massage(nums) -> int:
    dp0, dp1 = 0, 0
    for i in range(len(nums)):
        dp0, dp1 = dp1, max(dp0 + nums[i], dp1)
    return dp1
    
零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数
输入:coins = [1, 2, 5], amount = 11
输出:3

时间复杂度:O(Sn),其中 S 是金额,n 是面额数。我们一共需要计算 O(S) 个状态,S 为题目所给的总金额。对于每个
状态,每次需要枚举 n 个面额来转移状态,所以一共需要 O(Sn)的时间复杂度。
空间复杂度:O(S)。DP 数组需要开长度为总金额 S 的空间。

def coinChange(coins, amount: int) -> int:
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0

    for coin in coins:
        for x in range(coin, amount + 1):
            dp[x] = min(dp[x], dp[x - coin] + 1)
    return dp[amount] if dp[amount] != float('inf') else -1
    
零钱兑换 II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

时间复杂度:O(N×amount)。其中 N 为 coins 数组的长度。
空间复杂度:O(amount),dp 数组使用的空间。

def change(coins, amount: int) -> int:
    dp = [0] * (amount + 1)
    dp[0] = 1

    for coin in coins:
        for x in range(coin, amount + 1):
            dp[x] += dp[x - coin]
    return dp[amount]

    

二分法

在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。
输入: nums = [5, 7, 7, 8, 8, 10], target=8
输出: 2

"""复杂度分析:
时间复杂度 O(log N): 二分法为对数级别复杂度。
空间复杂度 O(1) : 几个变量使用常数大小的额外空间。"""

def search(nums, target):
    # 搜索右边界right
    i, j = 0, len(nums) - 1
    while i <= j:
        m = (i + j) // 2
        if nums[m] <= target:
            i = m + 1
        else:
            j = m - 1
    right = i

    # 搜索左边界 left
    i = 0
    while i <= j:
        m = (i + j) // 2
        if nums[m] < target:
            i = m + 1
        else:
            j = m - 1
    left = j
    
    return right - left - 1

旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
输入:[3,4,5,1,2]
输出:1

复杂度分析:
时间复杂度 O(log_2 N) : 在特例情况下(例如 [1, 1, 1, 1],会退化到 O(N)。
空间复杂度 O(1) : i, j , m 指针使用常数大小的额外空间。

def minArray(numbers: [int]) -> int:
        i, j = 0, len(numbers) - 1
        while i < j:
            m = (i + j) // 2
            if numbers[m] > numbers[j]:
                i = m + 1
            elif numbers[m] < numbers[j]:
                j = m
            else:
                j -= 1
        return numbers[i]
0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
输入: [0,1,3]
输出: 2

时间复杂度 O(log N): 二分法为对数级别复杂度。
空间复杂度 O(1): 几个变量使用常数大小的额外空间。

def missingNumber(nums) -> int:
    i, j = 0, len(nums) - 1
    while i <= j:
        m = (i + j) // 2
        if nums[m] == m:
            i = m + 1
        else:
            j = m - 1
    return i


双指针

两数之和

输入:nums = [2, 7, 11, 15], target=9
输出:[2,7] 或者 [7,2]

时间复杂度 O(N) : N 为数组 numsnums 的长度;双指针共同线性遍历整个数组。
空间复杂度 O(1) : 变量 i, j 使用常数大小的额外空间。

def twoSum(nums, target):
    i, j = 0, len(nums) - 1
    while i < j:
        s = nums[i] + nums[j]
        if s > target:
            j -= 1
        elif s < target:
            i += 1
        else:
            return nums[i], nums[j]
    return []

时间复杂度 O(N) : N 为数组 numsnums 的长度;
空间复杂度 O(N) :额外空间
def twoSum1(nums, target):
    dct = {}
    for i, n in enumerate(nums):
        if target - n in dct:
            return target - n, n
        dct[n] = i

调整数组顺序使奇数位于偶数前面

输入:nums = [1,2,3,4]
输出:[1,3,2,4] 或 [3,1,2,4]

复杂度分析:
时间复杂度 O(N): N 为数组 nums长度,双指针 i, j 共同遍历整个数组。
空间复杂度 O(1): 双指针 i, j 使用常数大小的额外空间。

def exchange(nums):
    m, n = 0, len(nums) - 1
    while m < n:
        if nums[m] % 2 == 0:
            nums[m], nums[n] = nums[n], nums[m]
            n -= 1
        else:
            m += 1
    return nums
和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

输入:target = 9
输出:[[2,3,4],[4,5]]

滑动窗口解法
复杂度
时间复杂度 O(target): 终点至少需要遍历到target//2+1
空间复杂度 O(1): 只使用了几个变量(结果数组使用的空间不计入内)

def findContinuousSequence(target: int):
    i, j, res = 1, 2, []
    while j <= target // 2 + 1:
        # 一个大于1的正整数,总是小于,它的中值加上一个比中值大的数。
        cur_sum = sum(list(range(i, j + 1)))
        if cur_sum < target:
            j += 1
        elif cur_sum > target:
            i += 1
        else:
            res.append(list(range(i, j + 1)))
            # 这里用j+=1,i+=1,i+=2都可以,因为是连续的正数序列,所以当sum==target
            时, i+1 开头的序列一定无解,左边界可以直接向右移动两位,即i+=2
            i += 2
    return res
盛最多水的容器

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

输入:[1, 8, 6, 2, 5, 4, 8, 3, 7]
输出:49

复杂度分析:
时间复杂度 O(N): 双指针遍历一次底边宽度 N 。
空间复杂度 O(1): 指针使用常数额外空间。

def maxArea(height) -> int:
    res, i, j = 0, 0, len(height) - 1
    while i < j:
        if height[i] < height[j]:
            res = max(res, height[i] * (j - i))
            i += 1
        else:
            res = max(res, height[j] * (j - i))
            j -= 1
    return res

二叉搜索树

树的遍历方式总体分为两类:深度优先搜索(DFS)、广度优先搜索(BFS);
常见的 DFS : 先序遍历、中序遍历、后序遍历;
常见的 BFS : 层序遍历(按层遍历)。

给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None
二叉树的深度

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

方法一:后序遍历(DFS)
时间复杂度 O(N) : N 为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度 O(N): 最差情况下(当树退化为链表时),递归深度可达到 N 。
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
        

方法二:层序遍历(BFS)
时间复杂度 O(N) : N 为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度 O(N) : 最差情况下(当树平衡时),队列 queue 同时存储 N/2 个节点。
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root: 
            return 0
        queue, res = [root], 0
        while queue:
            tmp = []
            for node in queue:
                if node.left: 
                    tmp.append(node.left)
                if node.right: 
                    tmp.append(node.right)
            queue = tmp
            res += 1
        return res
二叉树的深度

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

层序遍历(BFS)
时间复杂度 O(N) : N 为二叉树的节点数量,即 BFS 需循环 N 次。
空间复杂度 O(N) : 最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 O(N) 大小的额外空间。

class Solution:
    def levelOrder(self, root: TreeNode):
        if not root: 
            return []
        res, queue = [], collections.deque()
        queue.append(root)
        while queue:
            node = queue.popleft()
            res.append(node.val)
            if node.left: 
                queue.append(node.left)
            if node.right: 
                queue.append(node.right)
        return res
二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。

复杂度分析:
时间复杂度 O(M+N) :其中,N 和 M 分别为矩阵行数和列数,此算法最多循环 M+N 次。
空间复杂度 O(1) : i, j 指针使用常数大小额外空间。
def findNumberIn2DArray(matrix: list, target: int) -> bool:
        i, j = len(matrix) - 1, 0
        while i >= 0 and j < len(matrix[0]):
            if matrix[i][j] > target: 
                i -= 1
            elif matrix[i][j] < target: 
                j += 1
            else: 
                return True
        return False

链表

# Definition for singly-linked list.
class ListNode:
     def __init__(self, x):
         self.val = x
         self.next = None
环形链表

给定一个链表,判断链表中是否有环。

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        slow = fast = head 
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                return True
        return False
环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

class Solution(object):
    def detectCycle(self, head):
        fast, slow = head, head
        while True:
            if not (fast and fast.next): 
                return
            fast, slow = fast.next.next, slow.next
            if fast == slow:
                break
        fast = head
        while fast != slow:
            fast, slow = fast.next, slow.next
        return fast
相交链表

找到两个单链表相交的起始节点。

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        ha, hb = headA, headB
        while ha != hb:
            ha = ha.next if ha else headB
            hb = hb.next if hb else headA
        return ha
删除链表的倒数第N个节点

删除链表的倒数第 n 个节点,并且返回链表的头结点。

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        dummy = ListNode(0, head)
        first = head
        second = dummy
        for i in range(n):
            first = first.next

        while first:
            first = first.next
            second = second.next
        
        second.next = second.next.next
        return dummy.next
反转链表
双指针
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if not head:
            return head
        pre=None
        cur=head
        while cur:
            # cur=cur.next
            # pre=cur
            # cur.next=pre
            cur.next, pre, cur = pre, cur, cur.next
        return pre

递归
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        def helper(last,cur):
            if not cur: 
            	return last
            nex = cur.next
            cur.next = last           
            return helper(cur,nex)
        return helper(None,head)
移除链表元素

删除链表中等于给定值 val 的所有节点。

class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        ans = ListNode(0)
        ans.next = head
        prev, curr = ans, head
        while curr:
            if curr.val == val:
                prev.next = curr.next
            else:
                prev = curr
            curr = curr.next
        return ans.next
奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

class Solution:
    def oddEvenList(self, head: ListNode) -> ListNode:
        if not head:
            return head
        odd = head
        even_head = even = head.next
        while odd.next and even.next:
            odd.next = odd.next.next
            even.next = even.next.next
            odd, even = odd.next, even.next
        odd.next = even_head
        return head

回文链表

请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true

# 快节点每次走两步,走的同时,反转前半部分的链表。然后逐一比较后半部分和前半部分的值。
class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        slow, fast, prev = head, head, None
        while fast and fast.next:
            fast = fast.next.next
            slow.next, slow, prev = prev, slow.next, slow
        if fast:
            slow = slow.next
        while prev and prev.val == slow.val:
            prev, slow = prev.next, slow.next
        return prev is None

合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        cur = dum = ListNode(0)
        while l1 and l2:
            if l1.val < l2.val:
                cur.next, l1 = l1, l1.next
            else:
                cur.next, l2 = l2, l2.next
            cur = cur.next
        cur.next = l1 if l1 else l2
        return dum.next

其他

数组中重复的数字

先问面试官要时间/空间需求!!!
是否可以使用额外空间,是否可以修改原始数组
只是时间优先就用set,
还有空间要求O(1)原地hash
空间要求O(1)并且不能修改原数组,使用二分法

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,找出数组中重复的数字。
输入:[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3

# 哈希表 时间O(n),空间O(n)
def findRepeatNumber(nums) :
    s = set()
    for num in nums:
        if num in s:
            return num 
        s.add(num)  
            
# 原地hash,时间O(n),空间O(1)
def findRepeatNumber(nums):
    for idx, val in enumerate(nums):
        if idx != val and nums[val] == val:
            return val
        nums[val], nums[idx] = nums[idx], nums[val]

数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2

摩尔投票法:
票数和: 由于众数出现的次数超过数组长度的一半,若记众数的票数为 +1,非众数的票数为 −1 ,
则一定有所有数字的 票数和 > 0 。
票数正负抵消: 设数组 nums 中的众数为 x ,数组长度为 n 。若 nums 的前 a 个数字的 
票数和 =0 ,则数组后 (n−a) 个数字的票数和一定仍 >0 (即后 (n-a)个数字的 众数仍为 x )。

复杂度分析:
时间复杂度 O(N) : N 为数组 nums 长度。
空间复杂度 O(1) : votes 变量使用常数大小的额外空间。

def minArray(nums: [int]) -> int:
    votes, flag = 0, 0
    for v in nums:
        if votes == 0:
            flag = v
        if v == flag:
            votes += 1
        else:
            votes -= 1
    return flag if nums.count(flag) > len(nums) // 2 else None
    
用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

复杂度分析:
由于问题特殊,以下分析仅满足栈初始和结束状态下都为空的情况。
时间复杂度:对于插入和删除操作,时间复杂度均为 O(1)。插入不多说,对于删除操作,虽然看
起来是 O(n) 的时间复杂度,但是仔细考虑下每个元素只会「至多被插入和弹出 stack2 一次」,
因此均摊下来每个元素被删除的时间复杂度仍为 O(1)。
空间复杂度:O(n),需要使用两个栈存储已有的元素。

class CQueue:
    def __init__(self):
        self.A, self.B = [], []

    def appendTail(self, value: int):
        self.A.append(value)

    def deleteHead(self):
        if self.B: 
        	return self.B.pop()
        if not self.A: 
        	return -1
        while self.A:
            self.B.append(self.A.pop())
        return self.B.pop()

滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
输入: nums = [1, 3, -1, -3, 5, 3, 6, 7], k=3
输出: [3, 3, 5, 5, 6, 7]

单调队列
复杂度分析:
时间复杂度 O(n) : 其中 n 为数组 nums 长度;线性遍历 nums 占用 O(N) ;每个元素最多
仅入队和出队一次,因此单调队列 deque 占用 O(2N) 。
空间复杂度 O(k) :  deque中最多同时存储 k 个元素(即窗口大小)。

def maxSlidingWindow(nums, k: int):
    res, deque = [], []
    for i, v in enumerate(nums):
        if i >= k and deque[0] <= i - k:
            deque.pop(0)
        # 只存有可能成为最大值的数字的index进deque
        while deque and nums[deque[-1]] <= v: 
            deque.pop()
        deque.append(i)
        if i >= k - 1:
            res.append(nums[deque[0]])
    return res

第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。
s 只包含小写字母。
输入: s = “abaccdeff”
输出: 返回 “b”

复杂度分析:
时间复杂度 O(N) : N 为字符串 s 的长度;需遍历 s 两轮,使用 O(N) ;HashMap 查找的
操作复杂度为 O(1) ;
空间复杂度 O(N): HashMap 使用 O(N) 大小的额外空间。

def firstUniqChar(s: str):
    dicts = {}
    for i in s:
        dicts[i] = dicts.get(i, 0) + 1
    for i in s:
        if dicts[i] == 1:
            return i
    return ' '

def firstUniqChar(s: str):
    dic = {}
    for i in s:
        dic[i] = i not in dic
    for i in s:
        if dic[i]:
            return i
    return ""


汉诺塔问题

输入:A = [2, 1, 0], B = [], C = []
输出:C = [2, 1, 0]

输入:A = [1, 0], B = [], C = []
输出:C = [1, 0]

def hanota(A, B, C):
    def hanoi(n, x, y, z):
        if n == 1:
            z.append(x.pop())
            return
        else:
            hanoi(n - 1, x, z, y)
            hanoi(1, x, y, z)
            hanoi(n - 1, y, x, z)

    hanoi(len(A), A, B, C)
    return C
括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
输入: “()[]{}”
输出: true

def isValid(s: str) -> bool:
    dic = {")": "(", "]": "[", "}": "{"}
    stack = []
    for i in s:
        if stack and i in dic:
            if stack[-1] == dic[i]:
                stack.pop()
            else:
                return False
        else:
            stack.append(i)

    return not stack

扑克牌中的顺子

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 :
输入: [1, 2, 3, 4, 5] 输出: True
输入:[0, 0, 1, 2, 5] 输出: True

时间复杂度 O(N log N) = O(5 log 5) = O(1)
空间复杂度 O(1)

def isStraight(nums) -> bool:
    joker = 0
    nums.sort()
    for i in range(4):
        if nums[i] == 0:
            joker += 1
        elif nums[i] == nums[i + 1]:
            return False
    return nums[4] - nums[joker] < 5

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值