剑指offer python刷题笔记

面试题3:二维数组中的查找
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解法:从数组的右上角开始,当target小于当前元素时,从下一列开始;当target大于当前元素,从下一行开始;直到元素相等或者循环结束。
时间复杂度:O(m+n),其中m为行数,n为列数,最坏情况下,需要遍历m+n次
空间复杂度:O(1)
测试用例:二维数据中包含查找的数字;二维数组中没有查找的数字;特殊输入测试(矩阵为空)

def search_list(matrix, target):
    m = len(matrix)
    if not m:
        return False
    n = len(matrix[0])

    top = 0
    right = n - 1
    while top < m and right >= 0:
        if target > matrix[top][right]:
            top += 1
        elif target < matrix[top][right]:
            right -= 1
        else:
            return True
    return False


if __name__ == '__main__':
    matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    print(search_list([], 10))
    print(search_list(matrix, 10))
    print(search_list(matrix, 5))

面试题4:替换空格
题目:请实现一个函数,把字符串中的每个空格替换成"%20",例如"We are happy. ".则输出"We%20are%20happy. "。
解法:首先遍历一次字符串,统计空格数,并计算出替换之后的字符串的总长度。从字符串的后面开始复制和替换,准备两个指针,P1和P2,分别指向替换前后的字符串末尾;向前移动P1,逐个把它指向的字符复制到P2指向的位置,直到碰到第一个空格为止。
时间复杂度:O(n),所有的字符都只移动一次
测试用例:输入的字符串中包含空格(空格在最前面,空格在最后面,空格在中间,字符串中有连接多个空格);输入的字符串中没有空格;特殊输入测试(字符串为空,字符串只有一个空格字符,字符串中只有连续多个空格)
但是在python中,字符串为不可变类型,因此需要一个新的列表来进行存放,返回时再转换为字符串。

class Solution:
    def replaceSpace(self, s: str) -> str:
        res = [];
        if not s:
            return ''

        for c in s:
            if c == ' ':
                res.append('%');
                res.append('2');
                res.append('0');
            else:
                res.append(c);

        return ''.join(res);

面试题5:从尾到头打印链表
询问面试官是否允许修改链表的结构。如果可,则可以用反转链表的方法。不可,见下面解法。
题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
解法:通过栈来实现。每经过一个结点时,把结点放入栈中;遍历完整个链表后,再从栈顶逐个输出结点的值。
测试用例:功能测试(输入的链表有多个结点,输入的链表只有一个结点);特殊输入测试(链表为空)
在python中,借助于列表,逆序返回即可。

# 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]:
        if not head:
            return [];
        
        stack = []
        while head != None:
            stack.append(head.val);
            head = head.next;

        return stack[::-1]

面试题6:重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
解法:前序遍历中,第一个数字是树的根结点;中序遍历中,左右子树位于该根结点的两边。递归终止条件为根结点为空;通过在前序遍历列表中得到根结点的值,再在中序遍历中以此为切割点,将中序遍历切割成两个部分,注意此时应该跳过切割点;再根据中序遍历两个部分的长度对前序遍历进行切割,注意此时第一个元素已经被划分出去,所以下标从1开始;最后分别对左右部分递归调用buileTree。
测试用例:普通二叉树(完全二叉树,不完全二叉树);特殊二叉树(所有结点都没有右子结点的二叉树,所有结点才没有左子结点的二叉树,只有一个结点的二叉树);特殊输入测试(二叉树为NULL,输入的前序序列和中序序列不匹配)

# 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 not preorder:
            return None;

        #前序遍历的第一个就是当前的根节点 
        root_val = preorder[0];
        root = TreeNode(root_val);

        #找切割点
        separator_idx = inorder.index(root_val);

        #切割inorder数组,得到inorder数组的左右半边
        inorder_left = inorder[:separator_idx];
        inorder_right = inorder[separator_idx + 1:];


        #切割preorder数组,得到preorder数组的左右半边
        #长度与inorder数组一致
        preorder_left = preorder[1 : 1+len(inorder_left)];
        preorder_right = preorder[1 + len(inorder_left):];

        #递归
        root.left =self.buildTree(preorder_left, inorder_left);
        root.right = self.buildTree(preorder_right, inorder_right);

        return root;
        

面试题7: 用两个栈实现队列
题目:用两个核实现一个队列。请实现它的两个函数appendTail 和deleteHead,分别完成在队列尾部插入结点和在队列删除结点的功能。
解法:两个栈一个作为输入栈,一个作为输出栈;添加元素时,直接加在输入栈后面;删除元素时,如果输出栈不为空,则弹出输出栈的元素;否则如果输入栈也为空,说明此时队列已经空了,返回-1;否则将输入栈中的所有元素添加到输出栈,再进行弹出。
测试用例:往空的队列里添加、删除元素;往非空的队列里添加、删除元素;连续删除元素直到队列为空。

class CQueue:

    def __init__(self):
        self.stackIn, self.stackout = [], [];

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


    def deleteHead(self) -> int:
        if self.stackout:
            return self.stackout.pop();
        if not self.stackIn: # 如果两个栈都空了,则返回-1
            return -1;
        else:
            while self.stackIn:
                self.stackout.append(self.stackIn.pop());
            return self.stackout.pop();

拓展:用两个队列实现栈
用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
解法:使用双向队列。queue_in存放所有数据,queue_out仅在pop的时候会用到;push的时候直接在queue_in后面添加即可;pop的时候,首先需要确认队列不为空;先把queue_in中的所有元素(除了最后一个),依次出列放进queue_out,再交换in和out,此时out里只有一个元素(即之前in里面的最后一个元素),把out中的元素pop出来,即是原队列的最后一个;top的时候,首先确认不空;其次由于仅有in会存入数据,所有返回最后一个即可;empty因为只有in存放了数据,只要判断in是不是有数即可。

from collections import deque
class MyStack:

    def __init__(self):
        self.queue_in = deque();
        self.queue_out = deque();

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

    def pop(self) -> int:
        if self.empty():
            return None;
        for i in range(len(self.queue_in) - 1):
            self.queue_out.append(self.queue_in.popleft())

        self.queue_in, self.queue_out = self.queue_out, self.queue_in;
        return self.queue_out.popleft();

    def top(self) -> int:
        if self.empty():
            return None;
        return self.queue_in[-1]

    def empty(self) -> bool:
        return len(self.queue_in) == 0;

面试题8 : 旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组的末尾, 我们称之为数组的旋转。输入一个递增排序的数组的一个旋转, 输出旋转数组的最小元素。如3 4 5 1 2
解法:利用二分查找,left和right分别指向数组的第一个元素和最后一个元素。将mid与right做对比,如果mid < right,则说明是递增关系,最小值在左边或者就为mid,即right = mid;如果mid > right,则说明mid左边的都比right大,则left = mid +1;若相等,则让区间逐渐缩减,即right -= 1,也能找到答案
时间复杂度为O(logN),如果是全部相等的数组,则退化为O(N);空间复杂度为O(1)
测试用例:功能测试(输入的数组是升序排序的数组的一个旋转,数组中有或者没有重复数字);边界值测试(输入的数组是一个升序排序的数组、只包含一个数字的数组);特殊输入测试(数组为空)

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

面试题9 : 斐波那契数列
解法:动态规划经典例题,为了解决效率问题需要维护两个数值,记录前两步的答案。
时间复杂度:O(n)
空间复杂度:O(1)
测试用例:功能测试(输入3,5,10);边界值测试(输入0,1,2);性能测试(输入较大的数字)

class Solution:
    def fib(self, n: int) -> int:
        if n < 2:
            return n;
        Mod = 10 ** 9 +7;#这是%mod是为了防止溢出
        pre = 0;
        next = 1;
        res = 1;
        for i in range(3, n + 1):
            pre = next;
            next = res;
            res = (pre + next) % Mod;
        return res;

面试题10: 二进制中1 的个数
题目: 请实现一个函数,输入一个整数,输出该数二进制表示中1 的个数。例如把9 表示成二进制是1001,有2 位是1,因此如果输入9, 该函数输出2 。
解法:把一个整数减去1,再与原整数作与运算,会把该整数的最右边一个1变为0;那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。
测试用例:正数(包括边界值1,0x7ffffff),负数,0

class Solution:
    def hammingWeight(self, n: int) -> int:
        # count=0;
        # while n:
        #     if n&1:
        #         count+=1;
        #     n=n>>1
        # return count;
        # 上面这种解法当输入为负数时会导致死循环

        count=0;
        while n:
            n=n&(n-1);
            count+=1;
        return count;

面试题11: 数值的整数次方
题目:实现 pow(x, n) ,即计算 x 的 n 次幂函数。不得使用库函数,同时不需要考虑大数问题。
解法:既然是求一个数的n次方,如果已经知道了它的n/2次方,那么只要在n/2的基础上再平方一次就行了。即用如下公式求a的n次方:
a n = { a n 2 ∗ a n 2 , n 为偶数 a n − 1 2 ∗ a n − 1 2 , n 为奇数 a^n =\left\{ \begin{aligned} a^\frac{n}{2} *a^\frac{n}{2},n为偶数\\ a^\frac{n-1}{2} *a^\frac{n-1}{2},n为奇数\\ \end{aligned} \right. an={a2na2n,n为偶数a2n1a2n1,n为奇数
测试用例:把底数和指数分别设为正数、负数和0

    def myPow(self, x, n):
        if n == 0:
            return 1
        if n < 0:
            x = 1 / x
            n = -n
        if n % 2:
            return x * self.myPow(x, n - 1)
        return self.myPow(x * x, n / 2)

面试题12: 打印1到最大的n位数
题目: 输入数字n, 按顺序打印出从1最大的n位十进制数。比如输入3,则打印出1 、2 、3 一直到最大的3位数即999。
解法:需要考虑大数问题,用字符串来解决。本题其实是对数字0-9的全排列,从1位数0-9的全排列到n位数0-9的全排列,要注意的是数字开头不应该有0。
具体步骤:
1.为了避免数字开头出现0,先把首位first固定,取值范围为1-9;
2.用digit表示要生成的数字的位数,本题要从1位数一直生成到n位数,对每种数字的,位数都生成一下首位,所以有个双重for循环;
3.生成首位之后进入递归生成剩下的digit-1位数,从0-9中取值
4.递归的终止条件为已经生成了digit位的数字,即index==digit,将此时的数num转为int加到结果res中
测试用例:功能测试(输入1,2,3),特殊输入(输入-1,0)
时间复杂度:O(10^n),从 1 到 10 ^n -1 的数肯定都遍历了一遍
空间复杂度:O(n),空间复杂度度主要在递归栈以及num数组这里,递归的最大深度和数组的最大长度都为n;算返回值的话为O(10^n)

class Solution:
    def printNumbers(self, n: int) -> List[int]:
        def dfs(index, num, digit):
            if index == digit:
                # 4.递归终止条件
                res.append(int(''.join(num)))
                return

            for i in range(10):
                # 3 从0-9中取值,生成剩下的digit-1位数
                num.append(str(i))
                dfs(index+1,num,digit)
                num.pop()
                
        if n <= 0:
            return []
        
        res = []
        for digit in range(1, n+1):
            # 2 从1位数生成到n位数
            for first in range(1,10):
                # 1 固定首位的first,取值范围1-9
                num = [str(first)]
                dfs(1,num,digit) # 从1开始,因为首位已经固定

        return res

面试题13 : 在0(1)时间删除链表结点
题目:给定单向链表的头指针和一个要删除的结点的值,定义一个函数在0(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:
        if not head:
            return head;
        if head.val == val:
            return head.next;
        p = head;
        while p.next.val != val and p.next != None:
            p = p.next;

        if p.next.val == val:
            p.next = p.next.next;


        return head;

面试题14 : 调整数组顺序使奇数位于偶数前面
题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
解法:维护两个指针,left指向数组的第一个数字,right指向最后一个数字。如果第一个指针指向的数字是偶数,第二个指针指向的数字是奇数,则交换这两个数字。
测试用例:功能测试(输入数组中的奇数、偶数交替出现,输入的数组中所有偶数都出现在奇数前面,输入的数组中所有奇数都出现在偶数前面);特殊输入测试(输入为空,输入的数组只包含一个数字)
时间复杂度:o(n),_n_为数组 nums长度,双指针left,right共同遍历整个数组。
空间复杂度:o(1),双指针使用常数大小的额外空间。

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        left, right = 0, len(nums) - 1
        while left < right:
            while left < right and nums[left] % 2 == 1:
                left += 1
            while right >= 0 and nums[right] % 2 == 0:
                right -= 1

            if left < right:
                nums[left], nums[right] = nums[right], nums[left]
        return nums

面试题15 : 链表中倒数第k个结点
题目: 输入一个链表,输出该链表中倒数第k 个结点
解法:定义两个指针fast和slow,fast先向前走k - 1步;从第k步开始,两个指针一起走,当fast指针走到链表最后时,slow指向的刚好是倒数第k个结点
测试案例:功能测试(第k个结点在链表的中间,第k个结点是链表的头结点,第k个结点是链表的尾结点);特殊输入测试(链表为空,链表的结点个数小于k,k=0)

# 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:
        fast = head;
        slow = head;
        while k > 0 and fast:
            fast = fast.next;
            k -= 1;
            
        if fast == None:
            return None
        
        while fast:
            slow = slow.next;
            fast = fast.next;

        return slow;

面试题16 : 反转链表
题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点
解法:定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null;
首先用tmp保存cur.next,将cur.next指向pre,此时已经反转了第一个结点,继续移动pre和cur
测试用例:功能测试(输入的链表含有多个结点,链表中只有一个结点);特殊输入测试(空链表)

# 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:
        pre = None
        cur = head
        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
            

        return pre

面试题17 :合并两个排序的链表
题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的
测试用例:功能测试(输入的两个链表有多个结点,结点的值互不相同或者存在值相等的多个结点);特殊输入测试(两个结点一个或者两个为空,两个链表中只有一个结点)

# 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:
        if not l1:
            return l2
        if not l2:
            return l1

        if l1.val < l2.val:
            head = l1
            head.next = self.mergeTwoLists(l1.next, l2)
        else:
            head = l2
            head.next = self.mergeTwoLists(l1,l2.next)

        return head
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        if not list1:
            return list2;
        if not list2:
            return list1;

        
        if list1.val < list2.val:
            head = list1;
            list1 = list1.next;
            p = head;
        else:
            head = list2;
            list2 = list2.next;
            p = head;

        while list1 and list2:
            if list1.val < list2.val:
                p.next = list1;
                list1 = list1.next;
            else:
                p.next = list2;
                list2 = list2.next;
            p = p.next;

        if list1:
            p.next = list1;

        if list2:
            p.next = list2;

        return head;

面试题18: 树的子结构
题目:输入两棵二叉树A 和B,判断B是不是A的子结构。
解法:判断两树是否相等的代码。可能情况:有一棵树为空,或者两树相等返回True,或者判断A的左右子树里是否包含B
测试用例:功能测试(树A和树B都是普通的二叉树,树B是或者不是树A的子结构);特殊输入测试(一棵或者两棵树为空,二叉树只有一个节点)

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

class Solution:
    # 判断两树是否相等
    def isSame(self, A, B):
        if not B:
            return True;
        if not A:
            return False;
        if A.val != B.val:
            return False;
        return self.isSame(A.left, B.left) and self.isSame(A.right, B.right) and A.val == B.val;

    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
        if not A or not B:
            return False;
        if self.isSame(A, B) and A.val == B.val:
            return True;
        return self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)

面试题19 : 二叉树的镜像
题目:请完成一个函数,输入一个二叉树,该函数输出它的镜像。
解法:如果树为空,则返回空;否则交换左右结点,再分别对左右子树递归调用镜像算法;返回值为root
测试用例:功能测试(普通的二叉树,二叉树的所有结点都没有左子树或者右子树,只有一个结点的二叉树);特殊输入测试(二叉树为空)

# 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 not root:
            return None

        root.left, root.right = root.right, root.left

        self.mirrorTree(root.left)
        self.mirrorTree(root.right)

        return root

面试题20 : 顺时针打印矩阵
题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字
解法:从左到右打印一行,从上到下打印一列,从右到左打印一行,从下到上打印一列;最后还要记得处理可能单下来的一行和一列
测试用例:数组有多行多列,数组只有一行,数组只有一列,数组中只有一行一列

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix:
            return []
        res = []
        row = len(matrix)
        line = len(matrix[0])

        left, top, right, bottom = 0, 0, line - 1, row - 1
        while left < right and top < bottom:
            for i in range(left, right):
                res.append(matrix[top][i])
            for i in range(top, bottom):
                res.append(matrix[i][right])
            for i in range(right, left, -1):
                res.append(matrix[bottom][i])
            for i in range(bottom, top, -1):
                res.append(matrix[i][left])
            left += 1
            right -= 1
            top += 1
            bottom -= 1

        if top == bottom:
            for i in range(left, right + 1):
                res.append(matrix[top][i])
        elif left == right:
            for i in range(top, bottom +1):
                res.append(matrix[i][right])
        return res

面试题21 :包含min 函数的栈
题目: 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min 函数。复杂度为o(1)
解法:用空间换时间,通过辅助栈实现。
数据栈A:用于存储所有元素,保证入栈push(),出栈pop(),获取栈顶top()的正常逻辑。
数据栈B:栈A中的最小元素始终对应栈B的栈顶元素,即min()函数只需要返回B的栈顶元素即可。
测试用例:新压入的数字比之前的最小值大;新压入的数字比之前的最小值小;弹出栈的数字不是最小的元素; 弹出栈的数字是最小的元素;

class MinStack:
    def __init__(self):
        #初始化数据栈和辅助栈
        self.A, self.B = [], []

    def push(self, x: int) -> None:
        # 保持B的元素是非严格降序的,
        # 1.将x压入栈A
        # 2.若栈B为空或者x<=栈顶元素,则将x压入栈B
        self.A.append(x)
        if not self.B or self.B[-1] >= x:
            self.B.append(x)

    def pop(self) -> None:
        # 保持A,B的元素一致性
        if self.A.pop() == self.B[-1]:
            #1.A出栈元素y,若y==B的栈顶元素,则B出栈
            self.B.pop()

    def top(self) -> int:
        # 直接返回A的栈顶元素
        return self.A[-1]

    def min(self) -> int:
        # 直接返回B的栈顶元素
        return self.B[-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()

面试题22: 栈的压入、弹出序列
题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。
解法:借用一个辅助栈,模拟压入/弹出操作的排列。
入栈:按照压栈序列的顺序执行
出栈:每次入栈后,循环判断栈项元素=弹出序列的当前元素是否成立,将符合弹出序列顺序的栈顶元素全部弹出
返回值:当栈为空时,满足;不为空时,不满足
即如果下一个弹出的数字刚好是辅助栈栈顶元素,则直接弹出;如果下一个弹出的数字不在栈顶,则把压栈序列中的数字压入辅助栈,直到把下一个需要弹出的数字压入栈顶为止。
测试用例:功能测试(输入的两个数组含有多个数字或者只有一个数字,第二个数组满足或者不满足条件),特殊输入测试(输入两个空数组)

class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        stack, i = [], 0
        for num in pushed:
            stack.append(num)
            while stack and stack[-1] == popped[i]:
                stack.pop()
                i += 1

        return not stack

面试题23: 从上往下打印二叉树
题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印
解法:层序遍历。将每一层都用一个临时列表q存储起来,遍历该临时列表,并记录列表中结点的左右子树。

# 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]:
        if not root:
            return []
        res = []
        q = [root]

        while q:
            path = []
            for n in q:
                res.append(n.val) 
                if n.left:
                    path.append(n.left)
                if n.right:
                    path.append(n.right)
            q = path
            
        return res


面试题24: 二叉搜索树的后序遍历序列
题目:输入一 个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true。否则返回false。假设输入的数组的任意两个数字都互不相同。
解法:后序遍历结果——左右根;二叉搜索树定义——左子树<根结点<右子树。递归判断所有子树的正确性。
递归解析:
终止条件:当i>=j,说明此子树节点数量<=1,直接返回true
1.划分左右子树,左子树[i,right-1],右子树[right,j-1],根结点j
2.通过index == j判断是否为二叉搜索树
3.返回值为判断此树是否正确,index == j;判断左子树是否正确,verify(i, right-1);判断右子树是否正确,verify(right, j-1)
时间复杂度:o(n^2),递归占用o(n),每轮递归最差情况下需要遍历树所有节点,o(n);空间复杂度o(n)
测试用例:功能测试(输入的后序遍历的序列对应一棵二叉树,包括完全二叉树、所有结点都没有左/右子树的二叉树、只有一个结点的二叉树:输入的后序遍历的序列没有对应二叉树)。特殊输入测试(指向后序遍历序列的指针为 NULL 指针)。

class Solution:
    def verifyPostorder(self, postorder: List[int]) -> bool:
        # 二叉搜索树=排序 
        # 后序 = 左右根,最后一个节点为根结点 

        # 判断数组postorder[i,j]
        def verify(i, j):
            if i >= j :
                return True

            index = i

            # 划分左子树[i,right-1]
            while postorder[index] < postorder[j]:
                index += 1
            # right前的都比根结点小,right后的都比根结点大

            right = index
            # 划分右子树[right,j-1],即检查剩余部分是否都比根结点的值大
            while postorder[index] > postorder[j]:
                index += 1

            # 递归调用判断左右子树是否满足条件
            return index == j and verify(i, right - 1) and verify(right, j - 1)

        return verify(0, len(postorder) -1)

面试题 25: 二叉树中和为某一值的路径
题目:输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。
解法:dfs,path用于记录一条路径,res用来记录所有路径;如果root为空,直接返回;首先将root.val加入路径中,递归终止条件为target == sum并且左右子树为空,此时将path加入res;再分别对左右子树递归调用算法,最后记得将该root.val弹出
时间复杂度:o(n^2);空间复杂度o(n)
测试用例:功能测试(二叉树中有一条、多条符合条件的路径,二叉树中没有符合条件的路径);特殊输入测试(空树)

class Solution:
    def pathSum(self, root: TreeNode, target: int) -> List[List[int]]:
        res = []
        path = []

        def dfs(root, target):
            if not root:
                return
            target -= root.val
            path.append(root.val)

            if target == 0 and not root.left and not root.right:
                res.append(path[:])

            dfs(root.left, target)
            dfs(root.right, target)

            path.pop()

        dfs(root, target)
        return res

面试题 26: 复杂链表的复制
解法:拼接+拆分
需要复制三个部分:1.结点值,2.random指针,3.next指针。
1.首先构建拼接链表,复制原结点的val,并将它放在原结点的后面
2.构造新链表各节点的random指向,cur.next.random = cur.random.next
3.拆分链表,使得next指向正确。
4.返回新链表头结点即可
时间复杂度:o(n),三轮遍历链表;空间复杂度o(1)
测试用例:功能测试(random指向结点自身,两个结点的random形成环状结构,链表中只有一个结点);特殊输入测试(链表为空)

"""
# 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 not head:
            return None
        
        cur = head
        # 1.构建拼接链表,复制原结点的val,并将它放在原结点的后面
        while cur:
            curCopy = Node(cur.val)
            curCopy.next = cur.next
            cur.next = curCopy
            cur = curCopy.next

        # 2.构造random指针
        cur = head
        while cur:
            if cur.random:
                cur.next.random = cur.random.next 
            cur = cur.next.next

        # 3.拆分链表
        cur = head
        newhead = head.next
        while cur.next:
            temp = cur.next
            cur.next = temp.next
            cur = temp

        return newhead

面试题 27: 二叉搜索树与双向链表
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
解法:基于二叉搜索树的中序遍历为递增序列。
1.递归左子树
2.构建链表
3.递归右子树
时间复杂度 O(N) : N为二叉树的节点数,中序遍历需要访问所有节点。
空间复杂度 O(N): 最差情况下,即树退化为链表时,递归深度达到 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 None

        self.head = None
        self.tail = None

        def convert(cur):
            if not cur:
                return
            # 递归左子树
            convert(cur.left)
            if not self.tail:
                # 记录头结点 
                self.head = cur
            else:
                # 修改节点引用
                self.tail.right = cur
                cur.left = self.tail
            self.tail = cur
             # 递归右子树
            convert(cur.right)

        convert(root)
        # 首尾相连
        self.head.left = self.tail
        self.tail.right = self.head
        
        return self.head

面试题 28: 字符串的排列
题目:输入一个字符串,打印出该字符串中字符的所有排列。
解法:即给定一个可包含重复字母的字符串s,按任意顺序返回所有不重复的全排列。
排列问题需要一个used数组,标记已经选择的元素,一个排列里一个元素只能使用一次。
涉及到去重:首先需要对数组排序,同一树层上的“使用过”判断:if i > 0 and s[i] == s[i - 1] and not used[i -1]。
递归终止条件:到叶子节点时,即len(path)==len(s)时,收集结果
时间复杂度:O(n×n!),其中 n 为给定字符串的长度。这些字符的全部排列有 OO(n!) 个,每个排列平均需要 O(n) 的时间来生成。
空间复杂度:O(n)。我们需要 O(n) 的栈空间进行回溯,注意返回值不计入空间复杂度。
测试用例:功能测试(输入的字符串中有一个或者多个字符 ),特殊输入测试(字符串为空)

class Solution:
    def permutation(self, s: str) -> List[str]:
        if not s:
            return []
        lens = len(s)
        used = [0] * lens
        res = []
        path = []

        def dfs(s, used, path):
            # 终止条件
            if len(path) == len(s):
                res.append(''.join(path[:]))
                return 

            for i in range(len(s)):
                if not used[i]:
                    if i > 0 and s[i] == s[i - 1] and not used[i -1]:
                        continue

                    used[i] = 1
                    path.append(s[i])
                    dfs(s, used, path)
                    path.pop()
                    used[i] = 0
        
        dfs(sorted(s), used, path)
        return res

面试题 29: 数组中出现次数超过一半的数字
题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
解法1:将列表排序,出现一半的数字肯定在列表中间。时间复杂度o(nlogn)——排序,空间复杂度为o(1)

class Solution:
    def MoreThanHalfNum_Solution(self , numbers: List[int]) -> int:
        numbers.sort()
        return numbers[len(numbers) // 2]

解法2:候选法/摩尔投票法,用rating记录票数,如果 rating == 0,说明没有候选人,则将当前数当成候选人,即ans =ch ,rating+=1;如果rating >0,说明此时已有候选人,将当前数ch与ans进行比较,如果相同,则rating票数+1,否则-1
时间和空间复杂度分别为 O(N)和 O(1)

class Solution:
    def MoreThanHalfNum_Solution(self , numbers: List[int]) -> int:
        rating = 0
        for ch in numbers:
            if rating == 0:
                ans = ch
            if ch == ans:
                rating += 1
            else:
                rating -= 1
                
        return ans

面试题30: 最小的k个数
题目:输入n个整数,找出其中最小的k个数
测试用例:
功能测试(输入的数组中有相同的数字,输入的数组中没有相同的数字 );边界值测试(输入的k等于1或者等于数组的长度);特殊输入测试(k<1,k>数组的长度,数组为空)
解法思路:
1.快速排序,选取前k个元素返回

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        
        def quick_sort(arr, l, r):
            # 子数组长度为1时终止递归
            if l >= r :
                return 

            # 哨兵划分操作(以arr[l]作为基准值
            i, j = l, r
            while i < j:
                while i < j and arr[j] >= arr[l]:
                    j -= 1
                while i < j and arr[i] <= arr[l]:
                    i += 1
                arr[i], arr[j] = arr[j], arr[i]
            arr[l], arr[i] = arr[i], arr[l]

            # 递归左右子数组执行哨兵划分
            quick_sort(arr, l, i - 1)
            quick_sort(arr, i + 1, r)

        quick_sort(arr, 0, len(arr) - 1)
        return arr[:k]

2.最小堆:用一个大根堆实时维护数组的前k小值。首先将前k个数插入大根堆中,随后从K+1个数开始遍历,如果当前遍历到的比大根堆的堆顶的数要小,则把堆顶弹出,将当前元素插入。最后将大根堆里的数存入数组返回即可。python中为小根堆,所以要对数组中所有的数取相反数,才能使用小根堆维护前k小值。

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        # 方法1 一行代码
        return heapq.nsmallest(k, arr)

    
class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if k==0:
            return [];
        
        heap=[-num for num in arr[:k]];
        heapq.heapify(heap);

        for num in arr[k:]:
            if -num > heap[0]:
                heapq.heapreplace(heap,-num);

        return [-x for x in heap] 

基于快速排序的数组划分:
没有对k个数的顺序作要求,因此只需要将数组划分为最小的k个数和其他数字两部分即可。
1.哨兵划分:划分完毕后,基准数为arr[i],左右子数组区间分别为[l,i-1],[i+1,r]
2.递归或返回:
若k<i,代表第k+1小的数字在左子数组中,则递归左子数组
若k>i,代表第k+1小的数字在右子数组中,则递归右子数组
若k==i,代表此时arr[k]即为第k+1小的数字,直接返回数组前k个数字即可
时间复杂度为o(n),空间复杂度为o(logn)

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if k >= len(arr):
            # 如果k的长度大于数组长度,直接返回数组
            return arr

        def quick_sort(l, r):
            i, j = l, r
            while i < j:
                while i < j and arr[j] >= arr[l]:
                    j -= 1
                while i < j and arr[i] <= arr[l]:
                    i += 1
                arr[i], arr[j] = arr[j], arr[i]
            arr[i], arr[l] = arr[l], arr[i]

            if k < i:
                # 若k<i,代表第k+1小的数字在左子数组中,则递归左子数组
                return quick_sort(l, i - 1)
            if k > i:
                # 若k>i,代表第k+1小的数字在右子数组中,则递归右子数组
                return quick_sort(i + 1, r)
             
            #若 k==i,代表此时arr[k]即为第k+1小的数字,直接返回数组前k个数字即可
            return arr[:k]

        return quick_sort(0, len(arr) - 1)

面试题 31 :连续子数组的最大和
题目:输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 (n)
解法:动态规划求解
动态规划五部曲:
1.确定dp数组以及下标的定义,dp[i],下标i之前的最大连续子序列和
2.确定递推公式:
dp[i] = dp[i-1]+nums[i],即nums[i]加入当前连续子序列和
nums[i],即从头开始计算当前连续子序列和
二者取较大值。
3.dp数组的初始化。即dp[0]=nums[0]
4.确定遍历顺序。dp[i]依赖于dp[i-1],需要从前向后遍历
5.举例推导dp数组。 在递推公式的时候,可以直接选出最大的dp[i]
时间复杂度:o(n);空间复杂度:o(n)
测试用例:功能测试(输入的数组中有正数也有负数,输入的数组中全是正数,全是负数),特殊输入测试(数组为空)

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        lens = len(nums)
        if not lens:
            return 0
        dp = [0] * lens
        dp[0] = nums[0]
        res = dp[0]
        for i in range(1, lens):
            dp[i] = max(dp[i - 1] + nums[i], nums[i]) # 状态转移公式
            res = max(res, dp[i]) # 保存dp[i]的最大值

        return res

面试题 33: 把数组排成最小的数
题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
思路:找到一个排序规则,数组根据这个规则排序之后能排成一个最小的数字。
设数组nums中任意两数字的字符串为x和y,则排序判断规则为:
若x+y > y+x,则x大于y,否则x小于y;
x小于y 代表,排序完成后,数组x应在y左边,大于则反之
时间复杂度 O(NlogN) ;空间复杂度O(N)

class Solution:
    def minNumber(self, nums: List[int]) -> str:

        def compare(x,y):
            a = x + y 
            b = y + x
            if a > b:
                return 1
            elif a < b:
                return -1
            else:
                return 0

        vals = [str(num) for num in nums]
        vals.sort(key = functools.cmp_to_key(compare))
        return ''.join(vals)

面试题34:丑数
题目:我们把只包含因子2 、3 和5 的数称作丑数(Ugly Number) 。求按从小到大的顺序的第1500 个丑数
思路:丑数 = 某较小丑数 * 某因子。
动态规划:
状态定义:dp[i]代表第i+1个丑数
转移方程: dp[i]=min(dp[a]*2,dp[b]*3,dp[c]*5),分别独立判断 dp[i]和dp[a]×2 , dp[b]×3,dp[c]×5 的大小关系,若相等则将对应索引 a , b , c加 1
初始状态:dp[0]=1
返回值:dp[n-1]
时间复杂度:o(n),遍历计算dp列表。
空间复杂度:o(n),dp列表
测试用例:功能测试(输入2,3,4,5,6),特殊输入测试(边界值1,无效输入0),性能测试(输入较大的数字,如1500)

class Solution:
    def nthUglyNumber(self, n: int) -> int:
        a, b, c = 0, 0, 0 #设置a,b,c指向首个丑数
        dp = [1] * n #第一个丑数为1

        for i in range(1, n):
            n2, n3, n5= dp[a] * 2, dp[b] *3, dp[c] * 5
            dp[i] = min(n2, n3, n5) # 为三种情况的最小值
            # 判断dp[i]与他们的关系,相等则将对应索引加1
            if dp[i] == n2:
                a += 1
            if dp[i] == n3:
                b += 1
            if dp[i] == n5:
                c += 1
        
        return dp[-1]

面试题35 : 第一个只出现一次的字符
题目:在字符串中找出第一个只出现一次的字符。如输入“abaccdeff”,则输出‘b’。
思路:考察哈希表的用法。定义哈希表的key是字符,值为字符出现的次数。遍历字符串两次:
第一次:统计各字符串出现的次数是否>1
第二次:找到首个出现次数为1的字符
时间复杂度:O(N)
空间复杂度: O(1)
测试用例:功能测试〈字符串中存在只出现一次的字符, 字符息中不存在只出现一次字符, 字符串中所有字符都只出现一次);特殊输入测试〈字符串为空)

class Solution:
    def firstUniqChar(self, s: str) -> str:
        dict = {}
        for c in s:
            # 直接判断c是否已经在字典中,若不在,为1;在,为0;即只有出现一次的才会为1
            dict[c] = not c in dict
        for c in s:
            if dict[c]:
                # 判断是否为1
                return c
        return ' '

面试题37: 两个链表的第一个公共结点
解法:首先遍历两个链表得到他们的长度及长度差;第二次遍历时,让长链表先走长度差步,接着再同时遍历,找到的第一个相同的结点就是它们的公共结点。
时间复杂度:o(m+n)
测试用例:功能测试(输入的两个链表有公共交点:第一个公共结点在链表的中间,第一个公共结点在链表的末尾,第一个公共结点是链表的头结点;输入的两个链表没有公共结点)。特殊输入测试(输入的链表为空)

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        lenA, lenB = 0, 0
        pA, pB = headA, headB
        #得到两个链表的长度
        while pA:
            lenA += 1
            pA = pA.next
        while pB:
            lenB += 1
            pB = pB.next
        #保证PA指向的为较长链表,temp为长度差
        if lenA > lenB:
            temp = lenA - lenB
            pA = headA;
            pB = headB;
        else:
            temp = lenB - lenA
            pA = headB;
            pB = headA;
        # 长链表先走长度差步
        while temp:
            pA = pA.next
            temp -= 1
        # 同时遍历,返回交点,否则返回None
        while pA and pB:
            if pB == pA:
                return pA
            pA = pA.next
            pB = pB.next
            
        return None

面试题38: 数字在排序数组中出现的次数
思路:可转化为使用二分法分别找到左边界left和右边界right,数字target的数量为right - left - 1.
测试用例:功能测试(数组中包含查找的数字,数组中没有查找的数字,查找的数字在数组中出现一次/多次)。边界值测试(查找数组中的最大值、最小值,数组中只有一个数字)
时间复杂度 O(logN) : 二分法为对数级别复杂度。
空间复杂度 O(1) : 几个变量使用常数大小的额外空间

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        # 搜索右边界
        while left <= right:# 当[i,j]无元素时跳出
            mid = left + (right - left) // 2
            if nums[mid] <= target:
                left = mid + 1
            else:
                right = mid - 1
        r = left 

        # 若数组中无target,则提前返回
        if right >= 0 and nums[right] != target:
            return 0
        
        # 搜索左边界
        left = 0
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        l = right

        return r - l - 1

面试题39 : 二叉树的深度
测试用例:功能测试(输入普通的二叉树, 二叉树中所有结点都没有左/右子树); 特殊输入测试( 二叉树只有一个结点,二叉树为空)
递归法:如果为空,返回0;否则返回1+左右子树中的较大值
时间复杂度 O(N),空间复杂度 O(N)

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

迭代法:层序遍历,返回len(res)
时间复杂度 O(N),空间复杂度 O(N)

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        res = []
        q = [root]

        while q:
            res.append([n.val for n in q])
            path = []
            for n in q:
                if n.left:
                    path.append(n.left)
                if n.right:
                    path.append(n.right)
            q = path

        return len(res)

面试题41 : 和为s的两个数字vs 和为s的连续正数序列
题目一:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,输出任意一对即可。
思路:通过双指针,left指向数组的第一个(也是最小的)数字,right指向数组的最后一个(也是最大的)数字。如果二者的和等于target,则直接返回两者的值;如果大于target,则将right向前移一位;如果小于target,则将left向后移一位。
时间复杂度 O(N) : N为数组 nums的长度;双指针共同线性遍历整个数组。
空间复杂度 O(1) : 变量 left,right使用常数大小的额外空间。
测试用例:功能测试(数组中存在和为s 的两个数,数组中不存在和为s 的两个数);特殊输入测试(数组为空)

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        if len(nums) < 2:
            return []
        left, right = 0, len(nums) - 1
        res = []
        while left < right:
            sum1 = nums[left] + nums[right]
            if sum1 == target:
                res.append(nums[left])
                res.append(nums[right])
                break
            elif sum1 > target:
                right -= 1
            else:
                left += 1

        return res

题目二:输入一个正数s,打印出所有和为s 的连续正数序列(至少含有两个数)
思路:双指针。左边界left,右边界right,构建滑动窗口从左到右滑动。循环中每轮判断滑动窗口内元素和与目标值target的大小关系,若相等,则记录结果;若大于则移动左边界,若小于则移动右边界。
时间复杂度 O(N),空间复杂度 O(1)
测试用例:功能测试(存在和为s 的连续序列, 如9 、100 等;不存在和为s的连续序列, 如4 、0) 。边界值测试(连续序列的最小和3)

class Solution:
    def findContinuousSequence(self, target: int) -> List[List[int]]:
        left, right = 1, 2
        res = []
        sum1 = left + right
        while left < right:
            # 当s=target 时:记录连续整数序列,向右移动左边界,并更新元素和
            if sum1 == target:
                res.append(list(range(left, right + 1)))
                sum1 -= left
                left += 1
            #当sum1>target时:更新元素和,并向右移动左边界;
            elif sum1 > target:
                sum1 -= left
                left += 1
            else:
             #当sum1<target时:向右移动右边界,并更新元素和;
                right += 1
                sum1 += right

        return res

面试题42 : 翻转单词顺序vs 左旋转字符串
题目一:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。
思路:双指针法。
倒序遍历字符串s,记录单词左右索引边界i,j;
每确定一个单词的边界,则将其添加到单词列表res;
最终将单词列表拼接为字符串,并返回即可。
测试用例:功能测试(句子中有多个单词, 句子中只有一个单词) ; 特殊输入测试(字符串为空,字符串中只有空格)。

class Solution:
    def reverseWords(self, s: str) -> str:
        # 删除首尾空格
        s = s.strip()
        # 从后往前
        i, j = len(s) - 1, len(s) - 1
        res = []
        while i >= 0:
            while i >= 0 and s[i] !=' ':
                #搜索首个空格
                i -= 1
            res.append(s[i+1:j+1])# 添加单词
            while s[i] == ' ': # 跳过单词间空格
                i -= 1
            j = i # j指向下个单词的尾字符

        return ' '.join(res) # 拼接并返回

题目二:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。
思路:以’abcdefg’为例,要想得到’cdefgab’需要经过三步,首先,翻转’ab’;其次,翻转’cdefg’;最后,整体做一次翻转,于是得到’cdefgab’。因此,可以调用三次reverse函数。
测试用例:功能测试(把长度为n 的字符串左旋转0 个字符、1 个字符、2 个字符、n- 1个字符、n 个字符、n+ 1个字符)。 特殊输入测试(字符串为空)。

class Solution:
    def reversel(self, s, start, end):
        while start < end:
            s[start], s[end] = s[end], s[start]
            start += 1
            end -= 1
            
    def reverseLeftWords(self, s: str, n: int) -> str:
        lens = len(s)
        if not s or n >= lens or n < 0:
            return ''

        s = list(s) # 先转换为list
        self.reversel(s, 0, n - 1) # 第一次翻转
        self.reversel(s, n, lens - 1) # 第二次翻转
        self.reversel(s, 0, lens -1) # 第三次翻转
        return ''.join(s)

面试题44: 扑克牌的顺子
题目:从扑克牌中随机抽5 张牌,判断是不是一个顺子, 即这5张牌是不是连续的。2~10为数字本身, A 为1, J 为11, Q 为12 , K 为13,而大小王可以看成任意数字。
思路:此5张牌是顺子的充分条件为:
1 除大小王外,所有牌无重复;
2 除大小王外,max - min < 5
set+遍历,是大小王则不做处理;如果为已有的元素,直接返回false;否则返回max - min 是否< 5
时间复杂度 O(N)=O(5)=O(1) ,遍历数组使用 O(N) 时间
空间复杂度 O(N) = O(5) = O(1), 用于判重的辅助 Set 使用 O(N)额外空间。
测试用例:功能测试(抽出的牌中有一个或者多个大、小王,抽出的牌中没有大、小王,抽出的牌中有对子);特殊输入测试(输入为空)。

class Solution:
    def isStraight(self, nums: List[int]) -> bool:
        res = set()
        for num in nums:
            if num == 0:
                # 为大小王的情况
                continue
            elif num in res:
                # 出现对子
                return False
            else:
                res.add(num)

        return max(res) - min(res) < 5

面试题45: 圆圈中最后剩下的数字
题目: 0, 1, …, n-1这n 个数字排成一个圈圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
思路:著名的约瑟夫环问题,有两种解法
解法1:用环形链表模拟圆圈。时间复杂度为o(mn),空间复杂度,辅助链表o(n)
解法2:动态规划。
状态定义:设[i,m问题]的解为dp[i]
转移方程:从dp[i-1]递推得到dp[i]:dp[i]=(dp[i-1]+m)%i
初始状态:[1,m]问题的解恒为0,即dp[1] = 0
返回值:[n,m]问题的解为dp[n]
时间复杂度 O(n) : 状态转移循环n−1 次使用O(n) 时间,状态转移方程计算使用 O(1) 时间;
空间复杂度 O(1): 使用常数大小的额外空间;

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        x = 0 
        for i in range(2, n + 1):
            x = (x + m) % i
        return x

面试题46:求1+2+ … +n
题目:求1+2+…+n.要求不能使用乘除法、for、while、if、else 等关键字及条件判断语句(A?B:C) 。
思路:python的 and 操作如果最后结果为真,返回最后一个表达式的值,or 操作如果结果为真,返回第一个结果为真的表达式的值。可以利用这一性质。
测试用例:功能测试(输入5 、10 求1+2+…+5 和1+2+…+10) 。 边界值测试〈输入0 和1 )。

class Solution:
    def sumNums(self, n: int) -> int:
        return n > 0 and n + self.sumNums(n - 1)

面试题50:树中两个结点的最低公共祖先
思路:
root是p、q的最近公共祖先的定义:
1.p和q在root的子树中,且分列root的两侧
2.p=root,且q在root的左或右子树中
2.q=root,且p在root的左或右子树中
时间复杂度 O(N),空间复杂度 O(N)

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root or q == root or p == root:
            # 递归终止条件,为空或者root == p或者q
            return root
        # 递归左子节点,返回值记为left
        left = self.lowestCommonAncestor(root.left, p, q)
        # 递归右子节点,返回值记为right
        right = self.lowestCommonAncestor(root.right, p, q)
        
        # 如果同时为空,说明左右子树都不含p,q,返回None
        if not left and not right:
            return None
        # 当left为空,right不为空,p,q 都不在root的左子树中,直接返回right 
        elif not left and right:
            return right
        elif left and not right:
            return left
        else:
           # 如果同时不为空,说明p,q分别在左右子树中,返回root
            return root
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值