剑指offer刷题记录(Python)

作为一个半路出道的非科班人员,谨以此帖子记录我的刷题之路。
语言选择为Python。讲真,第一遍做的时候,都是看着别人的思路和答案一点点写,然后用自己的话再总结思路,所以哪里有错误欢迎指出

文章目录

    • 1、数组中重复的数字
    • 2、二维数组中的查找
    • 3、替换空格
    • 4、从尾到头打印链表
    • 5、重建二叉树
    • 6、二叉树的下一个结点
    • 7、用两个栈实现队列
    • 8、斐波那契数列
    • 9、青蛙跳台阶问题
    • 10、变态跳台阶
    • 12、旋转数组的最小数字
    • 13、 矩阵中的路径
    • 14、机器人的运动范围
    • 15、剪绳子
    • 15、二进制中1的个数
    • 16、数值的整数次方
    • 17、最大乘积
    • 18、删除链表中的节点
    • 19、删除链表中重复的结点
    • 21、表示数值的字符串
    • 22、调整数组顺序使奇数位于偶数前面
    • 23、链表中倒数第k个结点
    • 24、链表中环的入口结点
    • 25、反转链表
    • 26、合并两个排序的链表
    • 27、树的子结构
    • 28、二叉树的镜像
    • 29、对称的二叉树
    • 30、顺时针打印矩阵
    • 31、包含min函数的栈
    • 32、栈的压入、弹出操作
    • 33、从上往下打印二叉树
    • 34、分行从上到下打印二叉树
    • 35、之字形打印数组
    • 36、数组中出现次数超过一半的数字
    • 37、数组中只出现一次的数字
    • 38、最小的K个数
    • 39、字符串的排列
    • 40、字符串的组合
    • 41、数字在排序数组中出现的次数
    • 42、连续子数组的最大和
    • 43、整数中1出现的次数(从1到n整数中1出现的次数)
    • 44、把数组排成最小的数
    • 45、第一个只出现一次的字符
    • 丑数
    • 第一个只出现一次的字符
    • 数组中的逆序对
    • 扑克牌顺子
    • 两个链表的第一个公共结点
    • 和为S的两个数字
    • 和为S的连续正数序列
    • 不用加减乘除做加法
    • 二叉搜索树的最低公共祖先
    • 二叉搜索树的后序遍历序列
    • 二叉树中和为某一值的路径
    • 二叉树的深度
    • 平衡二叉树
    • 复杂链表的复制
    • 构建乘积数组
    • 字符流中第一个不重复的字符
    • 二叉搜索树与双向链表
    • 序列化二叉树
    • 二叉搜索树的第k个结点
    • 数据流中的中位数
    • 正则表达式匹配

1、数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字

思路:既然是长度为n,且数字范围都在[0,n-1]范围内,那么在没有重复数字的情况下对将该数组进行排序的话,第i位置上的数字就是数字i;如果有重复数字,那么i位置上就很有可能不是数字i。为了找出这样的数字,可以先看第i位置的数字(用m表示)是否为i,如果是,说明该位置正确,可以进行下一位数字的比较,如果不是,则把数字m与第m位置的数字进行比较(即数字是几就跟第几位置比较),如果两数字相等,说明该数字即为重复数字,如果不等,就把位置i上的数字m与位置m上的数字进行交换,重复这个过程,直到寻得重复数字。

# -*- coding:utf-8 -*-
class Solution:
    # 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
    # 函数返回True/False
    def duplicate(self, numbers, duplication):
        if numbers == None or len(numbers) <= 0: 
            return False                 ##注意判断数组是否为空
        for i in numbers:
            if i < 0 or i > len(numbers) - 1:          ##判断是否溢出
                return False
        for i in range(len(numbers)):
            while numbers[i] != i:
                if numbers[i] == numbers[numbers[i]]:
                    duplication[0] = numbers[i]
                    return True
                else:
                    index = numbers[i]
                    numbers[i],numbers[index] = numbers[index],numbers[i]
        return False

2、二维数组中的查找

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:从二维数组的右上角元素开始查找,小了向下移,大了向左移

# -*- coding:utf-8 -*-
class Solution:
    # array 二维列表
    def Find(self, target, array):
        if array == []:                    ###别忘了判断数组是否为空
            return False
        
        row_num = len(array)           ###注意Python中求二维数组的行数和列数的写法
        col_num = len(array[0])
        
        i = 0
        j = col_num - 1
        while j >= 0 and i < row_num:
            if array[i][j] < target:
                i += 1
            elif array[i][j] > target:
                j -= 1
            else:
                return True
        return False

看到一个大神的GitHub,他把上面的程序进行了修改,加入了可以判断非法输入、target数据类型与array元素数据类型不同时的处理程序段:

    if type(target) == float and type(array[0][0]) == int:
        if int(target) == target:
            return False
        target = int(target)
    elif type(target) == int and type(array[0][0]) == float:
        target = float(int)
    elif type(target) != type(array[0][0]):    
        return False

这题也可以再扩展,比如输出数组里target的个数,那么只要加一个计数变量,每次判断array[i][j] = target时,变量+1,最终return该计数变量就ok了

3、替换空格

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
书上给的思路我没看,直接写了自己的:先把字符串变为list,然后遍历list,如果存在空格就替换,最后再return为字符串。感觉如果用C++写话的,会在指针上有些绕。

# -*- coding:utf-8 -*-
class Solution:
    # s 源字符串
    def replaceSpace(self, s):
        new = list(s)
        for i in range(len(new)):
            if new[i] == ' ':
                new[i] = '%20'
        return ''.join(new)

参考了大神的GitHub,发现他给出了多种解法,地址:https://github.com/Jack-Lee-Hiter/AlgorithmsByPython/blob/master/Target%20Offer/%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.py
可以简单的用repalce()函数进行替换

# 在Python中str类型是不可变的类型, 使用replace语句会生成一个新的str, 原始的s还是带空格的str变量
def replaceSpace2(self, s):
    if type(s) != str:
        return
    return s.replace(' ', '%20')

也可以这么做:

# 创建新的字符串进行替换
def replaceSpace1(self, s):
    tempstr = ''
    if type(s) != str:
        return
    for c in s:
        if c == ' ':
            tempstr += '%20'
        else:
            tempstr += c
    return tempstr

4、从尾到头打印链表

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

在Python中如何实现链表的操作?我去查了一下,是这样:
首先要定义链表:

class ListNode:
    def __init__(self):
        self.val = None      #链表的默认数据域为空
        self.next = None    #Node的指针予默认指向None

然后对链表进行操作,想要反向打印某一链表,可以借助一个空数组L,通过循环先将该链表的最后一个值放入空数组L的第一位,再令当前值的指针指向倒数第二个数,将倒2数放进数组L的第一位,如此循环下去,就可以实现反向打印链表。

class Solution:
    def printListFromTailToHead(self, listNode):
        if listNode.val == None:
            return
        l = []
        head = listNode
        while head:
            l.insert(0, head.val)
            head = head.next
        return l

或者:

class Solution:
    # 返回从尾部到头部的列表值序列,例如[1,2,3]
    def printListFromTailToHead(self, listNode):
        # write code here
        a = []
        while(listNode):
            a.append(listNode.val)
            listNode = listNode.next
        return a[::-1]

测试用例如下:

node1 = ListNode(10)
node2 = ListNode(11)
node3 = ListNode(13)
node1.next = node2
node2.next = node3
print(S.printListFromTailToHead(node1))

输出结果
[13, 11, 10]

5、重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
思路:根据前序遍历和中序遍历的规则,先找到根节点在中序遍历数组中的位置,则该点左边即为左子树节点的中序遍历结果,右边即为右子树节点的中序遍历结果,调用递归即可找到重建该二叉树。

class Solution:
    # 返回构造的TreeNode根节点
    def reConstructBinaryTree(self, pre, tin):
        # write code here
        if not pre and not tin:    #判断树是否为空
            return None
        if set(pre) != set(tin):    #判断树的节点是否相同
            return None
        root = TreeNode(pre[0])
        i = tin.index(pre[0])
        root.left = self.reConstructBinaryTree(pre[1:i+1],tin[:i])
        root.right = self.reConstructBinaryTree(pre[i+1:],tin[i+1:])
        return root

6、二叉树的下一个结点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:三种情况

class Solution:
    def GetNext(self, pNode):
        # 输入是一个空节点
        if pNode == None:
            return None
        # 注意当前节点是根节点的情况。所以在最开始设定pNext = None, 如果下列情况都不满足, 说明当前结点为根节点, 直接输出None
        pNext = None
        # 如果输入节点有右子树,则下一个结点是当前节点右子树中最左节点
        if pNode.right:
            pNode = pNode.right
            while pNode.left:
                pNode = pNode.left
            pNext = pNode
        else:
            # 如果当前节点有父节点且当前节点是父节点的左子节点, 下一个结点即为父节点
            if pNode.next and pNode.next.left == pNode:
                pNext = pNode.next
            # 如果当前节点有父节点且当前节点是父节点的右子节点, 那么向上遍历
            # 当遍历到当前节点为父节点的左子节点时, 输入节点的下一个结点为当前节点的父节点
            elif pNode.next and pNode.next.right == pNode:
                pNode = pNode.next
                while pNode.next and pNode.next.right == pNode:
                    pNode = pNode.next
                # 遍历终止时当前节点有父节点, 说明当前节点是父节点的左子节点, 输入节点的下一个结点为当前节点的父节点
                # 反之终止时当前节点没有父节点, 说明当前节点在位于根节点的右子树, 没有下一个结点
                if pNode.next:
                    pNext = pNode.next
                else:
                    pNext = None
        return pNext

7、用两个栈实现队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
剑指offer刷题记录(Python)_第1张图片

class Solution:
    def __init__(self):
        self.stack1 = []
        self.stack2 = []
    def push(self, node):
        # write code here
        self.stack1.append(node)
    def pop(self):
        if len(self.stack1) == 0 and len(self.stack2) == 0:
            return
        elif len(self.stack2) == 0:
            while len(self.stack1) > 0:
                self.stack2.append(self.stack1.pop())
        return self.stack2.pop()

测试用例:

s = Solution()
s.push(10)
s.push(11)
s.push(12)
print(s.pop())
s.push(13)
print(s.pop())
print(s.pop())
print(s.pop())
print(s.pop())
print(s.pop())

输出:
10
11
12
13
None
None
感觉测试用例也要会写,不然不知道算法对错
如果是用两个队列实现栈,代码如下:

8、斐波那契数列

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
思路:用递归的话时间复杂度以n的指数形式递增,n大一些就非常慢,so还是老老实实用循环写吧

class Solution:
    def Fibonacci(self, n):
        # write code here
        if n <= 0:
            return 0
        elif n == 1:
            return 1
        else:
            a = [0,1]
            for i in range(2,n+1):   #从0项开始,所以去掉第0和第1项之后,循环相加的下标从2开始
                a.append(a[i-1] + a[i-2])
        return  a[n]

or这样:

class Solution:
    def Fibonacci(self, n=39):
        # write code here
        f0, f1 = 0, 1
        if n == 0:
            return 0
        elif n == 1:
            return 1
        for i in range(2, n+1):
            a = f0 + f1
            f0 = f1
            f1 = a
        return a

9、青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:当n=1只有一个台阶时,只有一种跳法,n=2时,可以有两种跳法,当n>2时,此时把跳法数目看作是n的函数f(n),则青蛙在第一次跳的时候有两种选择:跳一级,则跳法数目为剩下的n-1级台阶的跳法数目f(n-1);跳两级,则跳法数目为f(n-2)。和即为f(n-1) + f(n-2),即归纳为斐波那契数列问题。

class Solution:
    def jumpFloor(self, number):
        if number <= 2:
            return number
        else:
            pre1 = 1
            pre2 = 2
            for i in range(3,number+1):   #从1开始,所以去掉第1和第2项之后,循环相加的下标从3开始
                a = pre1 + pre2
                pre1 = pre2
                pre2 = a
        return a

注意:要区分是从0项开始还是1开始

10、变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:也是斐波那契数列的应用,当阶数为1时,只有一种跳法,f(1)=1,当阶数为n时,跳第一步时有n种跳选择:如果跳1阶,则跳法数为剩下n-1阶的跳法数,即f(n-1);如果第一步跳2阶,则跳法数为剩下n-2阶的跳法数,即f(n-2),依此类推,第一步跳n-1阶,跳法数为f(n-(n-1))=f(1);第一步跳n阶,跳法数f(n-n)=f(0),那么总的跳法数即为f(n-1)+f(n-2)+…+f(1)+f(0),同理当阶数为n-1时,式子为:,两式相减,得f(n)=2*f(n-1)。
具体数学推导如下:

剑指offer刷题记录(Python)_第2张图片
注:图中f(0)的值应该为0

class Solution:
    def jumpFloorII(self, number):
        if number == 0:
            return 0
        if number == 1:
            return 1
        else:
            pre = 1
            for i in range(2,number+1):
                now = pre*2
                pre = now
        return now

11、矩形覆盖
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
思路:横着/竖着,斐波数列应用

class Solution:
    def rectCover(self, number):
        if number <= 2:
            return number
        else:
            pre1 = 2
            pre2 = 1
            for i in range(3,number+1):
                a = pre1 + pre2
                pre2 = pre1
                pre1 = a
        return a

12、旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
没看题直接写成下面:

class Solution:
    def minNumberInRotateArray(self, rotateArray):
        if len(rotateArray) == 0:
            return 0
        else:
            return min(rotateArray)

but面试时不能这么写
思路:
输入的数组为非减排序数组的旋转,数组分为两个排好序的数组。{3,4,5,1,2}其中前半部分{3,4,5}和后半部分{1,2}都为非减数组,且前半部分的数大于等于后半部分。那么采用二分法解答这个问题,找到最小下标,统一返回,mid = l+ (r - l)/2
那么需要考虑三种情况:
(1)array[mid] <= array[r]:
中间数小于等于最右的数,说明中间数在右边的非递减数组中,所以数组最小值一定在中间数的左边,类似[2,2,3,4,5,6,6],
r = mid
(2) array[mid] >= arr[l]:
中间数大于等于最左的数,说明中间数在左边的非递减数组中,所以数组最小值一定在数组的右边,类似[3,4,5,6,0,1,2],
l = mid + 1
(3)array[mid] == array[r]:
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边还是右边,只能用暴力法

class Solution:
    def minNumberInRotateArray(self, rotateArray):
        if len(rotateArray) == 0:
            return 0
        front = 0
        rear = len(rotateArray)-1
        minVal = rotateArray[0]
        if rotateArray[front] < rotateArray[rear]:
            return rotateArray[front]
        else:
            while rear - front > 1:
                mid = (rear + front)//2
                if rotateArray[mid] > rotateArray[rear]:
                    front = mid
                elif rotateArray[mid] < rotateArray[front]:
                    rear = mid
                elif rotateArray[front] == rotateArray[mid] == rotateArray[rear]:
                    for i in range(1,len(rotateArray)):
                        if rotateArray[i] < minVal:
                            minVal = rotateArray[i]
                            rear = i
            minVal = rotateArray[rear]
            return minVal

13、 矩阵中的路径

14、机器人的运动范围

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思路一:使用DFS搜索

class Solution:
    def movingCount(self, threshold, rows, cols):
        # write code here
        if rows == 0  and cols == 0:
            return 0
        self.used = [[0]*  cols for i in range(rows)]
        return self.dfs(threshold,0,0,rows,cols)
    
    def dfs(self, k, i, j, rows, cols):
        count = 0
        if i < rows and i >= 0 and j >= 0 and j < cols and self.used[i][j] == 0:
            a = 0
            for x in list(str(i) + str(j)):   #计算当前位置的和
                a += int(x)
            if a <= k:       #判断是否符合条件,符合就标记已走过
                self.used[i][j] = 1    
                count = 1+self.dfs(k,i - 1,j,rows,cols)+ \
                self.dfs(k,i + 1,j,rows,cols)+ \
                self.dfs(k,i,j + 1,rows,cols)+ \
                self.dfs(k,i,j - 1,rows,cols)
        return count

思路二:每个位置都遍历一遍,检查是否符合要求,注意
1、单行或者单列,一旦坐标数位之和小于k,之后的格子就走不到了,返回结果。
2、只要行列数大于等于2,除了不符合要求的格子,其他的都能访问到

class Solution:
    def movingCount(self, threshold, rows, cols):
        # write code here
        if rows == 0  and cols == 0:
            return 0
        count = 0
        for i in range(rows):
            for j in range(cols):
                if self.right(threshold,i,j,rows,cols):
                    count += 1
                else:
                    if rows == 1 or cols == 1:  #单行或者单列,一旦不符合条件,就不能走了,直接返回
                        return count
        return count
    
    #判断位置是否符合条件
    def right(self, k, i, j, rows, cols):
        if i < rows and i >= 0 and j >= 0 and j < cols:
            a = 0
            for x in list(str(i) + str(j)):
                a += int(x)
            if a <= k:
                return True
        return False

15、剪绳子

利用动态规划
对于某个n来说,从1开始,切成1,n-1两段,那么此时的最大值肯定由n-1的再分割情况决定;
切成2,n-2段,那么此时的最大值由n-2的再分割情况决定;
以此类推,一直到n的一半…(再往下分就重复了)
每一次分割的最大值为max(i * n-i, i * dp[n - i])

class Solution:
    def cutRope(self, number):
        # write code here
        dp = [0] * (number - 1)  #注意边界,n是从2开始
        dp[0] = 1
        for i in range(3,number + 1):
            cur_max = 0       #每个未知的最大值
            for j in range(1,i//2 + 1):
                cur_yu = i - j     #分割剩下的部分
                cur_max = max(j * cur_yu, j * dp[cur_yu - 2], cur_max)     #求最大值
            dp[i - 2] = cur_max
        return dp[-1]

15、二进制中1的个数

思路:先判断这个数的二进制形式最右一位是否为1,然后右移一位,此时原来的从右数第二位变为最右位,判断他是否为1,然后在右移,如此下去,直到所有位都判断结束。
如何判断是否为1:与1做位与运算,若该位是1则运算结果为1,否则为0。

class Solution:
    def NumberOf1(self, n):
        # write code here
        return sum([(n>>i & 1) for i in range(32)]) 

16、数值的整数次方

若允许使用库函数。可以用pow()函数实现,或者这么写:

class Solution:
    def Power(self, base, exponent):
        # write code here
        if base == 0:
            return False
        else:
            return base**exponent

不能用库函数的话,就老老实实做判断,底数为0时输出False,指数为0时,输出1;先算出base的绝对值次方值,然后借助flag变量判断指数是否小于0,小于0则变倒数,大于0则直接输出。

class Solution:
    def Power(self, base, exponent):
        # write code here
        flag = 1
        result = 1
        if base == 0:
            return False
        if exponent == 0:
            return 1
        if exponent < 0:
            flag = 0
        for i in range(abs(exponent)):   ##先算绝对值次方,再判断指数是否小于0
            result *= base
        if flag == 0:
            result = 1/result
        return result

17、最大乘积

给定一个无序数组,包含正数、负数和0,要求从中找出3个数的乘积,使得乘积最大,要求时间复杂度:O(n),空间复杂度:O(1)
这题是拼多多18年小赵的笔试题,我没看答案,先是自己写,c是取多少个数的乘积(自己加的),array为给定的无序数组。

class Solution:
    def FindMaxThreeX(self,c,array):        
        p = []       #定义包含正数和0的数组
        n = []       #定义只包含负数的数组
        result1 = 1
        result2 = 1
        pm = []       #由p里的最大值组成的数组
        nm = []       #由n里的最大值组成的数组
        if len(array) < 0:
            return False
        if len(array) == 0:
            return 0
        for i in array:
            if i < 0:
                n.append(i)
            else:
                p.append(i)    
        for j in range(1,c+1):
            pm.append(max(p))
            del p[p.index(max(p))]
            #print(p,pm)
        for t in pm:
            result1 = result1 * t
        while len(n):
            for k in range(1,c):
                nm.append(min(n))
                del n[n.index(min(n))]
                #print(n,nm)
            nn = max(pm)
            result2 = result2 * nn
            for x in nm:
                result2 = result2 * x
        return max(result1,result2)

测试用例1:

test = Solution()
print(test.FindMaxThreeX(3,[2,1,3]))

输出:6。正确

测试用例2:

test = Solution()
print(test.FindMaxThreeX(4,[2,1,3,8,5]))    

输出:240。正确

测试用例3

test = Solution()
    print(test.FindMaxThreeX(3,[2,1,8,-5]))

输出:error

ValueError                                Traceback (most recent call last)
 in ()
      1 test = Solution()
----> 2 print(test.FindMaxThreeX(3,[2,1,8,-5]))

 in FindMaxThreeX(self, c, array)
     25         while len(n):
     26             for k in range(1,c):
---> 27                 nm.append(min(n))
     28                 del n[n.index(min(n))]
     29 #                 print(n,nm)

ValueError: min() arg is an empty sequence

根据错误提示,我发现刚开始我以为最大值无非两种情况:三个正数相乘,两个负数和一个正数相乘。但是,我忘记考虑原数组中的正负数的个数。所以明天再改正。
改正:
考虑 数组的3 种情况:
1、全是正数
2、全是负数
3、有正有负
前两种情况都只需要取得最大的 3 个数相乘即可。
第三种情况最好的情况肯定是两个最小的负数相乘得到一个正数,然后跟一个最大的正数相乘,这样得到
的数和三个最大数相乘的结果中较大的那个肯定是最大的数。
问题转化成求出数组中最大的三个数: max1, max2, max3, 两个最小的数 min1,min2,然后比较 max1 *
max2 * max3 和 max1 * min1 * min2 的大小。

18、删除链表中的节点

题目:请编写一个函数,使其可以删除某个链表中给定的(非末尾的)节点,您将只被给予要求被删除的节点。
思路:在链表中的删除节点操作一般是找到将删除点前一节点的指针指向该点的下一节点。但在单链表中,们无法获取前一点的指针。故可用下一节点覆盖当前节点,并使得当前节点的指针指向下一节点的再下一节点,这样就相当于删除了当前节点。

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None
 
class Solution(object):
    def deleteNode(self, node):
        node.val=node.next.val#当前值被后一个值覆盖
        node.next=node.next.next#下一节点跳到下下一节点

19、删除链表中重复的结点

题目:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路:该题为18题的扩展,可通过调用递归解决

class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        if pHead is None or pHead.next is None:
            return pHead
        head1 = pHead.next
        if head1.val != pHead.val:
            pHead.next = self.deleteDuplication(head1)
        else:
            while pHead.val == head1.val and head1.next is not None:
                head1 = head1.next
            if head1.val != pHead.val:
                pHead = self.deleteDuplication(head1)
            else:
                return None
        return pHead

21、表示数值的字符串

题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
思路:利用Python语言的try…except(异常处理)机制,直接float(s)判断是否存在就ok了。

class Solution:
    # s字符串
    def isNumeric(self, s):
        # write code here
        if len(s) == 0:
            return None
        else:
            try:
                float(s)
                return True
            except:
                return False

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

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路一:额外借助两个数组,分别存放数组中的奇数和偶数,再join起来就ok

class Solution:
    def reOrderArray(self, array):
        # write code here
        if len(array) == 0:
            return []
        else:
            j = []
            o = []
            for i in range(len(array)):
                if array[i]%2 == 1:
                    j.append(array[i])
                elif array[i]%2 == 0:
                    o.append(array[i])
        return j+o

思路二:借助冒泡排序的思维,从后往前。前偶后奇就进行交换

class Solution:
    def reOrderArray(self, array):
        # write code her
        if not array:
            return []
        for i in range(len(array)):
            for j in range(len(array) - 1, i, -1):
                if array[j] % 2 != 0 and array[j - 1] % 2 == 0:
                    array[j], array[j-1] = array[j-1],array[j]
        return array

23、链表中倒数第k个结点

题目:输入一个链表,输出该链表中倒数第k个结点。
思路:这题可以先进行一次链表遍历,获得链表的长度。再进行一次遍历,获得第(n-k+1)个数(以1为计数开始点),也即导数第k个数。但如果如果在只希望一次遍历的情况下, 寻找倒数第k个结点, 可以设置两个指针
第一个指针先往前走k-1步, 然后从第k步开始第二个指针指向头结点,然后两个指针一起遍历。当第一个指针指向尾节点的时候, 第二个指针正好指向倒数第k个结点。

推广: 寻找中间节点, 两个指针一起, 第一个指针每次走两步, 第二个指针每次走一步, 快指针指到尾部, 慢指针正好指到中间

class Solution:
    def FindKthToTail(self, head, k):
        # write code here
        if head == None or k <= 0:
            return None
        pAhead = head        #第一个指针
        pBehind =  None      #第二个指针
        
        for i in range(k-1):          #让第一个指针先走k-2步(这里是以0为计数开始点)
            if pAhead.next != None:
                pAhead = pAhead.next
            else:
                return None
        pBehind = head
        while pAhead.next != None:   #循环一直走到尾节点的前一点,再把两指针指向下一节点,就分别获得了尾节点和第k个节点
            pAhead = pAhead.next
            pBehind = pBehind.next
        return pBehind

注:这题要特别注意头节点为空,k<=0以及第一个指针的下一个节点为空的情况。(要进行判断)
其实在Python里也可以这么做:

class Solution:
    def FindKthToTail(self, head, k):
        # write code here
        l=[]
        while head!=None:
            l.append(head)
            head=head.next
        if k>len(l) or k<1:
            return
        return l[-k]

24、链表中环的入口结点

题目:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路:(左神的基础班刚刚讲过这题,感谢左神,感谢自己哈哈哈哈哈哈哈哈哈哈)
给出结论:设置两个指针,一快一慢,快指针一次走两步,慢指针一次走一步。如果无环/头结点为空/只有一个节点,快指针会遍历到空指针,按题意输出null;如果有环则快慢指针会在某一节点相遇,相遇时,慢指针不动,快指针回到头节点,此时两个指针都变为只走一步,再次遍历,那么两指针再次相遇时的节点就是入环的第一个节点。

class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        if pHead == None or pHead.next == None:
            return None
        F = pHead
        S = pHead
        while (F != None and F.next != None):
            S = S.next            
            F = F.next.next
            if F == S:
                F = pHead
                while F != S:      #没有相遇就一直向下走
                    F = F.next
                    S = S.next
                else:            ###while...else语句在条件语句为 false 时执行 else 
                    return F
        else:
            return None

25、反转链表

题目:输入一个链表,反转链表后,输出新链表的表头。
思路:单链表反转的一般思想是让指针反转,但是可能会出现当前节点的指针反转(即指向前一个节点)后,与后一个节点出现链表断裂的现象。所以我们需要提前保存下一节点的信息。故我们需要三个指针:指向当前节点的指针,分别指向当前节点curNode,前一节点preNode和后一节点nextNode。

class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        # write code here
        if pHead == None or pHead.next == None: ###空节点或只有一个节点的情况
            return pHead
        curNode = pHead        #当前节点即就为phead
        preNode = None         #前一节点为null
        while curNode != None:
            nextNode = curNode.next        #要提前保存下一节点的信息才能反转指针
            curNode.next = preNode         #指针反转:让当前节点的指针指向前一节点
            preNode = curNode               #逆序后使当前节点和下一节点都像后移动一个节点,继续下一次的指针反转
            curNode = nextNode
        return preNode                #while循环后curNode为null,则它的前一节点即为新链表的头节点(即原链表的尾节点)

注:我在做题的时候把循环体里最后一句写成了curNode = curNode.next,我本来是想将下一节点赋给当前节点,达到移动的效果,但后来发现,curNode.next在循环体内的第二行被赋值为preNode,即上一节点。如果按照我原来的写法,就相当于把上一节点赋值给了当前节点,和本意相悖,故一直报错。所以一定要看清变量之间的关系,理清思路。
(这题真是想了蛮久,看别人答案也是看了很久才搞明白,真笨鸭。)

写法二:倒序推导,假设只剩头结点没处理,后买的部分都已经翻转,那么只需要将头结点左右的指针进行翻转就ok

class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        # write code here
        if not pHead or not pHead.next:
            return pHead
        new = self.ReverseList(pHead.next)
        nextNode = pHead.next  #记录下一节点
        nextNode.next = pHead  #pHead右指针反转
        pHead.next = None    #pHead左指针反转
        return new

26、合并两个排序的链表

题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
剑指offer刷题记录(Python)_第3张图片
思路:两个排序链表的合并,要保证合并之后的链表不减,则需要进行将两链表的节点值进行大小比较,如图(a),链表1的和链表2的头节点比较,链表1头节点的值更小,故其成为新链表的头节点;再把链表2的头节点与链表1的第二个节点进行比较,发现链表2的头节点较小,则其为新链表的第二个节点,如图(b)。依次类推,这样就是一个递归问题。

class Solution:
    # 返回合并后列表
    def Merge(self, pHead1, pHead2):
        # write code here
        if pHead1 == None:
            return pHead2
        elif pHead2 ==None:
            return pHead1
        pMergeHead = None
        if pHead1.val < pHead2.val:
            pMergeHead = pHead1
            pMergeHead.next = self.Merge(pHead1.next,pHead2)
        else:
            pMergeHead = pHead2
            pMergeHead.next = self.Merge(pHead2.next,pHead1)
        return pMergeHead

注:还要特别注意两个链表为空的情况

27、树的子结构

题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
法一:遍历,对每个节点都验证B是否为A 的子结构,设A节点数为N ,B节点为M,则时间复杂度为O(N*M)

class Solution:
    def HasSubtree(self, pRoot1, pRoot2):
        def subtree(pRoot1,pRoot2):
            if pRoot2 == None and pRoot1 == None:
                return True
            if pRoot2 == None:
                return False
            if pRoot1 == None:
                return False

            if pRoot2.val == pRoot1.val:
                if pRoot2.left == None and pRoot2.right == None:
                    return True
                if subtree(pRoot1.left,pRoot2.left) and subtree(pRoot1.right,pRoot2.right):
                    return True
            return subtree(pRoot1.left,pRoot2) or subtree(pRoot1.right,pRoot2)
        if pRoot1 == None and pRoot2 == None:
            return False
        return subtree(pRoot1,pRoot2)

法二:把A按照先序遍历的方式序列化得到一个字符串T1,把B按照先序遍历的方式序列得到一个字符串T2,如果T2是T1的子串,则B是A 的子结构。即转变为KMP算法查找子串问题。

class Solution:
    def HasSubtree(self, pRoot1, pRoot2):
        # write code here
        tree1 = self.xuliehua(pRoot1)
        tree2 = self.xuliehua(pRoot2)
        if len(tree1) == 0 and len(tree2) == 0:
            return False                        ###空树return Flase
        if len(tree1) == 0 or len(tree2) == 0:
            return False
        return tree2 in tree1
    
    def xuliehua(self,pRoot):    ##根据前序遍历序列化
        if pRoot == None:
            return ''              ###将None值赋空值
        res = str(pRoot.val)
        res += self.xuliehua(pRoot.left)
        res += self.xuliehua(pRoot.right)
        return res

28、二叉树的镜像

题目:操作给定的二叉树,将其变换为源二叉树的镜像。
思路:遍历二叉树的每个节点,如果当前节点有子节点,就交换他的两个子节点,当交换完所有非叶子节点的左右子节点后,就得到了二叉树的镜像

class Solution:
    # 返回镜像树的根节点
    def Mirror(self, root):
        # write code here
        if root == None:
            return None
        ###对左右子节点进行交换
        a = root.left
        root.left = root.right
        root.right = a
        ###然后一直递归下去
        self.Mirror(root.left)
        self.Mirror(root.right)

注:交换步骤更通常简写为root.left, root.right = root.right, root.left

29、对称的二叉树

题目:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路:可定义一种与前序遍历相反的遍历:先遍历父节点,再遍历右子树,最后遍历左子树,此遍历称为对称前序遍历。如果这两个遍历序列相同,说明该二叉树对称。但如果不是满二叉树,则把在遍历时遇到的空指针也带入遍历序列。(与27题对比)

class Solution:
    def isSymmetrical(self, pRoot):
        # write code here
        return self.Symmetrical(pRoot,pRoot)
    def Symmetrical(self,pRoot1,pRoot2):
        if pRoot1 == None and pRoot2 == None:
            return True
        if pRoot1 == None or pRoot2 == None:
            return False
        if pRoot1.val != pRoot2.val:
            return False
        ####递归地将前序遍历与对称前序遍历的对应点进行比较(通过比较左子节点是否等于右子节点)
        return self.Symmetrical(pRoot1.left,pRoot2.right) and self.Symmetrical(pRoot1.right,pRoot2.left)

30、顺时针打印矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
思路:可以模拟魔方逆时针旋转的方法,一直做取出第一行的操作
例如
1 2 3
4 5 6
7 8 9
输出并删除第一行后,再进行一次逆时针旋转,就变成:
6 9
5 8
4 7
继续重复上述操作即可。
剑指offer刷题记录(Python)_第4张图片

class Solution:
    # matrix类型为二维列表,需要返回列表
    def printMatrix(self, matrix):
        # write code here
        result = []
        while matrix:
            result.extend(matrix.pop(0))    ##这里要用extend()
            if len(matrix) == 0 or len(matrix[0]) == 0:
                break
            matrix = self.turn(matrix)
        return result
    
    def turn(self, matrix):
        newmat = []
        n_rows = len(matrix)
        n_columns = len(matrix[0])
        for i in range(n_columns):
            newmat2 = []
            for j in range(n_rows):
                newmat2.append(matrix[j][i])
            newmat.append(newmat2)
        newmat.reverse()
        return newmat

31、包含min函数的栈

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
思路:创建一个辅助栈lmin,用于存放每次压入时的最小元素,即每当主栈压入元素时,比较本次压入元素与辅助栈最小元素的大小,如果当前元素小于辅助栈最小元素,则同时压入辅助栈,否则依旧将辅助栈中的最小元素再次压入辅助栈。但要注意第一次压入操作时,辅助栈为空,所以不管大小直接将该元素压入辅助栈(判断语句中体现该点)。
以上操作即可保证每次弹出的元素均为当前所有元素中的最小元素

class Solution:
    def __init__(self):
        self.l = []
        self.lmin = []
    def push(self, node):
        # write code here
        self.l.append(node)
        if self.lmin == [] or node < self.min():      ##注意压入辅助栈的两个条件都要考虑到
            self.lmin.append(node)
        else:
            self.lmin.append(self.min())
        #l2.append(node)
    def pop(self):
        # write code here
        if self.l == [] or self.lmin == []:
            return None
        self.lmin.pop()
        self.l.pop()
    def top(self):
        # write code here
        return self.l[-1]
    def min(self):
        # write code here
        return self.lmin[-1]

32、栈的压入、弹出操作

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路:这题要根据栈的特性来分析,通过一个辅助栈来完成。首先依次将元素压入辅助栈,在压入过程中要用while循环判断当前压入的元素是否与popV中要弹出的元素(即popV[0])相同,相同即一起弹出,直到不再相同。不再相同则压入新元素,再判断,不相同压入新元素,相同则弹出。如此循环下去,如果popV是所给压入序列的一个弹出序列,则popV最后会为空(因为符合弹出顺序,元素全部弹走了~),如果不为空说明不是所给压入序列的弹出序列

class Solution:
    def IsPopOrder(self, pushV, popV):
        # write code here
        if pushV == [] and popV == []:
            return True
        if pushV == [] or popV == []:
            return False
        stack = []
        for psh in pushV:
            stack.append(psh)
            while stack != [] and stack[-1] == popV[0]:   ###注意要使stack不为空
                popV.pop(0)
                stack.pop()
        return popV == []         ###即判断popV是否为空,空说明是弹出序列

注:最后一步的判断也可以return辅助栈是否为空,因为如果元素相同,则popV与辅助栈同时弹出元素,故辅助栈是否为空也可作为判断条件

33、从上往下打印二叉树

从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路:可以借助队列完成打印。每打印一个节点时,如果该节点存在左孩子or右孩子,就把左右子节点放入队列中,如此循环

class Solution:
    # 返回从上到下每个节点值列表,例:[1,2,3]
    def PrintFromTopToBottom(self, root):
        # write code here
        if not root:
            return []
        queue =[]
        result = []
        queue.append(root)
        while len(queue) > 0:
            currentRoot = queue.pop(0)     ###当前根节点为queue的第一个元素
            result.append(currentRoot.val)
            if  currentRoot.left != None:       ###存在子节点则添加到队列中
                queue.append(currentRoot.left)
            if currentRoot.right != None:
                queue.append(currentRoot.right)
        return result

34、分行从上到下打印二叉树

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路:为33题的扩展,借助几个辅助数组完成

class Solution:
    # 返回二维列表[[1,2],[4,5]]
    def Print(self, pRoot):
        # write code here
        if not pRoot:
            return []
        stack = []
        result = []
        stack.append(pRoot)
        while stack:
            tmp = []
            tmpstack = []
            for i in stack:
                tmp.append(i.val)
                if i.left:
                    tmpstack.append(i.left)
                if i.right:
                    tmpstack.append(i.right)
            result.append(tmp)
            stack = tmpstack
        return result

35、之字形打印数组

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
思路:在第34题的基础上,遍历result,遇到二维数组中奇数下标就反转

class Solution:
    def Print(self, pRoot):
        # write code here
        if not pRoot:
            return []
        stack = []
        result = []
        stack.append(pRoot)
        while stack:
            tmp = []
            tmpstack = []
            for i in stack:
                tmp.append(i.val)
                if i.left:
                    tmpstack.append(i.left)
                if i.right:
                    tmpstack.append(i.right)
            result.append(tmp)
            stack = tmpstack
        for j in range(len(result)):
            if j%2 == 1:
                #result[j] = result[j][::-1]
                result[j].reverse()
        return result

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

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路:
解法1:如果一个数组中有数字超过半数,那么对该数组排序后,最中间的数字一定是出现次数过半的。也即中位数,即长度为n的数组中第n/2大的数字

class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        numbers.sort()
        middle = numbers[len(numbers)/2]
        if numbers.count(middle) > len(numbers)/2:
            return middle
        else:
            return 0

解法2:利用dict遍历,遇到相同数字就+1

解法3:第二种思路根据数组的特点,出现次数超过一半的数,他出现的次数比其他数字出现的总和还要多,因此可以最开始保存两个数值:数组中的一个数字以及它出现的次数,然后遍历,如果下一个数字等于这个数字,那么次数加一,如果不等,次数减一,当次数等于0的时候,在下一个数字的时候重新复制新的数字以及出现的次数置为1,直到进行到最后,然后再验证最后留下的数字是否出现次数超过一半,因为可能前面的次数依次抵消掉,最后一个数字就直接是保留下来的数字,但是出现次数不一定超过一半。

class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        if len(numbers) == 0:
            return 0
        num = numbers[0]
        count = 0
        for i in numbers:
            if i == num:
                count += 1
            else:
                count -= 1
            if count == 0:
                num = i
                count = 1
        ##验证
        count = 0
        for j in numbers:
            if j == num:
                count += 1
        return num if count > len(numbers)/2 else 0

37、数组中只出现一次的数字

题目:整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。
法一:借助dict遍历,统计出现次数,然后寻找dict中值为1的键

class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        # write code here
        if not array:
            return None
        dict = {}
        for i in array:
            if i in dict.keys():
                dict[i] += 1
            else:
                dict[i] = 1
        result = []
        for j in dict.keys():
            if dict[j] == 1:
                result.append(j)
        return result

法二:利用位运算

class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self,array):
        if len(array) == 0:
            return None
        eor1 = 0
        eor2 = 0
        for i in array:
            eor1 ^= i
        rightOne = eor1&(~eor1+1)   ###取反+1再跟自己与
        for j in array:
            if (j & rightOne) != 0:
                eor2 ^= j
        return eor2,eor2^eor1

38、最小的K个数

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
思路一:可借助python包heapq中的nlargest和nsmallest进行筛选,但注意判断返回空的条件

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if tinput == None or len(tinput) < k or len(tinput) <= 0 or k <= 0:  ##此处的判断不要忘
            return []
        import heapq
        result = heapq.nsmallest(k,tinput)
        return result

思路二:面试不能写成思路一那样,老老实实写堆排序,然后取前k个,注意升序用大根堆,降序用小根堆。

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if not arr or k < 0 or k > len(arr):
            return [] 
        heapsize = len(arr)
        for i in range(heapsize//2 - 1,-1,-1):
            self.heapify(arr, i, heapsize - 1)
        while heapsize > 0: 
            arr[0], arr[heapsize - 1] = arr[heapsize - 1], arr[0]
            heapsize -= 1
            self.heapify(arr,0,heapsize)
        return arr[:k]

    def heapify(self,arr,i,heapsize):
        left = 2 * i + 1
        large = i
        while left < heapsize:
            if left + 1 < heapsize and arr[left + 1] > arr[left]:
                left += 1  
            if arr[i] < arr[left]:
                large = left
                arr[i], arr[large] = arr[large], arr[i]
            else:
                break
            i = large
            left = 2 * i + 1

39、字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
思路:利用迭代,任务可以分成两部分:第一部分将第一个字符与后面所有字符交换,第二部分就是固定第一个字符,求后面所有字符的排列。而后面字符的排列忧可分成两个部分,后面字符的第一个字符于她后面所有字符交换…
Python中itertools函数库展示了对迭代器进行操作的强大功能,可以用itertools.permutations()输出字符的全排列,再用set属性去重,最后排序。

class Solution:
    def Permutation(self, ss):
        # write code here
        import itertools
        if not ss:
            return []
        if len(ss) == 1:
            return list(ss)
        return sorted(list(set(map(''.join,itertools.permutations(ss)))))

然而面试的时候不能这么写。
确定第一个字母,剩下的位置递归进行求解

class Solution:
    def Permutation(self, ss):
        if len(ss) <= 1:
            return ss
        res = set()
        # 遍历字符串,固定第一个元素,第一个元素可以取a,b,c...,然后递归求解
        for i in range(len(ss)):
            for j in self.Permutation(ss[:i] + ss[i+1:]): # 依次固定了元素,其他的全排列(递归求解)
                res.add(ss[i] + j) # 集合添加元素的方法add(),集合添加去重(若存在重复字符,排列后会存在相同,如baa,baa)
        return sorted(res)         # sorted()能对可迭代对象进行排序,结果返回一个新的list

40、字符串的组合

41、数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。
思路:遍历一下就OK

class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        if not data or not k or k not in data:
            return False
        count = 0
        for i in data:
            if i == k:
                count += 1
        return count

或者直接返回:

class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        if not data or not k or k not in data:
            return False
        return data.count(k)

42、连续子数组的最大和

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
思路:可以通过分析实际数组的加减情况来决定。比如前几个数相加起来小于0或者还不如下一个数大,那么前面的这几个数都不用考虑了,直接从下一个数开始。注意在相加的过程中时刻保存目前出现的最大和。

class Solution:
    def FindGreatestSumOfSubArray(self, array):
        # write code here
        if not array:
            return 0
        curSum = 0
        GreatSum = None
        for i in range(len(array)):
            if curSum <= 0:
                curSum = array[i] 
            else:
                curSum += array[i]
            if curSum > GreatSum:
                GreatSum = curSum
        return GreatSum

class Solution:
    def FindGreatestSumOfSubArray(self, array):
        # write code here
        if len(array)==1:
            return array[0]
        cur = pos = array[0]
        for i in range(1,len(array)):
            pos = max(pos+array[i],array[i])
            cur = max(cur,pos)
        return cur

43、整数中1出现的次数(从1到n整数中1出现的次数)

求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
解法1:遍历,全都变成字符串,然后对字符串组再进行遍历,如果字符串中含有1,就对count变量加上1在该字符串中出现的次数

class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        if not n:
            return 0
        s = []
        count = 0
        for i in range(1,n+1):
            s.append(str(i))
        for j in s:
            if '1' in j:
                count += j.count('1')
        return count

解法 2:
剑指offer刷题记录(Python)_第5张图片

class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        if not n:
            return 0
        cur,before,after,ans = 0,0,0,0   ##当前位的值,高位值,低位值
        i = 1        ###当前位
        while (n):
            cur = (n//i)%10        ###当前位数字
            before = (n//i)//10   ###高位数字
            after = n%i  ###低位数字
            if cur == 0:
                ans += before * i
            elif cur == 1:
                ans += before * i + (after+1)
            elif cur > 1:
                ans += (before+1) * i
            i *= 10
        return ans

复杂度太大,不能这么写,如下:
剑指offer刷题记录(Python)_第6张图片

class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        if n<1:  return 0
        if n==1: return 1
        last,ans,pos = 0,0,1
        while(n):
            num = n%10    ####当前位数字
            n = n/10       ###高位数字
            ans += pos*n   ###基础数量
            if num>1:
                ans+=pos    ####当前位数字大于1,则ans多一个当前位数
            elif num==1:
                ans+=(last+1)    ###当前位数字等于1,则ans还要受到低位数字的影响
            last = last+num*pos   ###计算低位数字
            pos*=10      ##i向高位移一位
        return ans

44、把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

class Solution:
    def PrintMinNumber(self, numbers):
        # write code here
        import itertools
        if not numbers:
            return ''
        s = [str(x) for x in numbers]
        s = list(set(map(''.join,itertools.permutations(s))))
        s = [int(y) for y in s]
        return min(s)

45、第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

丑数

题目:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
方法1:逐个判断是否为丑数,但时间复杂度过大

class Solution:
    def GetUglyNumber_Solution(self, index):
        # write code here
        if index <= 0:
            return 0
        number = 1
        uglyfound = 0
        ans = []
        while uglyfound < index:
            if self.isugly(number):
                uglyfound += 1
            number += 1
        return number
    
    def isugly(self,number):
        while number%2 == 0:
            number /= 2
        while number%3 == 0:
            number /= 3
        while number%5 == 0:
            number /= 5
        if number == 1:
            return True
        else:
            return False

方法2:创建保存已有的丑数,通过一些trick进行后面丑数的计算
丑数是另一个丑数×2或×3或×5得来的,if我们将已有的丑数排序放在一个数组里,那么第一个比当前最大丑数还大的丑数一定是某个已有丑数×2或×3或×5中的最小值,我们只需记下这个已有丑数的位置(我称为临界值丑数),每次生成新的丑数的时候去更新临界值丑数的下标就可以找到下一个丑数了

class Solution:
    def GetUglyNumber_Solution(self, index):
        # write code here
        if index <= 0:
            return 0
        uglylist = [1]
        indextwo = 0    ####包含因子2的丑数下标
        indexthree = 0   ###包换因子3的丑数下标
        indexfive = 0     ###包含因子5的丑数下标
        for i in range(index-1):
            ####下一个比当前最大丑叔的数一定是已有丑数*2/*3*5中的最小值
            newugly = min(uglylist[indextwo]*2,uglylist[indexthree]*3,uglylist[indexfive]*5)
            uglylist.append(newugly)
            ###更新临界值丑数的下标
            if newugly %2 == 0:
                indextwo += 1
            if newugly %3 == 0:
                indexthree += 1
            if newugly %5 == 0:
                indexfive += 1
        return uglylist[-1]

第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

class Solution:
    def FirstNotRepeatingChar(self, s):
        # write code here
        if not s or len(s)== 0:
            return -1
        for i,v in enumerate(s):
            if s.count(v) == 1:
                return i
                break

也可以借助Python中的字典(类似于哈希表)来进行key-value的对应

数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
法一:遍历比较,时间复杂度O(n^2),空间复杂度o(1)

class Solution:
    def InversePairs(self, data):
        # write code here
        if not data or len(data) <= 0:
            return 0%1000000007
        ans = 0
        for i in range(len(data)-1,0,-1):
            for j in range(i-1,-1,-1):
                if data[j] > data[i]:
                    ans += 1
        return ans%1000000007

法二:利用index进行求解:即首先对原数组进行复制(这里涉及到深/浅拷贝,自己还没弄明白,接下来会讲清楚),对复制的数组进行排序,然后对每一个数值的index在新旧数组中的位置进行比较,比如原数组[1,2,3,4,5,6,7,0],排序之后是[0,1,2,3,4,5,6,7],对于排序后的第一个元素0来说,他排序移动的位置是从原来的index=7移动到index=0,所以对0来说,他在原位置时,前面有7个数比他大,即构成7个逆序对。同理对新数组的每一个元素进行比较,则可统计出所有逆序对。(真-神仙解法)

class Solution:
    def InversePairs(self, data):
        if len(data) <= 0:
            return 0
        count = 0
        b = data[:]
        b.sort()
        i = 0
        while i < len(b):
            count += data.index(b[i])
            data.remove(b[i])
            i += 1
        return count

法三:归并排序的变形,时间复杂度O(n*logn),空间复杂度o(n)

count = 0
class Solution:
    def InversePairs(self, data):
        global count    ####声明全局变量
        def MergeSort(lists):
            global count
            if len(lists) <= 1:
                return lists
            num = int( len(lists)/2 )
            left = MergeSort(lists[:num])
            right = MergeSort(lists[num:])
            r, l=0, 0
            result=[]
            while l

扑克牌顺子

class Solution:
    def IsContinuous(self, numbers):
        # write code here
        if numbers == None or len(numbers) == 0:
            return False
        numbers.sort()
        c = 0
        for i in numbers:
            if i == 0:
                c += 1      ####统计0的个数
        q = 0
        s = [x for x in numbers if x!= 0]
        if len(s) == 1:   ###如果只有1个非零值,那么总可以组成顺子
            return True
        else:
            for j in range(len(s)-1):
                if s[j+1] == s[j]:    ###如果有对子,肯定不能组成顺子
                    return False
                    break
                elif s[j+1] - s[j] > 1:
                    q += s[j+1] - s[j] - 1     ###统计空缺值个数
            if c == q:
                return True
            else:
                return False

两个链表的第一个公共结点

class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        # write code here
        if not pHead1 or not pHead2:
            return None
        stack = []
        while pHead1 != None:
            stack.append(pHead1)
            pHead1 = pHead1.next
        while pHead2 != None:
            if pHead2 in stack:
                return pHead2
                break
            else:
                pHead2 = pHead2.next

思考:设链表pHead1的长度为a,到公共结点的长度为l1;链表pHead2的长度为b,到公共结点的长度为l2,有a-l1 = b-l2

和为S的两个数字

法一:遍历,时间复杂度O(n^2)

class Solution:
    def FindNumbersWithSum(self, array, tsum):
        # write code here
        if len(array) == 0 or not tsum:
            return []
        result = []
        for i in range(len(array)):
            for j in range(i+1,len(array)):
                if array[i] + array[j] == tsum:
                    result.append([array[i],array[j]])
        if not result:
            return []
        else:
            c = []
            for i in result:
                c.append(i[0]*i[1])
        return result[c.index(min(c))]

法二:利用两个指针分贝在头和尾(即分别指向最小和最大值),和大了指针2向左移,和小了指针1向右移。当两个数的和一定的时候, 两个数字的间隔越大, 乘积越小,所以直接输出查找到的第一对数即可(这样可避免内存溢出)

class Solution:
    def FindNumbersWithSum(self, array, tsum):
        # write code here
        if not array or len(array) <= 0 or not tsum or array[-1] + array[-2] < tsum: ###注意几种情况下不存在这两个数
            return []  
        p1 = 0
        p2 = len(array)-1
        while p2 > p1:
            cursum = array[p1] + array[p2]
            if cursum > tsum:
                p2 -= 1
            elif cursum < tsum:
                p1 += 1
            elif cursum == tsum:
                return [array[p1],array[p2]]
                break
        else:                            ##如果一直找不到满足条件的两个数,就返回空集合
            return []

和为S的连续正数序列

思路:借鉴上一题,有一点小变化。采用两个变量small和big,表示所取序列的左边界和右边界。首先判断在这个范围内的值是否和为S,如果满足条件则加入到结果中,然后扩大big接着寻找下一组;如果不满足条件,两种情况:1、和比S大,那么就让和减去small,并且small+1(相当于缩小了所选序列的范围来减小和,那自然先要减去small再缩范围);2、和比S小,那么同理就要扩大范围,先将big+1,再另和加上新big。得到的和再与S比较,相等就加入结果,不等再进行范围缩小or扩大。如此循环下来,注意循环的范围是small最多增加到(1+s)/2为止,因为我们希望满足条件的序列至少要包含两个数。

class Solution:
    def FindContinuousSequence(self, tsum):
        # write code here
        if tsum < 3:
            return []
        small = 1
        big = 2
        a = []
        cursum = small + big
        while small < (tsum+1)//2:
            if cursum == tsum:
                a.append([i for i in range(small,big+1)])
                big += 1
                cursum += big
            elif cursum > tsum:
                cursum -= small
                small += 1
            else:
                big += 1
                cursum += big
        return a

不用加减乘除做加法

二叉搜索树的最低公共祖先

二叉搜索树:或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值

###两节点最低公共祖先
def lowestAncestor(root,a,b):
    if root == None or root == a or root == b:
        return root
    left = lowestAncestor(root.left,a,b)
    right = lowestAncestor(root.right,a,b)
    if left != None and right != None:
        return head
    return left if not left else right

二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路:
后序遍历 的序列中,最后一个数字是树的根节点 ,数组中前面的数字可以分为两部分:第一部分是左子树节点 的值,都比根节点的值小;第二部分 是右子树 节点的值,都比 根 节点 的值大,后面用递归分别判断前后两部分 是否 符合以上原则

class Solution:
    def VerifySquenceOfBST(self, sequence):
        # write code here
        if sequence==None or len(sequence)==0:
            return False
        length=len(sequence)
        root=sequence[length-1]
        # 在二叉搜索 树中 左子树节点小于根节点
        for i in range(length):
            if sequence[i]>root:
                break
        # 二叉搜索树中右子树的节点都大于根节点
        for j  in range(i,length):
            if sequence[j]0:
            left=self.VerifySquenceOfBST(sequence[0:i])
        # 判断 右子树是否为二叉树
        right=True
        if i

二叉树中和为某一值的路径

输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
思路:DFS算法

class Solution:
    # 返回二维列表,内部每个列表表示找到的路径
    def FindPath(self, root, sum):
        if not root: return []
        if root.left == None and root.right == None:
            if sum == root.val:
                return [[root.val]]
            else:
                return []
        a = self.FindPath(root.left, sum - root.val) + self.FindPath(root.right, sum - root.val)
        return [[root.val] + i for i in a]

二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
法一:递归。如果根节点有左子树而无右子树,那么树的深度就是左子树的深度+1;反之为右节点的深度+1;如果既有左子树又有右子树,那么树深为左、右子树的深度较大值+1。采用递归可以实现

class Solution:
    def TreeDepth(self, pRoot):
        # write code here
        if pRoot == None:
            return 0
        if pRoot.left == None and pRoot.right == None:
            return 1
        return max(self.TreeDepth(pRoot.left),self.TreeDepth(pRoot.right)) + 1

法二:采用非递归的层次遍历

class Solution:
    def TreeDepth(self, pRoot):
        # write code here
        if not pRoot:
            return 0
        stack = []
        node = pRoot
        layer = 1
        stack.append((node,layer))
        while stack:
            node,layer = stack.pop(0)    ####为了寻找stack的第一个点的左右子节点
            deep = layer       #####用deep记录当前层数
            if node.left:
                stack.append((node.left,layer + 1))
            if node.right:
                stack.append((node.right,layer + 1))
        return deep

平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

注:任意节点的左、右子树的深度相差不超过1的数是平衡二叉树

法一:结合树的深度进行判断,但每个点都要计算树的深度,复杂度O(n^2)

class Solution:
    def TreeDepth(self, pRoot):
        # write code here
        if pRoot == None:
            return 0
        if pRoot.left == None and pRoot.right == None:
            return 1
        return max(self.TreeDepth(pRoot.left),self.TreeDepth(pRoot.right)) + 1
    def IsBalanced_Solution(self, pRoot):
        # write code here
        if not pRoot:
            return True
        return abs(self.TreeDepth(pRoot.left)-self.TreeDepth(pRoot.right)) <= 1

法二:采用后序遍历,自下而上,每个点只要遍历一遍就行,复杂度O(n)

class Solution:
    def IsBalanced_Solution(self, p):
        return self.dfs(p) != -1
    def dfs(self, p):
        if p is None:
            return 0
        left = self.dfs(p.left)
        if left == -1:
            return -1
        right = self.dfs(p.right)
        if right == -1:
            return -1
        if abs(left - right) > 1:
            return -1
        return max(left, right) + 1

复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路:
1、遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
3、拆分链表,将链表拆分为原链表和复制后的链表

剑指offer刷题记录(Python)_第7张图片

 class RandomListNode:
#     def __init__(self, x):
#         self.label = x
#         self.next = None
#         self.random = None
class Solution:
    # 返回 RandomListNode
    def Clone(self, pHead):
        # write code here
        if not pHead:
            return None
        self.CloneNodes(pHead)
        self.ConnectRandomNode(pHead)
        return self.Reconnectnodes(pHead)
    #####复制复杂链表的每一个节点,将复制的节点接在原始节点的后面
    def CloneNodes(self,pHead):
        pNode = pHead
        while pNode:
            pCloned = RandomListNode(0)
            pCloned.label = pNode.label
            pCloned.next = pNode.next
            pNode.next = pCloned
            pNode = pCloned.next
    #####将复制后链表中的复制结点的random指针链接到被复制结点random指针的后一个结点
    def ConnectRandomNode(self,pHead):
        pNode = pHead
        while pNode:
            pCloned = pNode.next
            if pNode.random != None:
                pCloned.random = pNode.random.next
            pNode = pCloned.next
    #####拆分链表, 将原始链表的结点组成新的链表, 复制结点组成复制后的链表
    def Reconnectnodes(self,pHead):
        pNode = pHead
        pClonedHead = pClonedNode = pNode.next
        pNode.next = pClonedNode.next
        pNode = pNode.next
        while pNode:
            pClonedNode.next = pNode.next
            pClonedNode = pClonedNode.next
            pNode.next = pClonedNode.next
            pNode = pNode.next
        return pClonedHead

这题思路很简单,就是代码实现起来有点绕~
faer :哈希表法

class Solution:
    # 返回 RandomListNode
    def Clone(self, pHead):
        # write code here
        if pHead == None:
            return pHead
        node_dict = {}
        node_dict[pHead] = RandomListNode(pHead.label)
        tmp = pHead
        while pHead:
            random = pHead.random
            nextnode  = pHead.next
            if random != None:
                if random not in node_dict:
                    node_dict[random] = RandomListNode(random.label)
                node_dict[pHead].random = node_dict[random]
            if nextnode != None:
                if nextnode not in node_dict:
                    node_dict[nextnode] = RandomListNode(nextnode.label)
                node_dict[pHead].next = node_dict[nextnode]
            pHead = pHead.next
        return node_dict[tmp]

构建乘积数组

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。
剑指offer刷题记录(Python)_第8张图片
剑指offer刷题记录(Python)_第9张图片

class Solution:
    def multiply(self, A):
        # write code here
        if not A or len(A) == 0:
            return []
        size = len(A)
        B = [1]*size
        for i in range(1,size):        ####下三角
            B[i] = B[i-1] *A[i-1]
        temp = 1
        for i in range(size-2,-1,-1):    ####上三角
            temp *= A[i+1]     ###累乘
            B[i] *= temp
        return B

时间复杂度O(n)

字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。如果当前字符流没有存在出现一次的字符,返回#字符。
注意类中的各个变量

class Solution:
    # 返回对应char
    def __init__(self):
        self.s = ''
    def FirstAppearingOnce(self):
        # write code here
        res = filter(lambda x: self.s.count(x)==1,self.s)
        return res[0] if res else '#'
    def Insert(self, char):
        # write code here
        self.s += char

二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:要求双向链表是排序的,则可以巧用中序遍历与搜索二叉树之间的相似性,先中序遍历,将所有的节点保存到一个列表中。对这个list[:-1]进行遍历,每个节点的right设为下一个节点,下一个节点的left设为上一个节点。(注意改变指针时,self.arr只取到倒数第二个元素,是因为例如,对于6个元素的链表,其指针只有5个,比元素个数少一个。)

class Solution: 
    def Convert(self, pRootOfTree):
        # write code here
        if not pRootOfTree:
            return pRootOfTree
        if pRootOfTree.left == None and pRootOfTree.right == None:
            return pRootOfTree
        self.arr = []
        self.midTravel(pRootOfTree)
        for i,v in enumerate(self.arr[:-1]):
            v.right = self.arr[i+1]
            self.arr[i+1].left = v
        return self.arr[0]
            
    def midTravel(self,root):
        if not root:return
        self.midTravel(root.left)
        self.arr.append(root)
        self.midTravel(root.right)

序列化二叉树

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

class Solution:
    def Serialize(self, root):
        # write code here
        if root == None:
            return '#'
        return str(root.val)+','+self.Serialize(root.left)+','+self.Serialize(root.right)

    def Deserialize(self, s):
        # write code here
        if not s or len(s) <= 0:
            return
        s = s.split(',')
        def doit(s):
            if not s:
                return
            x = s.pop(0)
            if x == '#':
                return
            else:
                root = TreeNode(int(x))
                root.left = doit(s)
                root.right = doit(s)
            return root
        return doit(s)

二叉搜索树的第k个结点

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
思路:利用中序遍历,将所有节点记录在一个数组中,在返回第k-1个元素即可(注意判断k是否大于数组长度)

class Solution:
    # 返回对应节点TreeNode
    def KthNode(self, pRoot, k):
        # write code here
        if not pRoot or k<=0:
            return 
        a = []
        def zhong(pRoot):    ##中序遍历
            if not pRoot:
                return 
            zhong(pRoot.left)
            a.append(pRoot)
            zhong(pRoot.right)
        zhong(pRoot)
        if k > len(a):     ##特别注意这里要判断k的值是否大于数组长度
            return 
        return a[k-1]

同样的思路,更简洁的写法(感谢牛客网<华科平凡>提供的简洁代码):

class Solution:
    # 返回对应节点TreeNode
    def KthNode(self, pRoot, k):
        self.res=[]
        self.dfs(pRoot)
        return self.res[k-1] if 0

数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
法一:通过数组长度判断奇偶,输出

class Solution:
    def __init__(self):
        self.s = []
    def Insert(self, num):
        # write code here
        self.s.append(num)
    def GetMedian(self,s):
        # write code here
        self.s.sort()
        length = len(self.s)
        if not self.s or length <= 0:
            return  
        if length%2 == 0:
            return (self.s[length//2-1]+self.s[length//2])/2.0   #####要除以2.0,而不是2
        else:
            return self.s[int(length//2)]

法二:大根堆小跟堆
思路:建立一个大根堆和一个小根堆,然后像两个堆添加数据,规则为:第一个数据总是放在大根堆里,对于任一数据,如果小于等于大根堆堆顶值,则放入大根堆,否则放入小根堆,要注意的是,每添加一个元素都要比较两个堆的size,如果一个的size比另一个的size多2,则需要把size较大堆的堆顶元素弹出,并放入size较小的堆。添加数据完成后,比较两堆的size,如果都为0,返回None;size相同,返回两堆堆顶元素/2;否则返回size大的那个堆的堆顶元素

class MedianFinder:
    import heapq 

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.maxheap = []
        self.minheap = []
        
        
    def addNum(self, num: int) -> None:
         #第一个数(即大根堆为空)或者小于大根堆堆顶元素,就放到大根堆里,否则放到小根堆
        if self.maxheap == [] or num <= -self.maxheap[0]:
            heapq.heappush(self.maxheap,-num)
        else:
            heapq.heappush(self.minheap,num)
        if len(self.maxheap) == len(self.minheap)-2: #小根堆size大
            heapq.heappush(self.maxheap,-heapq.heappop(self.minheap))
        if len(self.minheap) == len(self.maxheap)-2: #大根堆size大
            heapq.heappush(self.minheap,-heapq.heappop(self.maxheap))
             

    def findMedian(self) -> float:  
        if len(self.maxheap) + len(self.minheap) == 0:
            return None
        if len(self.maxheap) == len(self.minheap):
            return (-self.maxheap[0] + self.minheap[0]) / 2
        else:
            return -self.maxheap[0] if len(self.maxheap) > len(self.minheap) else self.minheap[0]

正则表达式匹配

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

class Solution:
    # s, pattern都是字符串
    def match(self, s, pattern):
        # write code here
        if len(s) == 0 and len(pattern) == 0:
            return True
        if len(s) != 0 and len(pattern) == 0:
            return False

        #第二个字符为‘*’的时候
        if len(pattern) > 1 and pattern[1] == '*':
            if len(s) > 0 and (s[0] == pattern[0] or pattern[0] == '.'):
                ###'*'分别匹配0个,1个,多个的情况:
                # 1. a 和 a*a 中这情况, 这时候星号代表0个a, 因此s不需要右移, pattern需要右移两位
                # 2. abc 和 a*bc 这种情况, 星号代表了1个a, s右移一位, pattern右移两位继续比较
                # 3. aaa 和 a*a 这种情况, 星号代表了多个a, 因此s需要不断右移一位继续比较
                return (self.match(s,pattern[2:]) or self.match(s[1:],pattern[2:]) or self.match(s[1:],pattern))
            else:
                #如果s[0]不等于pattern[0],且pattern[0]不为'.',那么第一位比较不成功,pattern必须后移两位继续比较后面是否能和s第一位匹配
                return self.match(s,pattern[2:])

        #第二个字符不为'*'的时候
        if len(s)>0 and (s[0] == pattern[0] or pattern[0] == '.'):
            return self.match(s[1:],pattern[1:])
        return False

法二:动态规划
以字符串s各元素为横轴,字符串p各元素为纵轴建立dp表,dp[i][j]表示s的前i位和p的前j位是否匹配。要特别注意dp表的第一行

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        dp = [[False]*(len(p)+1) for _ in range(len(s)+1)]
        dp[0][0] = True
        # print(dp)
        
        #对于类似s=aab和p=c*a*b这种情况,dp表第一行如下:
        for i in range(len(p)):
            if p[i] == '*' and dp[0][i-1]:
                dp[0][i+1] = True
        # print(dp)    
        #dp表其他行
        for i in range(len(s)):
            for j in range(len(p)):
                #p的某一位不为‘*’
                if s[i] == p[j] or p[j] == '.':
                    dp[i+1][j+1] = dp[i][j]
                #p的某一位为‘*’
                elif p[j] == '*':
                    #p的前一位不与
                    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+1][j-1] or dp[i][j+1] or dp[i+1][j]
        # print(dp)
        return dp[-1][-1]

你可能感兴趣的:(刷题,剑指offer)