《剑指offer》python版本 分类刷题(下)

本文详细介绍了使用Python实现《剑指offer》中涉及的算法题,包括队列、栈、滑动窗口最大值、矩阵路径、回溯法、动态规划等解题思路和代码实现,覆盖了数据结构和算法多个方面。
摘要由CSDN通过智能技术生成

7/30

  1. 用两个栈实现队列
    题目描述
    用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
    解题思路
    队列的Push就是直接进栈;Pop:节点进入stack1,然后再stack1的元素pop出,push到stack2中,stack2再pop出来就是先进先出的顺序了。
    步骤:
  • 判断stack2是否为空,不为空就pop
  • 将stack1的节点pop出,然后push进stack2,再将stack2的元素pop。
class Solution:
    def __init__(self):
        self.stack1 = []
        self.stack2 = []
    def push(self, node):
        # write code here
        self.stack1.append(node)
    def pop(self):
        if self.stack2:
            return self.stack2.pop()
        while self.stack1:
            self.stack2.append(self.stack1.pop())
        return self.stack2.pop()

题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

  1. 包含min函数的栈
    题目描述
    定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
    解题思路
    借助最小元素辅助栈。每次压栈操作时, 如果压栈元素比当前最小元素更小, 就把这个元素压入最小元素栈, 原本的最小元素就成了次小元素. 同理, 弹栈时, 如果弹出的元素和最小元素栈的栈顶元素相等, 就把最小元素的栈顶弹出.
class Solution:
    def __init__(self):
        self.stack1 = []
        self.assist = []
    def push(self, node):
        # 压栈的时候,如果主栈的压入大于辅助栈压入,辅助栈不压;小于等于,则同时压入
        if not self.assist: #辅助栈如果为空,则压入
            self.assist.append(node)
        else: # 比较要压入元素和辅助栈当前栈顶元素(也就是最小元素),判断是否压入
            self.assist.append(min(self.assist[-1],node))
        self.stack1.append(node)
    def pop(self):
        # 出栈的时候,辅助栈也要跟着一块出,这么做是要保证辅助栈栈顶元素始终是主栈元素中最小的
        if self.stack1:
            self.assist.pop()
            return self.stack1.pop()
    def top(self):
        if self.stack1:
            return self.stack1[-1]
    def min(self):
        if self.assist:
            return self.assist[-1]
  1. 栈的压入、弹出序列
    题目描述
    输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
    解题思路:看的别人的,觉得很好理解

假设有一串数字要将他们压栈: 1 2 3 4 5

如果这个栈是很大很大,那么一次性全部压进去,再出栈:5 4 3 2 1

但是,如果这个栈高度为4,会发生什么? 1 2 3 4都顺利入栈,但是满了,那么要先出栈一个,才能入栈,那么就是先出4,然后压入5,随后再全部出栈:4 5 3 2 1

那么我总结了所有可能的出栈情况:

5 4 3 2 1//栈高度为5

4 5 3 2 1//栈高度为4

3 4 5 2 1//栈高度为3

2 3 4 5 1//栈高度为2

1 2 3 4 5//栈高度为1

借助一个辅助的栈,遍历压栈的顺序,依次放进辅助栈中。

对于每一个放进栈中的元素,栈顶元素都与出栈的popIndex对应位置的元素进行比较,是否相等,相等则popIndex++,再判断,直到为空或者不相等为止。

class Solution:
    def IsPopOrder(self, pushV, popV):
        # write code here
        stack = []
        for i in pushV:
            stack.append(i)
            while stack[-1] == popV[0]:
                stack.pop()
                popV.pop(0)
                if not popV:
                    return True
        return False

7/31 and 8/1

  1. 滑动窗口的最大值
    题目描述
    给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
    解题思路:
    用一个队列实现,初始化时,size个数字进入队列,找出队列中的最大值;随后一个数字出队,一个数字进队,直到全部进队。虽然也通过了,但我觉得自己代码写的很烂。
class Solution:
    def maxInWindows(self, num, size):
        # write code here
        """
        用一个队列实现,初始化时,size个数字进入队列,找出队列中的最大值;随后一个数字出队,一个数字进队,直到全部进队
        """
        if size == 0 or size > len(num):
            return []
        queue = []
        res = []
        for i in range (size):
            queue.append(num[i])
        res.append(max(queue))
        for i in range(size, len(num)):
            queue.pop(0)
            queue.append(num[i])
            res.append(max(queue))
        return res
  1. 矩阵中的路径

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

解题思路:
典型的回溯法题目。我觉得第一次做这种题直接去看书上解析了,自己不可能想出来 ==
打算整理一篇回溯法的博客。
首先,遍历这个矩阵,我们很容易就能找到与字符串str中第一个字符相同的矩阵元素ch。然后遍历ch的上下左右四个字符,如果有和字符串str中下一个字符相同的,就把那个字符当作下一个字符(下一次遍历的起点),如果没有,就需要回退到上一个字符,然后重新遍历。为了避免路径重叠,需要一个辅助矩阵来记录路径情况。

下面代码中,当矩阵坐标为(i,j)的格子和路径字符串中下标为pathLength的字符一样时,从4个相邻的格子(i,j-1)、(i-1,j)、(i,j+1)以及(i+1,j)中去定位路径字符串中下标为pathLength+1的字符。

如果4个相邻的格子都没有匹配字符串中下标为pathLength+1的字符,表明当前路径字符串中下标为pathLength的字符在矩阵中的定位不正确,我们需要回到前一个字符串(pathLength-1),然后重新定位。

一直重复这个过程,直到路径字符串上所有字符都在矩阵中找到格式的位置(此时str[pathLength] == ‘\0’)

class Solution:
    def hasPath(self, matrix, rows, cols, path):
        # write code here
        if not matrix and rows <= 0 and cols <= 0 and path == None:
            return False
        boolmatrix = [0] * (rows * cols)
        pathLength = 0
        for i in range(rows):
            for j in range(cols):
                if self.hasPathCore(matrix, rows, cols, i, j, path, pathLength, boolmatrix):
                    return True
        return False

    def hasPathCore(self, matrix, rows, cols, i, j, path, pathLength, boolmatrix):
        if len(path) == pathLength:
            return True

        hasNextPath = False
        if (i >= 0 and i < rows and j >= 0 and j < cols and
                matrix[i * cols + j] == path[pathLength] and not boolmatrix[i * cols + j]):
            pathLength += 1
            boolmatrix[i * cols + j] = True
            # 进行该值的上下左右的递归(周围是否存在下一个路径点)
            hasNextPath = (self.hasPathCore(matrix, rows, cols, i - 1, j, path, pathLength, boolmatrix)
                           or self.hasPathCore(matrix, rows, cols, i, j + 1, path, pathLength, boolmatrix)
                           or self.hasPathCore(matrix, rows, cols, i + 1, j, path, pathLength, boolmatrix)
                           or self.hasPathCore(matrix, rows, cols, i, j - 1, path, pathLength, boolmatrix))
            # 对标记矩阵进行布尔值标记
            if not hasNextPath:  # 说明周围4个点都不存在下一路径
                pathLength -= 1  # 回到前一个字符
                boolmatrix[i * cols + j] = False  # 将该点重新设为未标记
        return hasNextPath
  1. 机器人的运动范围

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

解题思路:
本题与矩阵中的路径思想一致,都是使用回溯法解决问题,只不过对于位置的约束条件的不同。
将地图全部置1,遍历能够到达的点,将遍历的点置0并令计数+1.这个思路在找前后左右相连的点很有用。

 class Solution:

    def movingCount(self, threshold, rows, cols):
        # write code her
        Matrix = [[1 for i in range(cols)] for j in range(rows)]
        count = self.findway(threshold, 0, 0, Matrix)
        print(Matrix)
        return count

    def findway(self, threshold, i, j, Matrix):
        count = 0

        if i >= 0 and j>= 0 and i < len(Matrix) and j < len(Matrix[0]) and Matrix[i][j] \
                and sum(map(int, str(i) + str(j))) <= threshold:
            Matrix[i][j] = 0
            count = 1+self.findway(threshold, i+1, j, Matrix)\
                    + self.findway(threshold, i-1, j, Matrix)\
                    + self.findway(threshold, i, j+1, Matrix)\
                    + self.findway(threshold, i, j-1, Matrix)
        return count

8/2

  1. 求1+2+3+…+n

题目描述
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

class Solution:
    def Sum_Solution(self, n):
        # write code here
        sum = n
        temp = sum and self.Sum_Solution(n-1)
        sum = sum + temp
        return sum
  1. 二进制中1的个数
    如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

class Solution:
    def NumberOf1(self, n):
        # write code here
        count = 0
        while n:
            n = (n - 1) & n
            count = count + 1
        return count

别的解法:

class Solution:
    def NumberOf1(self, n):
        # write code here
        if n>=0:
            return bin(n).count('1')
        else:
            return bin(2**32+n).count('1')
  1. 数值的整数次方
    题目描述
    给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

保证base和exponent不同时为0

这一题的意义是?

class Solution:
    def Power(self, base, exponent):
        # write code here
        return base**exponent

别的解法:使用快速幂
传统的幂运算,是对底数进行连乘,时间复杂度为o(n),例如:2^13 = 222……*2,连乘十三次。
利用指数的二进制,可以实现复杂度为o(logn)的幂运算。还是以2^13为例,13的二进制为1101,因此2的13次方可以分解成以下形式:
在这里插入图片描述

和13的二进制1101相对比,只要二进制为1的位,就有权重,权重为2^(i-1),i表示第几位,1101从右到左,依次为第1位,第2位,第3位,第4位。
下面的工作就是如何确定二进制中的哪一位为1,这里可以利用位运算中的&和>>运算。由于1的二进制除了第一位是1,其他的全是0,因此可以利用n&1是否为0来判断n的二进制的当前最低位是否为1,如果n&1等于0,说明当前n的最低位不为1。利用右移运算来逐位读取。

# -*- coding:utf-8 -*-
class Solution:
    def Power(self, base, exponent):
        # write code here
        e = abs(exponent)
        res = 1
        while e:
            if e & 1:  # 判断当前的最后一位是否为1,如果为1的话,就需要把之前的幂乘到结果中。
                res *= base
            base *= base  # 一直累乘,如果最后一位不是1的话,就不用了把这个值乘到结果中,但是还是要乘。
            e = e >> 1
        return res if exponent>=0 else 1/res

8/3

  1. 顺时针打印矩阵

思路: 先取矩阵的第一行,接着将剩下作为新矩阵进行一个逆时针90度的翻转,接着获取第一行,直到矩阵为空。

class Solution:
    # matrix类型为二维列表,需要返回列表
    def printMatrix(self, matrix):
        # write code here
        res = []
        while matrix:
            res += matrix.pop(0)
            if matrix:
                matrix = self.rotate(matrix)
        return res
    
    def rotate(self, matrix):
        # 逆时针旋转矩阵
        rows = len(matrix)
        cols = len(matrix[0])
        new_matrix = []
        # 行列调换
        for i in range(cols):
            new_line = []
            for j in range(rows):
                new_line.append(matrix[j][cols-1-i])
            new_matrix.append(new_line)
        return new_matrix
  1. 最小的K个数
    题目描述
    输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
    这题考排序,需要整理一下几个排序问题
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code herene
        if not tinput or k<=0 or k>len(tinput):
            return []
        tinput.sort()
        return tinput[:k]
  1. 整数中1出现的次数(从1到n整数中1出现的次数)
    题目描述:
    求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
    解题思路:
    胡乱写的,顺利的不可思议(发呆)
    我不能再这样了 ==
class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        s = ''
        for i in range(n+1):
            s += str(i)
        return s.count('1')

8/4

  1. 扑克牌顺子
# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        # write code here
        if not numbers:
            return False
        numbers.sort()
        zeros = numbers.count(0)
        for i in range(zeros,4):
            if numbers[i+1] == numbers[i]:return False
            zeros -= numbers[i+1]-numbers[i]-1
            if zeros < 0:
                return False
        return True
  1. 和为S的两个数字

题目描述:
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

解题思路:
数列满足递增,设两个头尾两个指针i和j,
若ai + aj == sum,就是答案(相差越远乘积越小,例如和为10,1×9是乘积最小的。)
若ai + aj > sum,aj肯定不是答案之一(前面已得出 i 前面的数已是不可能),j -= 1
若ai + aj < sum,ai肯定不是答案之一(前面已得出 j 后面的数已是不可能),i += 1

class Solution:
    def FindNumbersWithSum(self, array, tsum):
    # write code here
        i = 0
        j = len(array)-1
        while i < j:
            if array[i] + array[j] < tsum:
                i += 1
            elif array[i] + array[j] > tsum:
                j -= 1
            elif array[i]+array[j] == tsum:
                return array[i], array[j]
        return []
  1. 和为S的连续正数序列
    题目描述
    小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
    解题思路:
    跟上一题挺像的,用滑动窗口来实现
# -*- coding:utf-8 -*-
class Solution:
    def FindContinuousSequence(self, tsum):
        # write code here
        if tsum < 3:
            return []
        
        # 初始化滑动窗口
        small = 1
        big = 2
        res = []
        
        while small < big:
            curSum = sum(range(small,big+1))
            if curSum == tsum:
                # 找到一组解,添加到res
                res.append(range(small, big+1))
                # 移动滑动窗口继续寻找
                big += 1
            elif curSum < tsum:
                big += 1
            elif curSum > tsum:
                small += 1
        return res

8/5

  1. 丑数

题目描述
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

解题思路
因为丑数只包含质因子2,3,5,假设我们已经有n-1个丑数,按照顺序排列,且第n-1的丑数为M。那么第n个丑数一定是由这n-1个丑数分别乘以2,3,5,得到的所有大于M的结果中,最小的那个数。

事实上我们不需要每次都计算前面所有丑数乘以2,3,5的结果,然后再比较大小。因为在已存在的丑数中,一定存在某个数T2,在它之前的所有数乘以2都小于已有丑数,而T2×2的结果一定大于M,同理,也存在这样的数T3,T5,我们只需要标记这三个数即可。

# -*- coding:utf-8 -*-
class Solution:
    def GetUglyNumber_Solution(self, index):
        # write code here
        if index == 0:
            return 0
        # 1作为特殊数直接保存
        baselist = [1]
        min2 = min3 = min5 = 0
        curnum = 1
        while curnum < index:
            minnum = min(baselist[min2] * 2, baselist[min3] * 3, baselist[min5] * 5)
            baselist.append(minnum)
            # 找到第一个乘以2的结果大于当前最大丑数M的数字,也就是T2
            while baselist[min2] * 2 <= minnum:
                min2 += 1
            # 找到第一个乘以3的结果大于当前最大丑数M的数字,也就是T3
            while baselist[min3] * 3 <= minnum:
                min3 += 1
            # 找到第一个乘以5的结果大于当前最大丑数M的数字,也就是T5
            while baselist[min5] * 5 <= minnum:
                min5 += 1
            curnum += 1
        return baselist[-1]
  1. 孩子们的游戏(圆圈中最后剩下的数)

题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

如果没有小朋友,请返回-1

约瑟夫环问题:
方法1:使用list模拟循环链表,用cur作为指向list的下标位置。
当cur移到list末尾直接指向list头部,当删除一个数后list的长度和cur的值相等则cur指向0

# -*- coding:utf-8 -*-
class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        if n < 1 or m < 1:
            return -1
        n_list = [i for i in range(n)]  # 列表模拟
        # print(nlist)
        cur = 0  # 指向nlist的指针
        while len(n_list) >1:
            for i in range(1, m):
                cur += 1
                # 当指针移到nlist的末尾,就把指针指向nlist的头
                if cur == len(n_list):
                    cur = 0
            # 删除一个数,此时由于删除后nlist的下标随之变化,因此cur指向的便是原数组中的下一个数字,此时cur不需要移动
            n_list.remove(n_list[cur])
            if cur == len(n_list):
                cur = 0
        return n_list[0]

方法2:找规律
使用动态规划。注意到,输入的序列在删除一个元素后,序列的长度会改变,如果索引
在被删除的元素位置开始计算,那么每删除一个元素,序列的长度减一而索引会完全改变。
如果能找到改变前的索引和新索引的对应关系,那么该问题就容易解决了。
我们定义一个函数f(n, m),表示每次在n个数字0,1,2,3,…,n-1中每次删除第m个数字后剩下
的数字。那么第一个被删除的数字的索引是(m-1)%n。删除该索引元素后,剩下的n-1个数字
为0,1,2,…,k-1,k+1,…,n-1。下次删除数字是重k+1位置开始,于是可以把序列看
作k+1,…,n-1,0,1,…,k-1。该序列最后剩下的序列也是f的函数。但该函数和第一个函数
不同,存在映射关系,使用f’来表示,于是有:f(n, m)=f’(n-1, m)。接下来需要找到映射关系。
k+1 --> 0
k+2 --> 1
.
.
.
n-1 --> n-k-2
0 --> n-k-1
.
.
.
k-1 --> n-2
所以可以得到:right = left-k-1,则p(x) = (x-k-1)%n,而逆映射是p’(x) = (x+k+1)%n
即0n-1序列中最后剩下的数字等于(0n-2序列中最后剩下的数字+k)%n,很明显当n=1时,
只有一个数,那么剩下的数字就是0.问题转化为动态规划问题,关系表示为:

f(n)=(f(n-1)+m)%n; 当n=1,f(1)=0;
  class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
  
        if n < 1 or m < 1:
            return -1
        last = 0
        for i in range(2, n+1):
            last = (last+m)%i
        return last
  1. 不用加减乘除做加法

题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

解题思路:
【参考牛客上票数最高的解答】

首先看十进制是如何做的: 5+7=12,三步走

  • 第一步:相加各位的值,不算进位,得到2。

  • 第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。

  • 第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。

同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111

  • 第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。

  • 第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。

  • 第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。
    继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。

# -*- coding:utf-8 -*-
class Solution:
    def Add(self, num1, num2):
        # write code here
        while(num2): 
            num1,num2 = (num1^num2) & 0xFFFFFFFF,((num1&num2)<<1) & 0xFFFFFFFF
        return num1 if num1<=0x7FFFFFFF else ~(num1^0xFFFFFFFF)

(1) 越界检查
就是答案中的 & 0xFFFFFFFF 操作,其中 & 是按位与, 0xFFFFFFFF代表16进制下的边界 (按二进制表示的话,对应4*8=32位)。
由于python长整数类型可以表示无限位,所以需要人为设置边界,避免死循环。
设置成32位应该是考虑到其他语言的特点,测试样例中不会出现超过32位整型的数,实际上,把边界调大的话,不会影响最终结果。

(2)正负数判断及还原
正数与边界数 按位与(&) 操作后 仍得到这个数本身,负数与边界数 按位与(&) 操作后 得到的是对应二进制数的真值。答案中,通过查看符号位(最高位,即与0x7FFFFFF比较大小)判断a为正数还是负数,正数则直接返回。负数则返回~(a^0xFFFFFFFF)。 (注: ~ 表示按位取反)

8.6

  1. 字符流中第一个不重复的字符
class Solution:
    # 返回对应char
    def __init__(self):
        self.s = ''
        self.charDic = {}
    def FirstAppearingOnce(self):
        # write code here
        for key in self.s:
            if self.charDic[key] == 1:
                return key
        return '#'
    def Insert(self, char):
        # write code here
        self.charDic[char] = 1 if char not in self.charDic else self.charDic[char]+1
        self.s += char
  1. 数据流中的中位数
# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.s = []
    def Insert(self, num):
        # write code here
        self.s.append(num)
    def GetMedian(self, n=None):
        # write code here
        self.s.sort()
        l = len(self.s)
        if l%2:
            return self.s[(l-1)//2]
        else:
            return (self.s[l//2] + self.s[l//2-1])/2.

不过这么写没有什么意义,看了别人写的:
使用两个堆:

大根堆:large保存大的半数的数据
小根堆:small保存小的半数的数据
获取中位数的时间复杂度为O(1),插入一个数据的时间复杂度为O(log(n))

核心思路:

1.维护一个大顶堆,一个小顶堆,且保证两点:

  • 1)小顶堆里的全大于 大顶堆里的;

  • 2)2个堆个数的差值小于等于1

2.当insert的数字个数为奇数时:使小顶堆个数比大顶堆多1;当insert的数字个数为偶数时,使大顶堆个数跟小顶堆个数一样。

3.那么当总数字个数为奇数时,中位数就是小顶堆堆头;当总数字个数为偶数时,中位数就是 2个堆堆头平均数

# -*- coding:utf-8 -*-
from heapq import *
class Solution:
 def __init__(self):
     self.heaps = [], []
 def Insert(self, num):
     # write code here
     small, large = self.heaps
     heappush(small, -heappushpop(large, num))#将num放入大根堆,并弹出大根堆的最小值,取反,放入大根堆small
     if len(large) < len(small):
         heappush(large, -heappop(small)) #弹出small中最小的值,取反,即最大的值,放入large
 def GetMedian(self,ss):
     # write code here
     small,large = self.heaps
     if len(large) > len(small):
         return float(large[0])
     return (large[0] - small[0]) /2.0
  1. 滑动窗口的最大值
# -*- coding:utf-8 -*-
class Solution:
    def maxInWindows(self, num, size):
        # write code here
        if size > len(num) or size==0:
            return []
        res = []
        q = []
        for i in range(size):
            q.append(num[i])
        res.append(max(q))
        for i in range(size, len(num)):
            q.pop(0)
            q.append(num[i])
            res.append(max(q))
        return res
  1. 连续子数组的最大和

思路分析
1、状态方程 : max( dp[ i ] ) = getMax( max( dp[ i -1 ] ) + arr[ i ] ,arr[ i ] )

2、上面式子的意义是:我们从头开始遍历数组,遍历到数组元素 arr[ i ] 时,连续的最大的和 可能为 max( dp[ i -1 ] ) + arr[ i ] ,也可能为 arr[ i ] ,做比较即可得出哪个更大,取最大值。时间复杂度为 n。

# -*- coding:utf-8 -*-
class Solution:
    def FindGreatestSumOfSubArray(self, array):
        # write code here
        res = array[0] #记录当前所有子数组的和的最大值
        maxSum = array[0] #包含array[i]的连续数组最大值
        for i in array[1:]:
            maxSum = max(maxSum+i, i)
            res = max(maxSum, res)
        return res
  1. 数组中出现次数超过一半的数字
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        length = len(numbers)
        if length < 1:
            return 0
        for i in numbers:
            if numbers.count(i) > length//2:
                return i 
        return 0
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        length = len(numbers)
        if length < 1:
            return 0
        numDict = {}
        for i in numbers:
            numDict[i] = 1 if i not in numDict else numDict[i]+1
            if numDict[i] > length // 2:
                return i 
        return 0

没啥意义,看看别人写的。

思路一: 数组排序后,如果符合条件的数存在,则一定是数组中间那个数。(比如:1,2,2,2,3;或2,2,2,3,4;或2,3,4,4,4等等)这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(NlogN)并非最优;

思路二: 如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。

在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。

class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        # 遍历每个元素并记录次数;若与前一个元素相同,次数加1,否者次数减1
        checkNum = numbers[0]
        cnt = 1
        for n in numbers[1:]:
            if cnt == 0: # 如果计数器为0,换下一个元素,并把计数器置为1
                checkNum = n
                cnt = 1
            elif n == checkNum:
                cnt+=1 # 相同加一
            else:
                cnt-=1 # 不同减一
        # 检查找的的这个数是否符合条件,即出现次数是否大于数组长度的一半
        if cnt>0:
            cnt1 = 0
            for n in numbers:
                if n == checkNum:
                    cnt1+=1
            return checkNum if cnt1 > len(numbers)//2 else 0
        return 0

8.7

  1. 调整数组顺序使奇数位于偶数前面
class Solution:
    def reOrderArray(self, array):
        # write code here
        res = []
        if not array:
            return []
        for i in array:
            if i%2==1:
                res.append(i)
        for i in array:
            if i%2==0:
                res.append(i)
        return res
  1. 把数组排成最小的数
# -*- coding:utf-8 -*-
class Solution:
    def PrintMinNumber(self, numbers):
        # write code here
        res = sorted(numbers,cmp=self.compare)
        return ''.join(str(x) for x in res)
    def compare(self,num1,num2):
        num1 = str(num1)
        num2 = str(num2)
        if int(num1+num2) > int(num2+num1):
            return 1
        elif int(num1+num2) < int(num2+num1):
            return -1
        else:
            return 0
  1. 数组中的逆序对
# -*- coding:utf-8 -*-
from collections import deque

class Solution:
    def __init__(self):
        self.count = 0
    def InversePairs(self, data):
        # write code here
        self.mergeSort(data)
        return self.count%1000000007
    
    def mergeSort(self,lis):
        if len(lis) <= 1:
            return lis
        middle = int(len(lis)//2)
        left = self.mergeSort(lis[:middle])
        right = self.mergeSort(lis[middle:])
        return self.merge(left, right)
    
    def merge(self,left,right):
        merged,left,right = deque(), deque(left), deque(right)
        while left and right:
            if left[0] > right[0]:
                self.count+= len(left)
                merged.append(right.popleft())
            else:
                merged.append(left.popleft())
        if right:
            merged.extend(right)
        else:
            merged.extend(left)
        return merged
                

8/10

  1. 数据在数组中出现的次数

题目描述
统计一个数字在升序数组中出现的次数。
解题思路:
二分查找,分别查找出连续数字的开始位置和结束位置

# -*- coding:utf-8 -*-
class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        return self.getEnd(data,k) - self.getStart(data, k) + 1
    def getStart(self,data,k):
        start = 0
        end = len(data) - 1 
        mid = (start+end)//2
        while start<=end:
            if data[mid]<k:
                start = mid +1
            else:
                end = mid - 1
            mid = (start+end)//2
        return start
    
    def getEnd(self,data,k):
        start = 0
        end = len(data) - 1 
        mid = (start+end)//2
        while start<=end:
            if data[mid]<=k:
                start = mid +1
            else:
                end = mid - 1
            mid = (start + end)//2
        return end
    
  1. 数组中只出现一次的数字
    题目描述
    一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
# -*- coding:utf-8 -*-
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        # write code here
        # 异或操作
        res = 0
        for i in array:
            res = res ^ i 
        # 获取res中最低位为1的位置
        idx = 0
        while(res&1) == 0:
            res >>=1
            idx+=1
        a = b = 0
        for i in array:
            if self.isBit(i, idx):
                a = a ^ i 
            else:
                b = b ^ i 
        return a, b 
    # 判断num的二进制从低到高idx位是不是1
    def isBit(self, num, idx):
        num = num >> idx
        return num & 1
  1. 数组中重复的数字
    题目描述
    在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
    解题:挺简单的,使用哈希字典
# -*- coding:utf-8 -*-
class Solution:
    # 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
    # 函数返回True/False
    def duplicate(self, numbers, duplication):
        # write code here
        dic = {}
        for num in numbers:
            if not num in dic:
                dic[num] = 1
            else:
                dic[num] += 1
        for num in numbers:
            if dic[num] > 1:
                duplication[0] = num
                return True
        return False

8/11

  1. 复杂链表的复杂(填坑)

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

解题:分三步走
第一步:先把原来的链表复制一份,复制的节点就放在原节点的后面
第二步:复制random指针,让新的节点的random指针指向旧节点random指针的后面
第三步:分离两个链表就可以了

# -*- coding:utf-8 -*-
# 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 pHead == None:
            return
        # 第一步,完成链表节点的复制和单项指针的重指向
        kk = pHead
        while kk:
            node = RandomListNode(kk.label) # 复制一个节点出来
            # 这两句让新节点和链表做了左右相连
            node.next = kk.next
            kk.next = node
            # 对链表的下一个节点做同样的操作
            kk = node.next
            
        # 第二步,复制random节点
        kk = pHead
        while kk:
            if kk.random:
                kk.next.random = kk.random.next
            kk = kk.next.next
        
        # 第三步,分离两个链表
        kk = pHead
        ee = pHead.next
        nn = pHead.next
        while kk:
            # 让旧的next指针指向下一个旧节点
            kk.next = kk.next.next
            if nn.next:
                # 让新的节点的next指针指向下一个新节点
                nn.next = nn.next.next
                nn = nn.next
            kk = kk.next
        return ee
  1. 构建乘积数组

题目描述
给定一个数组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]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。

剑指的思路:

B[i]的值可以看作下图的矩阵中每行的乘积。

下三角用连乘可以很容求得,上三角,从下向上也是连乘。

因此我们的思路就很清晰了,先算下三角中的连乘,即我们先算出B[i]中的一部分,然后倒过来按上三角中的分布规律,把另一部分也乘进去。

在这里插入图片描述

解题:

class Solution:
    def multiply(self, A):
        if not A:
            return []
        length = len(A)
        B = [None]*length
        B[0]=1
        for i in range(1,length):
            B[i] = B[i-1]*A[i-1]
        tmp = 1
        for j in range(length-2,-1,-1):
            tmp *= A[j+1]
            B[j] *= tmp
        return B
  1. 链表中的入口节点
    题目描述
    给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
    解题:
    啥是环入口:
    在这里插入图片描述
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
     def EntryNodeOfLoop(self, pHead):
        # write code here
        stack = []
        while pHead:
            stack.append(pHead)
            pHead = pHead.next
            if pHead in stack:
                return pHead
        return
  1. 剪绳子
    题目描述
    给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
class Solution:
    def cutRope(self, number):
        # write code here
        #动态规划,自下向上解决问题
        if number < 2:
            return 0
        if number == 2:
            return 1
        if number == 3:
            return 2
        #保存结果的数组,
        result = [0,1,2,3]
        for i in range(4, number+1):
            max = 0
            for j in range(1,i/2+1):
                temp = result[j]*result[i-j]
                if temp > max:
                    max = temp
            result.append(max)
        return result[number]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值