剑指Offer全题目参考答案与解析(Python 2.X版本)

推荐Ctrl + F搜索题目


二维数组的查找


行列排除法
  • 从右上角or左下角开始查找,以左下角开始查找为例,若当前值<目标值,则目标值一定在当前值所在列的右侧,因此列+1;反之将行-1。相当于循环一次就可以排除一列or一行。
  • 对于m行n列矩阵,暴力查找时间复杂度O(mn),以下方法O(m+n)。
class Solution:
    # array 二维列表
    def Find(self, target, array):
        # write code here
        row = len(array)-1
        col = 0
        while row>=0 and col<len(array[0]):
            if target == array[row][col]:
                return True
            elif target > array[row][col]:
                col+=1
            else:
                row-=1
        return False

替换空格


  • 先遍历确定’ '个数,然后从后向前插入(相对于从前向后插入,此方法下每个字符只用被修改一次),对于第count个空格和第count+1空格之间的字符,需要向后移位2*count。
1 2 3 4 5 6 7 8 9 10 11 12
h e l l o w
h % 2 0 e l l % 2 0 o w
# -*- coding:utf-8 -*-
class Solution:
    # s 源字符串
    def replaceSpace(self, s):
        # write code here
        l = list(s)
        count = 0
        for k in l:
            if k == ' ':
                count+=1
                l.extend(['1','1'])
        for i in range(len(s)-1,-1,-1):
            if l[i]!=' ':
                l[i+2*count]=l[i]
            else:
                count-=1 
                l[i+2*count]='%'
                l[i+2*count+1]='2'
                l[i+2*count+2]='0'
        return ''.join(l)

从尾到头打印链表


根据题意,使用递归完成。

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

重建二叉树


  • 前序遍历: 根节点 | 左子树 | 右子树: [1 | 2 4 5 | 3 6 7], 中序遍历: 左子树 | 根节点 | 右子树: [4 2 5 | 1 | 6 3 7]

  • 因此,可用中序遍历中根节点位置将前序遍历化为三段。对左子树和右子树分别可以进行相同的划分,例如 左子树前序遍历[2 | 4 | 5]和中序遍历[4 | 2 | 5],递归即可生成树。

  • 以下代码中,pre.pop(0)代表此递归对应树的前序遍历,中序遍历list用于辅助确定当前节点的值。

class Solution:
    def reConstructBinaryTree(self, pre, tin):
        if not tin: return
        root = TreeNode(pre.pop(0))
        for i in range(len(tin)):
            if root.val == tin[i]: break
        root.left = self.reConstructBinaryTree(pre, tin[:i])
        root.right = self.reConstructBinaryTree(pre, tin[i+1:])
        return root

用两个栈实现队列


  • 根据题意,需要用两个栈来模拟队列的push和pop,其他例如获取队列内容等不需要考虑。

  • 因此,push则向que最后添加node,pop则是将asi中的元素pop,其中asi可理解为que的reverse list。值得注意的是,当asi和que同时为空时应直接返回None。

class Solution:
    def __init__(self):
        self.que, self.asi = [], []
    def push(self, node):
        # write code here
        self.que.append(node)
    def pop(self):
        # return xx
        if not self.asi:
            if not self.que: return
            while self.que:
                self.asi.append(self.que.pop())
        return self.asi.pop()

旋转数组的最小数字


二分法+按顺序搜索

非减排序数组旋转后,数组等价于两个非减数组的拼合,当指针指向mid处时判断left和right大小关系,若left<mid,则指针肯定在第一个递增数组,此时将left=mid继续循环;否则right=mid;当right-left==1时,right即为第二个数组的第一个值,即整个数组的最小值。
值得注意的是,当left=mid=right时,无法判断mid此时在第一个递增数组还是第二个递增数组,如:

left mid right
原始 2 3 3 3 3 3 3 3 3
第一种旋转 3 2 3 3 3 3 3 3 3
第二种旋转 3 3 3 3 3 3 3 2 3

因此,当出现left=mid=right时,只能按顺序遍历[left,right]来找到最小数字。

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

    def minByOrder(rotateArray):
        for i in range(len(rotateArray)-1, 0, -1):
            if rotateArray[i-1] > rotateArray[i]:
                return rotateArray[i]
        return rotateArray[0]

斐波那契数列


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

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

跳台阶


一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

方法1:直接解法

n级台阶,可以分为n2个2阶和number-2*n2个1阶,需计算所有2阶台阶数量下的跳法总和。n2个2阶下,跳法情况是组合C c n2,即 c(c-1)…(c-n2+1)/n2!,此方法使用乘除法,计算效率较低,且复杂度O(n2)。

# -*- coding:utf-8 -*-
class Solution:
    def jumpFloor(self, number):
        # write code here
        total_count = 0
        for n2 in range(int(number/2)+1): #2的数量是0~int(number/2)+1
            c = number - n2 #此情况下数字总数
            numer = 1
            deno = 1
            for i in range(c-n2+1,c+1):  #计算此2数量下的count
                numer*=i
            for i in range(1,n2+1):
                deno*=i            
            total_count+=numer/deno
        return total_count
方法2:斐波那契数列(推荐)

发现台阶跳法规律,f(n)=f(n-1)+f(n-2),是斐波那契数列(详细解析同理于矩形覆盖)。

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

变态跳台阶


看起来很变态,其实和跳台阶思路差不多,先找f(n)和f(n-1)…f(1)的关系,再由前面的项相加即可,对于n级台阶:

先跳 后面可能性
1 f(n-1)
2 f(n-2)
n-1 1
n 1

如果用迭代做,重复计算太多了,因此考虑继续分解:

f(x) =
f(0) = 1
f(1) = 1
f(2) = f(0) + f(1)
f(3) = f(0) + f(1) + f(2) = 2[f(0)+f(1)]
f(4) = f(0) + f(1) + f(2) + f(3) = 4[f(0)+f(1)]
= …
f(n) = 2f(n-1) = 2^n-2 * [f(0)+f(1)]

因此, f(n) = 2^n-1.

# -*- coding:utf-8 -*-
class Solution:
    def jumpFloorII(self, number):
        # write code here
        return pow(2,number-1)

矩形覆盖


其实是斐波那契数列

设n个矩形有f(n)种放法,则将一个矩形如下放置,剩下矩形有f(n-1)种放法。

1 2 n

则将一个矩形如下放置,剩下矩形有f(n-2)种放法,因为还有一个矩形必须放在×的位置,即有两个矩形固定。

1 2 n
× ×

因此,f(n)=f(n-1)+f(n-2),是斐波那契数列。

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

二进制数中1的个数


方法1:
# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1(self, n):
        # write code here
        n &= 0xffffffff
        count = 0
        while n:
            count += 1
            n = n & (n-1) 
        return count
方法2:
# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1(self, n):
        # write code here
        n &= 0xffffffff
        count = 0
        while n:
            if n & 1 == 1:
                count+=1
            n>>=1 
        return count
以下理解不知正确与否:
  • python存取一个数,长度是无限制的(和java等不同,int是32位的)。当存储负数时,python会符号位前也有无限个1。例如,java中-1是以补码0xffffffff,但在python中0xffffffff前还有fffff……,因此对于负数无法直接求1的个数,会陷入死循环。

  • 对于负数,需要做预先处理,即n &= 0xffffffff,这个即是n与0xffffffff求交,输出负数n的无符号形式(负号前面的1全部变为0,因此变成了正数,负号的1变为此正数的最高位)

print(hex(1)) #0x1
print(hex(-1)) #-0x1 = 0xffffffff(补码,二进制第一位是符号位,前面还有无限个1)
print(hex(1& 0xffffffff)) #0x1 = 0x00000001
print(hex(-1& 0xffffffff)) #0xffffffff,相当于把前面无限个1变为0,从符号位至最后一位保持不变,但符号位的1被提出到与其他位等价。
print(-1& 0xffffffff) #4294967295 = 2^32-1 = -1的无符号形式

数值的整数次方


快速幂法:
  • 原始情况下, b ^ e = b * b * … * b,可以分解成e个b的乘积,需要做e-1次乘法运算,效率很低。
  • 然而我们发现 b ^ e = b ^ (1 + 2 + 4 + … + 2n),所有的e都可以分解成此数列,其本质上是一个数的二进制表示,如3 = 0011 = 1 + 2, 5 = 0101 = 1 + 4, 9 = 1001 = 1 + 8…
  • 这样我们存储 b ^ 1, b ^ 2 , b ^ 4 … (即 base = base * base),通过 exp & 1 == 0 (判断exp最右位是否为1)来判断数字此位是否需要相乘,最终把相乘结果输出即可。
# -*- coding:utf-8 -*-
class Solution:
    def Power(self, base, exponent):
        # write code here
        res, exp = 1, abs(exponent)
        while exp != 0:
            if exp & 1 != 0: res *= base
            base *= base
            exp >>= 1
        return res if exponent > 0 else 1/res

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


空间换时间,借用两个辅助数组分别填入奇数偶数并返回,时间空间复杂度均为O(N)。

# -*- coding:utf-8 -*-
class Solution:
    def reOrderArray(self, array):
        # write code here
        odd, even = [], []
        for a in array:
            odd.append(a) if a & 1 else even.append(a)
        return odd + even

链表中倒数第k个结点


声明两个指针n1, n2,指针n1先向前走k1步,找到链表第k+1个node,然后指针n1,n2一起走,当n2走过最后一个node时(指向None),n1即是倒数第k个node

class Solution:
    def FindKthToTail(self, head, k):
        # write code here
        if not head or k < 1: return
        node1, node2 = head, head
        for _ in range(k):
            if not node1: return
            node1 = node1.next
        while node1:
            node1, node2 = node1.next, node2.next
        return node2

反转链表


遍历链表,每次记录上次遍历点pre,三个指针交替向前行进。

class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        # write code here
        pre = None
        while pHead:
            cur, pHead.next = pHead.next, pre
            pre, pHead = pHead, cur
        return pre

合并两个排序链表


借用一个链表头tmp,按照大小依次将head1,head2链表加入排序,最后将剩余部分加到链表尾部,返回tmp.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
        tmp = ListNode(0)
        head = tmp
        while pHead1 and pHead2:
            if pHead1.val < pHead2.val:
                tmp.next, pHead1 = pHead1, pHead1.next
            else:
                tmp.next, pHead2 = pHead2, pHead2.next
            tmp = tmp.next
        tmp.next = pHead2 if not pHead1 else pHead1
        return head.next

树的子结构


  • 先找到子树根节点value = 父树根节点value的节点;
  • 判断以此节点为根节点时,是否是子结构(r2为空则代表是子结构);
  • 遍历pRoot1,方可确定是否为子结
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值