剑指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
原文地址:https://www.cnblogs.com/zuotongbin/p/11126972.html