• 剑指offer题解(python版)(更新到第16题)


    在牛客网上刷题,坚持暑假刷完(8月31日前)《剑指offer》,变量定义基本与牛客网的相同,有错误,或者有更好的解法都可以与博主联系,会不断优化代码,静待更新。

    1.二维数组中的查找

    题目描述:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
    解题思路:根据行列的数据变化规律可以缩减寻找数字的时间复杂度,时间复杂度为O(m+n),m,n分别为数组的行数与列数。

    # -*- coding:utf-8 -*-
    class Solution:
        # array 二维列表
        def Find(self, target, array):
            # write code here
            rows, cols= len(array), len(array[0])
            row, col = rows-1, 0
            while row>=0 and col<=cols-1:
                if array[row][col] == target:
                    return True
                elif array[row][col]>target:
                    row-=1
                else:
                    col+=1
            return False
    

    2.替换空格

    题目描述:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
    进一步描述:在网络编程中,如果 URL 参数中含有特殊字符,如空格、'#'、':' 等,可能导致服务器端无法获得正确的参数值。我们需要这些特殊符号转换成服务器可以识别的字符。转换规则是在 '%' 后面跟上 ASCII 码的两位十六进制的表示。如空格的 ASCII 码是 32,即十六进制的 0x20,因此空格被替换成 "%20" 。再比如 '#' 的 ASCII 码为 35,即十六进制的 0x23,它在 URL 中被替换为 "%32"。再比如 ':' 的 ASCII 码为 50,即十六进制的 0x32,它在 URL 中被替换为 "%32"。
    解题思路:利用python string的replace函数,可以感受到python在处理字符上的方便,但是这样仅仅是完成了功能,并没有了解到内部的实现。引入朴素的第二种写法,解法二先计算出空格的个数,将字符串延长,最后利用两个索引, 从后往前,不断为新的字符串赋值,时间复杂度和空间复杂度均为O(n),n为字符串长度。
    注意:python中的sring是不可以修改指定位置的值的,转换为list后,再利用string.join()函数转换为string。

    #解法1
    # -*- coding:utf-8 -*-
    class Solution:
        # s 源字符串
        def replaceSpace(self, s):
            # write code here
            if not s:
                return s
            else:
                s = s.replace(' ', '%20')
            return s
    
    #解法2
    # -*- coding:utf-8 -*-
    class Solution:
        # s 源字符串
        def replaceSpace(self, s):
            length = len(s)
            if not s:
                return s
            blankNum = 0
            for i in range(length):
                if s[i] == ' ':
                    blankNum+=1
            if not blankNum:
                return s
            s = list(s)
            s+=2*blankNum*' '
            point2 = length+2*blankNum-1
            point1 = length-1
            
            while point1!=point2:
                if s[point1]!=' ':
                    s[point2]=s[point1]
                    point2-=1
                else:
                    s[point2-2:point2+1]='%20'
                    point2-=3
                point1-=1
            str = ''.join(s)
            return str
    

    3.从尾到头打印链表

    题目描述:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
    解题思路:将链表的值读取存储到list里,然后翻转,return。

    # -*- coding:utf-8 -*-
    # class ListNode:
    # def __init__(self, x):
    # self.val = x
    # self.next = None
    
    class Solution:
        # 返回从尾部到头部的列表值序列,例如[1,2,3]
        def printListFromTailToHead(self, listNode):
            # write code here
            if not listNode:
                return []
            stack = []
            while listNode:
                stack.append(listNode.val)
                listNode = listNode.next
            stack.reverse()
            return stack
    

    4.重建二叉树

    题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
    解题思路:前序序列获得根节点,利用根节点和中序序列分割左右子树,对左右子树继续递归。

    # -*- coding:utf-8 -*-
    # class TreeNode:
    # def __init__(self, x):
    # self.val = x
    # self.left = None
    # self.right = None
    class Solution:
        # 返回构造的TreeNode根节点
        def reConstructBinaryTree(self, pre, tin):
            # write code here
            if not pre or not tin:
                return None
            pos = tin.index(pre[0])
            root = TreeNode(pre[0])
            root.left = self.reConstructBinaryTree(pre[1:pos+1],tin[0:pos])
            root.right = self.reConstructBinaryTree(pre[pos+1:],tin[pos+1:])
            return root
    

    5.用两个栈实现队列

    题目描述:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
    解题思路:一个输入栈,一个输出栈。当输入时,将输出栈的值全部输入到输入栈,再压入需要输入的值。输出时,将输入栈中的值全部压入输出栈中。时间复杂度为O(n)。

    # -*- coding:utf-8 -*-
    class Solution:
        def __init__(self):
            self.stack1 =[]
            self.stack2 =[]
        def push(self, node):
            while self.stack2:
                temp = self.stack2.pop()
                self.stack1.append(temp)
            self.stack1.append(node)
            # write code here
        def pop(self):
            while self.stack1:
                temp = self.stack1.pop()
                self.stack2.append(temp)
            if self.stack2:
                return self.stack2.pop()
            else:
                return None
    

    6.旋转数组的最小数字

    题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
    解题思路:如果使用min函数,时间复杂度为O(n),就失去了学习的意义。这题主要考察二分查找。因为旋转数组其实是分成了两段的有序数组。我们所寻找的最小值应该是断点值(当然,全部重复不存在断点)。如何确定一段序列是否存在断点是解题的关键。当nums[0]<nums[-1]时,说明这段序列有序,直接返回nums[0]。否则就利用中间值和nums[0]与nums[-1]比较,来判断断点值属于左半序列,还是右半序列。当存在全部相等的情况时,无法判断是在左边还是在右边,直接left+=1,right-=1。注意不要用递归写,递归占的内存太大。该算法的复杂度是O(logn),仅当数组的数字全部相同时,复杂度为O(n)。
    leetcode上有该题的中等难度,和困难难度的题。困难难度是包含重复数字,和本题一样。

    # -*- coding:utf-8 -*-
    class Solution:
        def minNumberInRotateArray(self, nums):
                 # write code here
            length = len(nums)
            if not length:
                return 0
            elif length==1:
                return nums[0]
            else:
                left=0
                right=length-1
                while left<right:
                    if nums[left]<nums[right]:
                        return nums[left]
                    mid = left+ int((right-left)/2)
                    if nums[mid]>nums[left] or nums[mid]>nums[right]:
                        left=mid+1    
                    elif nums[mid]<nums[-1] or nums[mid]<nums[0]:
                        right=mid
                    else:
                        left+=1
                        right-=1
                return nums[left]
    

    7.斐波那契数列

    题目描述:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
    n<=39

    解题思路:递归实现太耗时间,斐波那契的的输入变量n可以和计算次数联系起来。n,则计算n次。根据斐波那契的定义,我们无需知道前面所有的值,只需要知道最近两次的值,所以每次往后计算两个数。依此类推,返回a即可。
    对于python还有生成器的写法,不过这里不是返回生成器。

    # -*- coding:utf-8 -*-
    class Solution:
    
        def Fibonacci(self, n):
            # write code here
            a, b = 0, 1 
            for i in range(n):
                a, b = b, a+b
            return a
    

    8.跳台阶

    题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
    解题思路:递归实现太耗时间,观察到f(n) = f(n-1)+f(n-2),斐波那契数列。只是初始值不一样。

    # -*- coding:utf-8 -*-
    class Solution:
        def jumpFloor(self, number):
            # write code here
            if number==0:
                return 0
            a, b = 1, 2
            for i in range(number-1):
                a, b = b, a+b
            return a  
    

    9.变态跳台阶

    题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
    解题思路:写出公式我们发现f(n)=f(n-1)+f(n-2)+...+f(0),等价于f(n)=2*f(n-1),f(0)=1,f(1)=1,f(2)=2。

    # -*- coding:utf-8 -*-
    class Solution:
        def jumpFloorII(self, number):
            # write code here
            a = 1 
            if number <= 0:
                return 0
            for i in range(number-1):
                a = a*2
            return a 
    

    10.矩形覆盖

    题目描述:我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
    解题思路:首先要进行抽象,也就是区分怎样叫不同的覆盖,我们将n想象成横坐标,发现在每个点上,只有两个选择,走一格和走两格。于是问题被转化成在0~n的路径上,每次只能走一格或者走两格,有多少种办法走到终点。如果用深度遍历的方式会超时。很多递归的题目可以联想到斐波那契数列。这题f(n)=f(n-1)+f(n-2),n>2,在n=2的时候不满足,因为f(0)=0,特殊处理就好了。

    # -*- coding:utf-8 -*-
    class Solution:
        def jumpFloorII(self, number):
            # write code here
            a = 1 
            if number <= 0:
                return 0
            for i in range(number-1):
                a = a*2
            return a 
    

    11.二进制中1的个数

    题目描述:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
    解题思路:首先要意识到这是一道考察位运算的题目,如果没有这个意识,很可能开始循环除2,这样也是可以做的,但是就是要注意负数的情况,这个方法比较繁琐,就不展开了。既然是位运算的题目,不免涉及到移位,与,或,异或等运算符的使用。这里只贴出最优解,二进制中1的个数有几个,就循环几次。具体的分析思路见——位运算之二进制中1的个数

    # -*- coding:utf-8 -*-
    class Solution:
        def NumberOf1(self, n):
            count =0
            while n>=-0X80000000 and n!=0:
                n = n&(n-1)
                count+=1
            return count
    

    12.数值的整数次方

    题目描述:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
    解题思路:这题是实现一个pow函数,注意base为0,正数,负数,exponent为0,正数和负数共九种情况。容易遗漏的是base为0,指数为负数的异常值判断,且0的0次方是无意义的,增加一个标志位判断异常。其它情况下,当指数为负数的时候,转化为绝对值,最后取倒数即可。复杂度和指数的绝对值有关。

    # -*- coding:utf-8 -*-
    class Solution:
        def Power(self, base, exponent):
            # write code here
            self.InvalidInput = False
            
            if base==0 and exponent<=0:
                self.InvalidInput = True
                return 0.0
            result = 1
            e = abs(exponent)
            while e:
                result*=base
                e-=1 
            if exponent == 0 :
                return 1.0
            elif exponent > 0:
                return result
            else:
                return 1/result
    

    这题主要是考察代码的完整性,还有一个更高效的做法。因为我们之前的做法是base一次一次的相乘,而如果得到a的4次方,其实我们不需要a*a*a*a,只要a22,做两次平方。有这个思路,我们可以递归实现更加高效的代码,注意指数的奇偶性。
    (a^n = egin{cases}a^{n/2}*a^{n/2}&n为偶数\a^{{(n-1)}/2}*a^{{(n-1)}/2}*a&n为奇数end{cases})

    # -*- coding:utf-8 -*-
    class Solution:
        def Power(self, base, exponent):
            # write code here
            self.InvalidInput = False
            
            if base==0 and exponent<=0:
                self.InvalidInput = True
                return 0.0
    
            e = abs(exponent)
            result = self.PowerWithUnsignedExponent(base, e)
            if exponent >= 0:
                return result
            else:
                return 1/result
        def PowerWithUnsignedExponent(self, base, exponent):
            if exponent==0:
                return 1
            elif exponent==1:
                return base
            result = self.PowerWithUnsignedExponent(base, exponent>>1)
            result = result*result
            if exponent&0x1:
                result*=base
            return result
    

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

    题目描述:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
    解题思路:python处理这种问题写法上方便一些,但是时间复杂度在系数上高些,虽然都是线性的。该方法的空间复杂度是O(n)。

    # -*- coding:utf-8 -*-
    class Solution:
        def reOrderArray(self, array):
            # write code here
            arrayodd = [odd for odd in array if odd%2==1]
            arrayeven = [even for even in array if even%2==0]
            return arrayodd+arrayeven
    

    为了降低空间复杂度,我们采用原地的方式,记录下第一个偶数的位置,后续的元素全部插入到该偶数的前面,该偶数位置往后移动一格。array.insert和array.pop的复杂度都是O(n)。交换会改变顺序,而插入不会改变相对顺序,这题如果不要求顺序的话,用双指针就好了,一头一尾。在做这类题的时候,经常会遇到要写多个while的情形,我很容易将一个while写在另一个while的里面,因为满足了第一个条件,再执行第二个,直接思路就是嵌套,但为了写起来方便和阅读起来容易懂,两个while完全可以写成并行的,写成顺序执行,而不是嵌套。

    # -*- coding:utf-8 -*-
    class Solution:
        def reOrderArray(self, array):
            # write code here
            i=0
            j=0
            if 0==len(array)==1: # 数组大小为0或者为1直接返回
                return array
            while i<len(array):
                if array[i]%2==0:
                    j = i+1
                    break
                i+=1
                
            if i==len(array): # 全偶数直接返回
                return array
            
            while j<len(array):
                if array[j]%2==1:
                    array.insert(i, array.pop(j))
                    i+=1
                j+=1  
            return array
    

    14.链表中倒数第k个结点

    题目描述:输入一个链表,输出该链表中倒数第k个结点。
    解题思路:直接遍历链表获得长度,然后再找第k个结点,但是这样做就有点繁琐。可以使用两个指针。但是要注意,当第一个指针出发到第几个时,第二个才开始出发。我们知道倒数第k个结点到最后None结点,还需要k个.next操作,那么我们第一个指针先进行k次.next操作,第二个指针这时候正好赋值为head头结点,此时两个指针一起向后遍历,直到第一个指针到None,第二个指针一定是还差k次next操作到.None的,也就是停在了倒数第k个结点上。
    注意点:这种题目虽然思路比较简单,但是要想清楚判断条件对不对,是indexk,还是indexk-1。另外这个题目也考察程序的鲁棒性。输入head有可能是空指针,k可能超过链表的长度,k也可能为0。写代码的时候尽可能一遍想清楚,争取一遍写对,写完整。而不是没想清楚,先写下看运行哪里错了。

    # -*- coding:utf-8 -*-
    # class ListNode:
    # def __init__(self, x):
    # self.val = x
    # self.next = None
    
    class Solution:
        def FindKthToTail(self, head, k):
            # write code here
            index1 = 0 # 指针一
            index2 = 0 # 指针二
            temp1 = head # 结点一
            temp2 = None # 结点二
            if head==None: # 输入head为空指针
                return None
            while temp1:
                index1+=1
                temp1 = temp1.next
                if temp2:
                    temp2 = temp2.next
                if index1==k:
                    temp2 = head
            if temp2==None: # k超过了链表的长度,这里当k为0时,也是返回None
                return None
            else:
                return temp2
    

    15.反转链表

    题目描述:输入一个链表,反转链表后,输出新链表的表头。
    解题思路:这里可以直接在原链表上进行反转的操作,记录下前一个结点,当前结点,和后一个结点就好了。但我使用的是头插法,自然就会成为倒序的链表,注意要记录好下一个结点的值,不然当我们将当前结点插入到新的链表后,会失去next信息,就无法进行后序的插入。其次要注意空指针的情况。在写代码过程中,自己要首先想好几个测试用例,确保内心运行一遍无误,再提交,不要养成没写明白就开始提交的习惯。

    # -*- coding:utf-8 -*-
    # class ListNode:
    # def __init__(self, x):
    # self.val = x
    # self.next = None
    class Solution:
        # 返回ListNode
        def ReverseList(self, pHead):
            # write code here
            head = ListNode(0)
            temp = None
            while pHead:
                nextNode = pHead.next
                temp = head.next
                head.next = pHead
                pHead.next = temp
                pHead = nextNode
            return head.next
    

    16.合并两个排序的链表

    题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
    解题思路:两种方法。一是选择第一个值小的链表做基准,另一个链表和它不断比较,如果小于,就插入。另外一种方法是递归,两两比较,每次选出值小的结点。注意输入链表为空的情况,在插入时,要特别注意保存当前结点的next值,而不是当前结点。因为保存pCompare到temp了,后面再修改pCompare.next,temp也会跟着改变。应该保存pCompare.next。时间复杂度时O(max(m, n)),m和n分别为两个链表的长度。强调下,递归法只考虑第一层如何运行的,还有写好边界条件。想着其它层如何运行,会很乱。

    # 解法一:插入法
    # -*- coding:utf-8 -*-
    # class ListNode:
    # def __init__(self, x):
    # self.val = x
    # self.next = None
    class Solution:
        # 返回合并后列表
        def Merge(self, pHead1, pHead2):
            # write code here
            if pHead1 == None:
                return pHead2
            elif pHead2 == None:
                return pHead1
            
            pNode = pHead1 if pHead1.val<=pHead2.val else pHead2
            pCompare = pHead2 if pHead1.val<=pHead2.val else pHead1
            Head = ListNode(0)
            Head.next = pNode
            pre = Head
            
            while pNode:
                if pCompare:
                    if pCompare.val>=pNode.val:
                        pre = pNode
                        pNode = pNode.next
                    else:
                        pre.next=pCompare
                        pre = pCompare
                        temp = pCompare.next
                        pCompare.next = pNode
                        pCompare = temp    
                else:
                    break
            
            if pCompare:
                pre.next = pCompare
            return Head.next
    
    # 解法二:递归法
    # -*- coding:utf-8 -*-
    # class ListNode:
    # def __init__(self, x):
    # self.val = x
    # self.next = None
    class Solution:
        # 返回合并后列表
        def Merge(self, pHead1, pHead2):
            # write code here
            if pHead1 == None:
                return pHead2
            elif pHead2 == None:
                return pHead1
            
            if pHead1.val <= pHead2.val:
                head = pHead1
                head.next = self.Merge(pHead1.next, pHead2)
            else:
                head = pHead2
                head.next = self.Merge(pHead1, pHead2.next)
            return head
    
  • 相关阅读:
    java设计模式--桥接模式
    java设计模式--单例模式
    java设计模式--迭代器模式
    java设计模式--组合模式
    java设计模式--备忘录模式
    java设计模式--适配器模式
    洛谷P1464 Function
    洛谷P2434 [SDOI2005]区间
    p1416攻击火星
    p1359租用游艇
  • 原文地址:https://www.cnblogs.com/zuotongbin/p/11126972.html
Copyright © 2020-2023  润新知