剑指offer--Leetcode版

面试题03. 数组中重复的数字

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

思路:1. 利用字典,时间复杂度O(n),空间复杂度O(n)

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
        dic={}
        for i in nums:
            if i not in dic:
                dic[i]=1
            else:
                return i
        return False

思路:2. 数组哈希法,遍历数组,把序列[2,3,1,0,2,5,3]修改成一个下标和下标对应值是相同的数组[0,1,2,3,2,5,3] (nums[nums[i]] = nums[i]),寻找当前位(i, 4)的值(nums[i], 2)和当前位的值(nums[i], 2)作为下标的值(nums[nums[i]], 2)相等,时间复杂度O(n),空间复杂度O(1)

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
        n = len(nums)
        for i in range(n):
            while nums[i] != i:
                if nums[i] == nums[nums[i]]:
                    return nums[i]
                nums[nums[i]], nums[i] = nums[i], nums[nums[i]]
        return False

面试题04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

思路:利用二分查找,左下角的元素是这一行中最小的元素,同时又是这一列中最大的元素,比较左下角元素和目标。

  时间复杂度O(n),空间复杂度O(1)

class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        x = len(matrix)
        if x == 0:
            return False
        y = len(matrix[0])
        if y == 0:
            return False
        i = x-1
        j = 0
        while i >= 0 and j < y:
            if matrix[i][j] == target:
                return True
            elif matrix[i][j] < target:
                j += 1
            else:
                i -= 1
        return False

面试题05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

思路:利用字符串内置函数,str.replace(), str.split(), "".join()

  时间复杂度O(n),空间复杂度O(1)

class Solution:
    def replaceSpace(self, s: str) -> str:
        return "%20".join(s.split(" "))

面试题06. 从尾到头打印链表

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

思路:利用栈,遍历链表,先入后出

  时间复杂度O(n),空间复杂度O(n)

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

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        ArrayList = []
        while head:
            ArrayList.append(head.val)
            head = head.next
        return ArrayList[::-1]

 面试题07. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

思路:利用递归(深度优先搜索)。前序遍历序列中,第一个数字总是树的根结点的值。在中序遍历序列中,根结点的值在序列的中间,左子树的结点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。

  • 前序遍历:先访问根结点,再访问左子结点,最后访问右子结点。
  • 中序遍历:先访问左子结点,再访问根结点,最后访问右子结点。
  • 后序遍历:先访问左子结点,再访问右子结点,最后访问根结点。

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if len(preorder)==0:
            return None
        root = TreeNode(preorder[0])
        pos = inorder.index(preorder[0])
        root.left = self.buildTree(preorder[1:pos+1], inorder[:pos])
        root.right = self.buildTree(preorder[pos+1:], inorder[pos+1:])
        return root

面试题09. 用两个栈实现队列

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

思路:利用两个“先进后出”的栈实现一个“先进先出”的队列,将队列元素a, b, c 压入stack1 [a, b, c],弹出压入stack2 [c, b, a],stack2中的栈顶元素是最先进入队列的元素。

  时间复杂度O(n),空间复杂度O(n)

class CQueue:

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

    def appendTail(self, value: int) -> None:
        self.stack1.append(value)

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


# Your CQueue object will be instantiated and called as such:
# obj = CQueue()
# obj.appendTail(value)
# param_2 = obj.deleteHead()

 面试题10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

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

思路:利用动态规划,状态转移方程为f(n)=f(n-1)+f(n-2)

  时间复杂度O(n),空间复杂度O(n)

class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:
            return n
        f = [0,1]
        for i in range(2,n+1):
            f.append(f[i-1]+f[i-2])
        return f[n]%1000000007

面试题10- II. 青蛙跳台阶问题

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

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

思路:利用动态规划,状态转移方程为f(n)=f(n-1)+f(n-2)

  时间复杂度O(n),空间复杂度O(n)

class Solution:
    def numWays(self, n: int) -> int:
        f = [1,1,2]
        for i in range(3,n+1):
            f.append(f[i-1]+f[i-2])
        return f[n]%1000000007

面试题11. 旋转数组的最小数字

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

思路:利用二分查找,比较numbers[mid] 和 numbers[right],分三种情况,numbers[mid] > numbers[right],最小数字一定在mid的右边;numbers[mid] == numbers[right],此时最小数字不好判断在mid左边还是右边;numbers[mid] < numbers[right],最小数字一定就是mid或者在mid的左边。

  时间复杂度O(log(n)),空间复杂度O(1)

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        if len(numbers) == 0:
            return 0
        left = 0
        right = len(numbers) - 1
        while left < right:
            mid = (right + left) // 2
            if numbers[mid] > numbers[right]:
                left = mid + 1
            elif numbers[mid] == numbers[right]:
                right -= 1
            else:
                right = mid
        return numbers[left]

面试题12. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。

思路:利用深度优先搜索是对图中的点依次遍历来找到第一个字符的位置,再利用回溯法,从第一个字符的位置开始按约束条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并满足约束,就退回一步重新遍历。本题约束条件是上下左右四个字符中需要存在与字符串中的下一个字符相同。

  时间复杂度O(n2),空间复杂度O(n)

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        if not word: return True
        if not board: return False
        for i in range(len(board)):
            for j in range(len(board[0])):
                if self.dfs(board, i, j, word):
                    return True
        return False
    
    def dfs(self, board, i, j, word):
        if not word:
            return True
        if i >= len(board) or i < 0 or j >= len(board[0]) or j < 0 or board[i][j] != word[0]: #匹配失败跳出递归
            return False
        tmp = board[i][j]
        board[i][j] = '.'
        if self.dfs(board, i+1, j, word[1:]) or self.dfs(board, i-1, j, word[1:]) or self.dfs(board, i, j+1, word[1:]) or self.dfs(board, i, j-1, word[1:]):
            return True
        else:
            board[i][j] = tmp

面试题13. 机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

思路:利用回溯法,从坐标0,0位置开始按约束条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并满足约束,就退回一步重新遍历。约束条件是上下左右四个格子中存在行坐标和列坐标的数位之和小于k的格子。

  时间复杂度O(n),空间复杂度O(n)

class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        arr = [[1 for i in range(m)] for j in range(n)]
        self.findway(arr,0,0,k)
        return self.count
    def __init__(self):
        self.count = 0
    def findway(self,arr,i,j,k):
        if i < 0 or j< 0 or i >= len(arr) or j >= len(arr[0]):
            return
        #map(function, iterable, ...)根据提供的函数对指定序列做映射, 返回迭代器,map(int, list),['1','2']->[1,2]
        #list(string)把string中的字符转为列表,'12'->['1','2']
        tmpi = list(map(int, list(str(i))))
        tmpj = list(map(int, list(str(j))))
        if sum(tmpi) + sum(tmpj) > k or arr[i][j] != 1:
            return
        arr[i][j] = 0
        self.count += 1
        self.findway(arr, i+1, j, k)
        self.findway(arr, i-1, j, k)
        self.findway(arr, i, j+1, k)
        self.findway(arr, i, j-1, k)

面试题14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

思路:动态规划。边界条件:dp[1] = dp[2] = 1;状态转移方程:dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j]))。

  时间复杂度:O(n2)空间复杂度:O(n)

class Solution:
    def cuttingRope(self, n: int) -> int:
        dp = [0 for _ in range(n + 1)]  
        dp[1] = dp[2] = 1  
        for i in range(3, n + 1):
            for j in range(i):
                dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j]))
        return dp[n]

面试题14- II. 剪绳子 II

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

思路:同上

class Solution:
    def cuttingRope(self, n: int) -> int:
        dp = [0 for _ in range(n + 1)]  
        dp[1] = dp[2] = 1  
        for i in range(3, n + 1):
            for j in range(i):
                dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j]))
        return dp[n]% int(1e9+7)

面试题15. 二进制中1的个数

思路1:整数除2取余,时间复杂度O(n),空间复杂度O(1)

class Solution:
    def hammingWeight(self, n: int) -> int:
        count = 0
        while n != 0:
            count += n%2
            n = n//2
        return count

思路2: bin() 返回一个整数的二进制表示,str.count()返回字符串里某个字符出现的次数,时间复杂度O(n),空间复杂度O(1)

class Solution:
    def hammingWeight(self, n: int) -> int:
        #负数用补码表示,或者n=n&0xffffffff
        if n<0:
            return bin(2**32+n).count('1')
        return bin(n).count('1')

面试题16. 数值的整数次方

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

思路:遍历指数exponent求次方,指数为负数的时候,可以先对指数取反,然后把指数 n 做“二进制分解”算出次方的结果之后再取倒数。

  时间复杂度O(n),空间复杂度O(1)

class Solution:
    def myPow(self, x: float, n: int) -> float:
        flag = 0
        result = 1
        if x == 0:
            return False
        if n < 0:
            flag = 1
            n = -n
        while n :
            if n & 1: 
                result *= x
            x *= x
            n >>= 1
        if flag == 1:
            result = 1 / result
        return result

面试题17. 打印从1到最大的n位数

 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

class Solution:
    def printNumbers(self, n: int) -> List[int]:
        return [i for i in range(1, 10**n)]

面试题18. 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

思路:虚拟结点dummy node,遍历链表。

  时间复杂度O(n),空间复杂度O(1)

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

class Solution:
    def deleteNode(self, head: ListNode, val: int) -> ListNode:
        dummy = ListNode(0)  
        dummy.next = head
        if head.val == val: return head.next
        while head and head.next:
            if head.next.val == val:   
                head.next = head.next.next
            head = head.next
        return dummy.next

面试题19. 正则表达式匹配

请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。

思路:利用动态规划,如果s[0..i-1] 匹配p[0..i-1],定义dp[i][j] 为 true,否则为 false。

  时间复杂度O(n2),空间复杂度O(1)

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        s_len = len(s)
        p_len = len(p)
        dp = [[False] * (p_len + 1) for _ in range(s_len + 1)]
        dp[0][0] = True
        for i in range(p_len):
            if p[i] == "*" and dp[0][i - 1]:
                dp[0][i + 1] = True
        for i in range(s_len):
            for j in range(p_len):
                if p[j] == s[i] or p[j] == ".":
                    dp[i + 1][j + 1] = dp[i][j]
                elif p[j] == "*":
                    if p[j - 1] != s[i]:
                        dp[i + 1][j + 1] = dp[i + 1][j - 1]
                    if p[j-1] == s[i] or p[j-1] == ".":
                        dp[i+1][j+1] = (dp[i][j+1] or dp[i+1][j]   or  dp[i+1][j-1])
        return dp[-1][-1]

面试题20. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"0123"及"-1E-16"都表示数值,但"12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"都不是。

思路:分条件判断,遍历字符串

  时间复杂度O(n),空间复杂度O(1)

class Solution:
    def isNumber(self, s: str) -> bool:
        num = False
        numAfterE = True
        dot = False
        exp = False
        sign = False
        n = len(s)
        for i in range(n):
            # 如果当前为空格
            if (s[i] == ' '):
                # 如果后面不为空格,而且前面已经有了合格的字符 
                # 不合法的例子: '1e-2 +1.3e'
                if (i < n - 1 and s[i + 1] != ' ' and (num or dot or exp or sign)): return False
            # 如果当前为符号
            elif (s[i] == '+' or s[i] == '-'):
                # 如果前面不是e的话, 而且前面不是空格
                # 不合法的例子: '3-+'
                if (i > 0 and s[i - 1] != 'e' and s[i - 1] != 'E' and s[i - 1] != ' '):
                    return False
                #有了符号
                sign = True
            # 如果当前为数字
            elif (s[i] >= '0' and s[i] <= '9'):
                #有了数字
                num = True
                #数字在e后面
                if(exp):
                    numAfterE = True
            # 如果当前为小数点
            elif (s[i] == '.'):
                # 如果已经有了小数点或者自然指数
                # 不合法的例子 '1.2.'或者'99e2.5'
                if (dot or exp): 
                    return False
                # 有了小数点
                dot = True
            # 如果当前为自然指数
            elif (s[i] == 'e' or s[i] == 'E'):
                # 如果已经有了自然指数或者前面没有数字
                # 不合法的例子 '2e10e' 或者'e3' 
                if (exp or not num):
                    return False
                # 有了指数,以及指数后面没有数字
                exp = True
                numAfterE = False
            # 其他字符
            else:
                return False
        # 返回是否是数字以及指数后面是否有数字
        # 不合法的例子 1e
        return num and numAfterE

面试题21. 调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

思路:1. 遍历数组,奇数前插入,偶数后插入,时间复杂度O(n2),空间复杂度O(1)。

   2. 冒泡排序,前偶数后奇数相邻就交换,时间复杂度O(n2),空间复杂度O(1)。

   3. 新建列表,类似双向队列,空间换时间,时间复杂度O(n),空间复杂度O(n)。

   4. 新建列表,先插入奇数,后插入偶数,时间复杂度O(n),空间复杂度O(n)。

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        #1.遍历数组,index记奇数索引,奇数往前插入,时间复杂度O(n2),空间复杂度O(1)
        index = -1
        for i in range(len(nums)):
            if nums[i] % 2 == 1:
                index += 1
                nums.insert(index,nums.pop(i))
        return nums

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        #2.冒泡排序,前偶数后奇数相邻就交换,时间复杂度O(n2),空间复杂度O(1)
        for i in range(len(nums)):
            for j in range(len(nums)-i-1):
                if nums[j]%2 == 0 and nums[j+1]%2 == 1:
                    nums[j], nums[j+1] = nums[j+1], nums[j]
        return nums

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        #3.新建列表,类似双向队列,空间换时间,时间复杂度O(n),空间复杂度O(n)
        res = []
        l = len(nums)
        for i in range(l):
            if nums[l-i-1] % 2 != 0:
                res.insert(0,nums[-i-1])
            if nums[i] % 2 == 0:
                res.append(nums[i])
        return res

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        #4.新建列表,先插入奇数,后插入偶数,时间复杂度O(n),空间复杂度O(n)
        res = []
        l = len(nums)
        for i in range(l):
            if nums[i] % 2 != 0:
                res.append(nums[i])
        for i in range(l):
            if nums[i] % 2 == 0:
                res.append(nums[i])
        return res

面试题22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

思路:利用前后指针,设置fast比slow快k个结点,然后遍历链表,当fast过了最后一个结点后,slow就在倒数第k个结点

  时间复杂度O(n),空间复杂度O(1)

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

class Solution:
    def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
        if head == None or k == 0:
            return None
        fast, slow = head, head
        for i in range(k):
            if fast:
                fast = fast.next
            else:
                return None
        while fast:
            fast = fast.next
            slow = slow.next
        return slow

面试题24. 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

思路:利用前后指针,当前结点的尾结点和前一个结点替换,遍历链表

  时间复杂度O(n),空间复杂度O(1)

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

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        cur = head
        pre = None
        while cur:
            cur.next, pre, cur = pre, cur, cur.next
        return pre

面试题25. 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

思路:利用双节点和虚拟结点dummy node,遍历链表

  时间复杂度O(m+n),空间复杂度O(1)

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

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

面试题26. 树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)。B是A的子结构, 即 A中有出现和B相同的结构和节点值。

思路:利用递归(深度优先搜索),分为两步:第一步在树A中找到和B的根结点的值一样的结点R,第二步再判断树A中以R为根节点的子树是不是包含和树B一样的结构。

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
        if not A or not B:
            return False
        return self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B) or self.is_subtree(A, B)
    def is_subtree(self, tree1, tree2):
        if not tree2:
            return True
        if not tree1 or tree1.val != tree2.val:
            return False
        return self.is_subtree(tree1.left, tree2.left) and self.is_subtree(tree1.right, tree2.right)

面试题27. 二叉树的镜像

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

思路:利用递归(深度优先搜索),交换所有的非叶结点的左右子结点

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def mirrorTree(self, root: TreeNode) -> TreeNode:
        if root == None:
            return None
        root.left, root.right = root.right, root.left
        self.mirrorTree(root.left)
        self.mirrorTree(root.right)
        return root

面试题28. 对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

思路:利用递归(深度优先搜索),使用前序遍历和前序遍历的对称遍历(中右左)

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        return self.getisSymmetric(root,root)
        
    def getisSymmetric(self,p,q):
        if p==None and q==None:
            return True
        if p!=None and q!=None:
            return p.val==q.val and self.getisSymmetric(p.left,q.right) and self.getisSymmetric(p.right,q.left)
        return False

面试题29. 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

思路:利用zip(*zipped)、逆序遍历[::-1],取首行,去除首行后,对矩阵翻转来创建新的矩阵,直到新矩阵为[],退出并将取到的数据返回。

  时间复杂度O(n),空间复杂度O(n)

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        res=[]
        while matrix:
            res.extend(matrix.pop(0))
            matrix=list(zip(*matrix))[::-1]
        return res

面试题30. 包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

思路:使用两个stack,一个为数据栈,另一个为辅助栈。数据栈用于存储所有数据,辅助栈用于存储最小值。

  时间复杂度O(1),空间复杂度O(n)

class MinStack:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.Data = []
        self.Min = []

    def push(self, x: int) -> None:
        if len(self.Min) == 0 or self.Min[-1] >= x:
            self.Min.append(x)
        self.Data.append(x)

    def pop(self) -> None:
        if self.Data[-1] == self.Min[-1]:
            self.Min.pop()
        self.Data.pop()

    def top(self) -> int:
        return self.Data[-1]

    def min(self) -> int:
        return self.Min[-1]


# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.min()

面试题31. 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

思路:借用一个辅助的栈,按照压栈顺序[1,2,3,4,5]压入,直到栈顶元素是出栈顺序[4,5,3,2,1]的第一个元素(4),开始出栈,每出栈一个元素,则将出栈顺序向后移动一位,直到不相等,循环等压栈顺序遍历完成。

  时间复杂度O(n2),空间复杂度O(n)

class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        stackData = []
        for i in pushed:
            stackData.append(i)
            while len(stackData) and stackData[-1] == popped[0]:
                stackData.pop()
                popped.pop(0)
        if len(stackData):
            return False
        return True

面试题32 - I. 从上到下打印二叉树

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

思路:1. 利用递归(深度优先搜索),遍历到新层,增加一层数组。

   2. 利用队列(广度优先搜索),每一次打印一个结点的时候,如果该结点有子结点,则把该结点的子结点放到一个队列的末尾。接下来到队列的头部取出最早进入队列的结点,重复前面的打印操作,直至队列中所有的结点都打印出来为止。

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def levelOrder(self, root: TreeNode) -> List[int]:
        def helper(node, level):
            if not node:
                return
            else:
                sol[level-1].append(node.val)
                if len(sol) == level:  # 遍历到新层时,只有最左边的结点使得等式成立
                    sol.append([])
                helper(node.left, level+1)
                helper(node.right, level+1)
        sol = [[]]
        helper(root, 1)
        return sum(sol,[])

class Solution:
    def levelOrder(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        result = []
        queue = [root]
        while len(queue):
            node = queue.pop(0)
            result.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        return result

面试题32 - II. 从上到下打印二叉树 II

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

思路:1. 利用递归(深度优先搜索),遍历到新层,增加一层数组。

   2. 利用队列(广度优先搜索),每一次打印一个结点的时候,如果该结点有子结点,则把该结点的子结点放到一个队列的末尾。接下来到队列的头部取出最早进入队列的结点,重复前面的打印操作,直至队列中所有的结点都打印出来为止。

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        def helper(node, level):
            if not node:
                return 
            else:
                sol[level-1].append(node.val)
                if len(sol) == level:  # 遍历到新层时,只有最左边的结点使得等式成立
                    sol.append([])
                helper(node.left, level+1)
                helper(node.right, level+1)
        sol = [[]]
        helper(root, 1)
        sol.pop()
        return sol

class Solution:
    def levelOrder(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        result = []
        queue = [root]
        while len(queue):
            out = []
            temp = []
            for node in queue:
                temp.append(node.val)
                if node.left:
                    out.append(node.left)
                if node.right:
                    out.append(node.right)
            result.append(temp)
            queue = out
        return result

面试题32 - III. 从上到下打印二叉树 III

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

思路:1. 利用递归(深度优先搜索),2. 利用队列(广度优先搜索)。

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        def helper(node, level):
            if not node:
                return
            else:
                sol[level-1].append(node.val)
                if len(sol) == level:  # 遍历到新层时,只有最左边的结点使得等式成立
                    sol.append([])
                helper(node.left, level+1)
                helper(node.right, level+1)
        sol = [[]]
        helper(root, 1)
        for i in range(1,len(sol)-1,2):
            sol[i]=sol[i][::-1]
        return sol[:-1]

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []
        result = []
        queue = [root]
        level = 0
        while queue:
            curLayerValues = []
            level += 1
            for _ in range(len(queue)):
                node = queue.pop(0)
                curLayerValues.append(node.val)
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            result.append(curLayerValues[::-1]) if (level % 2 == 0) else result.append(curLayerValues)
        return result

面试题33. 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

思路:利用递归(深度优先搜索),根据后序遍历结果的最后一个数字是根结点的值,二叉搜索树左子树上所有结点的值均小于它的根结点的值,右子树上所有结点的值均大于它的根结点的值,先判断数组的左子树和右子树的位置,然后再判断左子树、右子树是不是二叉搜索树。

  时间复杂度O(n2),空间复杂度O(n)

class Solution:
    def verifyPostorder(self, postorder: List[int]) -> bool:
        if len(postorder) == 0:
            return True
        root = postorder[-1]
        #左右子树的分界pos
        pos = 0
        while postorder[pos] < root:
            pos += 1
        #在二叉搜索书中右子树的结点大于根结点
        for i in range(pos, len(postorder)-1):
            if postorder[i] < root:
                return False
        # 判断 左子树是否为二叉树
        left=True
        if  pos>0:
            left=self.verifyPostorder(postorder[0:pos])
        # 判断 右子树是否为二叉树
        right=True
        if pos<len(postorder)-1:
            right=self.verifyPostorder(postorder[pos:-1])
        return left and right

面试题34. 二叉树中和为某一值的路径

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

思路:利用递归(深度优先搜索),使用前序遍历,先判断当前root是否同时满足条件,然后依次遍历左右子树。

  时间复杂度O(n2),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
        if not root:
            return []
        if not root.left and not root.right and sum == root.val:
            return [[root.val]]
        res = []
        left = self.pathSum(root.left, sum-root.val)
        right = self.pathSum(root.right, sum-root.val)
        for i in left+right:
            res.append([root.val]+i)
        return res

面试题35. 复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

思路:利用字典,字典的key是原复杂链表结点,value是复制后新链表结点的地址。先复制复杂指针的label和next,复制label和next的同时建立一个hash表来存放新旧复杂链表的对应关系,然后再查找random并更新,因为有字典,所以后续只需一步就能找到random

  时间复杂度O(n),空间复杂度O(n)

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""
class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        if head == None:
            return None
        res = res_tail = Node(0)
        tem = head
        dict_tem = {}
        while head:
            res_tail.next = Node(head.val)
            res_tail = res_tail.next
            dict_tem[head] = res_tail
            head = head.next
        res = res.next
        res_tail = res  
        head = tem
        while head:
            if head.random:
                res.random = dict_tem[head.random]
            head, res = head.next, res.next
        res = res_tail
        return res

面试题36. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

思路:利用递归(深度优先搜索),使用中序遍历(递增排列),将所有的节点保存到一个列表中。对这个list进行遍历,每个节点的left设为上一个节点,right设为i+1-n节点。

  时间复杂度O(n),空间复杂度O(n)

"""
# Definition for a Node.
class Node:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
"""
class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        if not root:
            return
        self.arr = []        
        self.inorderTraversal(root)
        n = len(self.arr)        
        for i,v in enumerate(self.arr):
            v.left, v.right = self.arr[i-1], self.arr[i+1-n]       
        return self.arr[0]
    
    def inorderTraversal(self, Root):
        if Root==None:
            return 
        self.inorderTraversal(Root.left)
        self.arr.append(Root)
        self.inorderTraversal(Root.right)

面试题37. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

思路:层次遍历来序列化和反序列化

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        s = ""
        queue = []
        queue.append(root)
        while queue:
            root = queue.pop(0)
            if root:
                s += str(root.val)
                queue.append(root.left)
                queue.append(root.right)
            else:
                s += "n"
            s += " "        
        return s

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        tree = data.split()
        print(tree)
        if tree[0] == "n":
            return None
        queue = []
        root = TreeNode(int(tree[0]))
        queue.append(root)
        i = 1
        while queue:
            cur = queue.pop(0)
            if cur == None:
                continue
            cur.left = TreeNode(int(tree[i])) if tree[i] != "n" else None
            cur.right = TreeNode(int(tree[i + 1])) if tree[i + 1] != "n" else None
            i += 2
            queue.append(cur.left)
            queue.append(cur.right)
        return root
        

面试题38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

思路:利用递归,将排列后的字符分为两部分:第一个字符,以及这个字符之后的所有字符。

  时间复杂度O(n2),空间复杂度O(n)

class Solution:
    def permutation(self, s: str) -> List[str]:
        if len(s)<=1:
            return s.split()
        answer = []
        for i, st in enumerate(s):
            s1 = s[:i]+s[i+1:]
            for y in self.permutation(s1):
                if st+y not in answer:
                    answer.append(st+y)   
        return answer

面试题39. 数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。

思路:利用字典。时间复杂度O(n),空间复杂度O(n)

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        dic={}
        for num in nums:
            if num not in dic:
                dic[num]=1
            else:
                dic[num]+=1
            if dic[num] > len(nums)/2:
                return num
        return 0

面试题40. 最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

思路:创建一个大小为k的数组a来存储最小的k个数字,遍历arr,将数组a中最大的值替换。

  时间复杂度O(n2),空间复杂度O(n)

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if k > len(arr) or k == 0:
            return []
        a=arr[:k]
        temp_max=max(a)
        for i in arr[k:]:
            if i < temp_max:
                a.remove(temp_max)
                a.append(i)
                temp_max=max(a)
        return sorted(a)

面试题41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

思路:利用最大最小堆(根结点的value是堆里所有结点中value最大/小者),将数据分为两部分,位于左边最大堆的最大数据(根)比右边最小堆的最小数据(根)要小,保证两个堆中数据的数目之差不能超过1,根据左边最大的数及右边最小的数得到中位数。利用小顶堆实现大顶堆:添加元素进去时,取反;取出元素时,也取反。

  获取中位数的时间复杂度为O(1),插入一个数据的时间复杂度为O(log(n))

from heapq import *

class MedianFinder:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.max_h = []
        self.min_h = []

    def addNum(self, num: int) -> None:
        #将num放入小根堆,并弹出小根堆的最小值,取反,放入大根堆
        heappush(self.max_h, -heappushpop(self.min_h, num))
        #弹出大根堆中最大的值,取反,即最小的值,放入小根堆,使得最小最大堆元素相差最多为1,最小堆元素个数大于 等于最大堆元素个数
        if len(self.min_h) < len(self.max_h):
            heappush(self.min_h,-heappop(self.max_h))

    def findMedian(self) -> float:
        if len(self.min_h) != len(self.max_h):
            return self.min_h[0]*1.
        else:
            return (-self.max_h[0]+self.min_h[0])/2.


# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()

面试题42. 连续子数组的最大和

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。

思路:动态规划,状态转移方程是 array[i] = max(array[i-1], 0)+array[i]。

  时间复杂度O(n),空间复杂度O(1)

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if nums == []:
            return 0
        for i in range(1, len(nums)):
            nums[i] = max(nums[i-1], 0)+nums[i]
        return max(nums)

面试题43. 1~n整数中1出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

思路:利用递归,将一个数字拆分为最高位first和其右边last,计算数字的量级power,分first==1和first>1两种情况:

  •         first==1,比如1234,其中1000-1234贡献分两部分,这些数的最高位贡献last+1个1,占有最高位的数字还贡献self.countDigitOne(last)个1;1-999的贡献self.countDigitOne(power-1)
  •         first>1,比如3234,1000-1999最高位贡献power个1,占有最高位的数字3000-3234贡献self.countDigitOne(last)个1;1-999,1000-1999,2000-2999的贡献first*self.countDigitOne(power-1)

  时间复杂度O(n),空间复杂度O(n)

class Solution:
    def countDigitOne(self, n: int) -> int:
        if n <= 0: return 0
        if n < 10: return 1
        first = int(str(n)[0])
        last = int(str(n)[1:])
        power = 10**(len(str(n))-1)
        if first == 1:
            return last+1 + self.countDigitOne(power-1) + self.countDigitOne(last)
        else:
            return power + first*self.countDigitOne(power-1) + self.countDigitOne(last)

面试题44. 数字序列中某一位的数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

请写一个函数,求任意第n位对应的数字。

时间复杂度O(n),空间复杂度O(n)

class Solution:
    def findNthDigit(self, n: int) -> int:
        if n < 10:
            return n
        i = 0
        while n > numbers(i):
            n -= numbers(i)
            i += 1
        num_index = n//i
        index = n%i
        num = 10**(i-1) + num_index
        return int(str(num)[index])

def numbers(n):
    if n == 0:
        return 1
    else:
        return 10**(n-1)*9*n

面试题45. 把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

思路:利用lambda表达式、cmp(x,y)、sorted(),将数字转换成字符串再进行两两比较,若ab > ba 则 a 大于 b,然后排序。

  时间复杂度O(nlogn),空间复杂度O(n)

class Solution:
    def minNumber(self, nums: List[int]) -> str:
        from functools import cmp_to_key
        
        def compare(a,b) : 
            return 1 if a+b > b+a else -1
        
        nums = sorted( [str(i) for i in nums] , key=cmp_to_key(compare)) 
        return "".join(nums)

面试题46. 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

思路:利用动态规划,状态转移方程为f(n)=f(n-1)+f(n-2)

  时间复杂度O(n),空间复杂度O(n)

class Solution:
    def translateNum(self, num: int) -> int:
        num = str(num)
        L = len(num)
        dp = [1] * (L + 1)
        for i in range(2, L + 1):
            dp[i] = dp[i - 1]
            if '10' <= num[i - 2] + num[i - 1] <= '25':
                dp[i] = dp[i - 1] + dp[i - 2]
        return dp[-1]

面试题47. 礼物的最大价值

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

思路:利用动态规划

  时间复杂度O(n2),空间复杂度O(n)

class Solution:
    def maxValue(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        for i in range(1, m):
            grid[i][0] += grid[i-1][0]
        for i in range(1, n):
            grid[0][i] += grid[0][i-1]
        for i in range(1, m):
            for j in range(1, n):
                grid[i][j] += max(grid[i][j-1], grid[i-1][j])
        return grid[m-1][n-1]

面试题48. 最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

思路:滑动窗口,使用哈希表记录每个字符的下一个索引,然后尽量向右移动尾指针来拓展窗口,并更新窗口的最大长度。如果尾指针指向的元素重复,则将头指针直接移动到窗口中重复元素的右侧。

  时间复杂度O(n2),空间复杂度O(n)

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        n = len(s)
        hashmap = {}
        head, res = 0, 0
        for tail in range(n):
            if s[tail] in hashmap:
                head = max(hashmap[s[tail]], head)
            hashmap[s[tail]] = tail + 1
            res = max(res, tail - head + 1)
        return res

面试题49. 丑数

我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

思路:利用三指针,分别对应数组中乘因子2、3和5的下标,下一个丑数,定义为丑数数组中的数乘以因子,所得的最小值。

  时间复杂度O(n),空间复杂度O(n)

class Solution:
    def nthUglyNumber(self, n: int) -> int:
        if n == 0:
            return 0
        res = [1]
        index2 = 0
        index3 = 0
        index5 = 0
        for i in range(n-1):
            res.append(min(res[index2]*2,res[index3]*3,res[index5]*5))
            if res[-1] == res[index2]*2:
                index2 += 1
            if res[-1] == res[index3]*3:
                index3 += 1
            if res[-1] == res[index5]*5:
                index5 += 1
        return res[-1]

面试题50. 第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。

思路:利用字典,遍历字符串

  时间复杂度O(n),空间复杂度O(n) 

class Solution:
    def firstUniqChar(self, s: str) -> str:
        dict = {}
        for i in range(len(s)):
            if s[i] not in dict:
                dict[s[i]] = 1
            else:
                dict[s[i]] += 1
        for i in range(len(s)):
            if dict[s[i]] == 1:
                return s[i]
        return " "

面试题51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

思路:分治思想,归并排序,逆序对的总数 =左边数组中的逆序对的数量 + 右边数组中逆序对的数量 + 左右结合成新的顺序数组时中出现的逆序对的数量。每次在合并前,先递归地处理左半段、右半段,则左、右半段有序,且左右半段的逆序对数可得到,再计算左右半段合并时逆序对的个数。

  时间复杂度O(nlogn),空间复杂度O(n)

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        data = nums
        if not data:
            return 0
        temp = [i for i in data]
        return self.mergeSort(temp, data, 0, len(data)-1)
       
    def mergeSort(self, temp, data, low, high):
        if low >= high:
            temp[low] = data[low]
            return 0
        mid = (low + high) // 2
        left = self.mergeSort(data, temp, low, mid)
        right = self.mergeSort(data, temp, mid+1, high)
        count = 0
        i = low
        j = mid+1
        index = low
        while i <= mid and j <= high:
            if data[i] <= data[j]:
                temp[index] = data[i]
                i += 1
            else:
                temp[index] = data[j]
                count += mid-i+1
                j += 1
            index += 1
        while i <= mid:
            temp[index] = data[i]
            i += 1
            index += 1
        while j <= high:
            temp[index] = data[j]
            j += 1
            index += 1
        return count + left + right

面试题52. 两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。

思路:拼接两个链表,pHead1+pHead2与pHead2+pHead1,遍历链表

  时间复杂度O(m+n),空间复杂度O(1)

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

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        p = headA
        q = headB
        while p != q:
            if not p:
                p=headB
            else:
                p=p.next
            if not q:
                q=headA
            else:
                q=q.next
        return p

面试题53 - I. 在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。

思路:利用二分法分别找到数字在数组中出现的第一个位置和最后一个位置,Bisearch(data, k+0.5)找到最后一个位置+1,Bisearch(data, k-0.5)找到第一个位置

  时间复杂度O(logn),空间复杂度O(n)

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        def Bisearch(data, k):
            low = 0
            high = len(data)-1
            while low <= high:
                mid = (low+high)//2
                if data[mid] > k:
                    high = mid - 1
                elif data[mid] < k:
                    low = mid + 1
            return low
        return Bisearch(nums, target+0.5) - Bisearch(nums,target-0.5)

面试题53 - II. 0~n-1中缺失的数字

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

思路:利用二分法找乱序的一侧

   时间复杂度O(logn),空间复杂度O(n)

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        left, right = 0, len(nums)
        while left < right:
            mid = left+(right-left)//2
            if nums[mid]==mid: 
                left = mid+1
            else: 
                right = mid
        return(left)

面试题54. 二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第k大的节点。

思路:利用递归(深度优先搜索),使用中序遍历

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        self.res=[]
        self.dfs(root)
        return self.res[-k]  
    def dfs(self,root):
        if not root:return
        self.dfs(root.left)
        self.res.append(root.val)
        self.dfs(root.right)

面试题55 - I. 二叉树的深度

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

思路:利用递归(深度优先搜索)

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if root is None:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right))+1

面试题55 - II. 平衡二叉树

输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

思路:利用递归(深度优先搜索),使用后序遍历,在遍历到一个结点之前已经遍历了它的左右子树,可以一边遍历一边判断每个结点是不是平衡的。

  时间复杂度O(n),空间复杂度O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        return self.dfs(root) != -1
    def dfs(self, pRoot):
        if pRoot is None:
            return 0
        left = self.dfs(pRoot.left)
        right = self.dfs(pRoot.right)
        if left == -1 or right == -1 or abs(left - right) > 1:
            return -1
        return max(left, right) + 1

 

posted @ 2020-02-14 21:15  DeepLearning_Man  阅读(1488)  评论(0编辑  收藏  举报