剑指offer_Python解题(一)

1、二维数组中的查找

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序;每一列都是按照从上到下递增的顺序排序。请完成一个函数,输入这样一个二维数组,判断数组中是否含有该整数。

1.1 思路

顺序排序,查找。典型的二分递归思路:
具体思路如下:
0- 先判断是否在数组的数据分布范围内
1- 判断是在下半行还是中间行的下半段,缩小数组
判断是在上半行还是中间行的上半段,缩小数组
2- 当仅仅剩余一个值时,判断两者是否相等

1.2 解题

# 二分递归
# 例子:
#     arr = [[1, 2, 3, 4], [5, 6, 7, 8]
#         , [9, 10, 11, 12],[15, 16, 17, 18]
#         , [20, 22, 25, 27], [29, 30, 40, 41 ]]
#     n_in = 28
#     find_int(arr, n_in)
import copy
def find_int(arr_in, n_in):
    arr = copy.deepcopy(arr_in)
    m = len(arr) - 1
    n = len(arr[0]) - 1
    # 0. 不在查找的范围
    if (n_in < arr[0][0]) or (n_in > arr[m][n]):
        return False
    else:
        # 对奇数偶数一同处理
        m_mid = m // 2 + m % 2
        n_mid = n // 2 + n % 2
        # 1. 一般情况 在后面行
        if (n_in > arr[m_mid][n_mid]) and (n_in > arr[m_mid][n]):
            arr = arr[m_mid:]
            print('now array is:', arr)
            return find_int(arr, n_in)
        # 2. 在中间行后半段
        elif (n_in >= arr[m_mid][n_mid]) and (n_in <= arr[m_mid][n]):
            arr = [arr[m_mid][n_mid:]]
            print('now array is:', arr)
            if (len(arr[0]) == 1):
                return arr[0] == n_in
            else:
                return find_int(arr, n_in)
        # 3. 一般情况 在前面行
        elif (n_in < arr[m_mid][n_mid]) and (n_in < arr[m_mid][0]):
            arr = arr[:m_mid]
            print('now array is:', arr)
            return find_int(arr, n_in)
        # 4. 在中间行前半段
        elif (n_in <= arr[m_mid][n_mid]) and (n_in >= arr[m_mid][0]):
            arr = [arr[m_mid][:n_mid]]
            print('now array is:', arr)
            if (len(arr[0]) == 1):
                return arr[0] == n_in
            else:
                return find_int(arr, n_in)

arr = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [15, 16, 17, 18]
        , [20, 22, 25, 27], [29, 30, 40, 41]]
n_in = 26
find_int(arr, n_in)
"""
>>> find_int(arr, n_in)
now array is: [[15, 16, 17, 18], [20, 22, 25, 27],
[29, 30, 40, 41]]
now array is: [[25, 27]]
now array is: [[25]]
False
"""

2、替换空格

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

2.1 思路

替换问题,显然需要遍历,当需要遍历的时候就可以双指针同时遍历来加速:
具体思路如下:
0- 双向同时遍历,正向正常空格替换 %20,反向空格替换为 02%
1- 合并前半 和 后半(逆序)字符
2- 考虑字段长度奇偶问题

2.2 解题

# 双向遍历
def replaceSpace(s):
    s_l = len(s) - 1
    s_s, s_e = 0, s_l
    s_outs, s_oute = '', ''
    while s_s <= s_e:
        # 考虑字段长度奇偶问题
        if s_s == s_e:
            s_outs = s_outs + '%20' if s[s_s] == ' ' else s_outs + s[s_s]
        else:
            s_outs = s_outs + '%20' if s[s_s] == ' ' else s_outs + s[s_s]
            s_oute = s_oute + '02%' if s[s_e] == ' ' else s_oute + s[s_e]
        s_s += 1
        s_e -= 1

    return s_outs + s_oute[::-1]


s = 'We are happy '
replaceSpace(s)
"""
>>> replaceSpace(s)
'We%20are%20happy%20'
"""

3、从尾到头打印链表

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

3.1 思路

读取指针值,添加到list, 最后逆方向返回

3.2 解题

def printListFromTailToHead(self, listNode):
    if not listNode:
        return []
    else:
        p = listNode
        stack = []
        while p:
            stack.append(p.val)
            p = p.next
        return stack[::-1]

4、重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}
和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

设L、D、R分别表示遍历左子树、访问根结点和遍历右子树, 则对一棵二叉树的遍历有三种情况:DLR(称为先根次序遍历),LDR(称为中根次序遍历),LRD (称为后根次序遍历)。

4.1 思路

二叉树,递归思路:
0- 从DRL中找到 节点值
1- 从LDR中找到 节点的位置
2- 然后拆分成左右进行递归

4.2 解题

# 0 由于python 中无树结构,需要自己构建
class Binary_Tree():
    def __init__(self, value=None, left=None, right=None):
        self.value = value
        self.left = left  
        self.right = right

# 1 按照思路重建二叉树
def reConstrauctBtree(dlr, ldr):
    if dlr == []:
        return None
    ldr_dc = dict(zip(ldr, range(len(ldr))))
    n = ldr_dc[dlr[0]]
    root = Binary_Tree(dlr[0])
    print('node:', dlr[0], 'left :', dlr[1:n+1], 'right :', dlr[n+1:])
    root.left = reConstrauctBtree(dlr[1:n+1], ldr[:n])
    root.right = reConstrauctBtree(dlr[n+1:], ldr[n+1:])
    return root
    
dlr = [1, 2, 4, 7, 3, 5, 6, 8]
ldr = [4, 7, 2, 1, 5, 3, 8, 6]
r = reConstrauctBtree(dlr, ldr)

# 2 前项遍历查看
def pretree(root):
    print(root.value)
    if root.left:
        pretree(root.left)
    if root.right:
        pretree(root.right)

pretree(r)

>>> r = reConstrauctBtree(dlr, ldr)
node: 1 left : [2, 4, 7] right : [3, 5, 6, 8]
node: 2 left : [4, 7] right : []
node: 4 left : [] right : [7]
node: 7 left : [] right : []
node: 3 left : [5] right : [6, 8]
node: 5 left : [] right : []
node: 6 left : [8] right : []
node: 8 left : [] right : []

>>> pretree(r)
1
2
4
7
3
5
6
8

5、用两个栈实现一个队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型

跳过

6、旋转数组中的最小数字

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

本意为:一个递增数组,将大的数帮到前面,求改数组的最小值

6.1 思路

本质是顺序数组,首先考虑二分递归:
0- 求数组的中位数,左中位数,右中位数
1- 分4种情况比较三个中位数
2- 取小段再递归
3- 直到左中位数等于右中位数

6.2 解题



import copy
def minNumberInRotateArray(rotateArray: list) -> int:
    """
    二分递归
    """
    r_ = copy.deepcopy(rotateArray)
    if r_ == []:
        return 0
        
    n = len(r_) - 1
    mid = n // 2 + n % 2
    left_m = mid // 2 + mid % 2
    right_m = (n - mid) // 2 + (n - mid) % 2 + mid
    print('left mid:', r_[left_m], 'mid:', r_[mid], 'right mid:'
    , r_[right_m]) #, 'r_', r_)
    if (left_m == right_m):
        return r_[left_m]
    # 0- l < m < r
    elif (r_[mid] <= r_[right_m]) and (r_[mid] >= r_[left_m]):
        return minNumberInRotateArray(r_[:mid+1])
    # 1- l > m > r
    elif (r_[mid] >= r_[right_m]) and (r_[mid] <= r_[left_m]):
        return minNumberInRotateArray(r_[mid:])
    # 2- l > m < r
    elif (r_[mid] <= r_[right_m]) and (r_[mid] <= r_[left_m]):
        return minNumberInRotateArray(r_[left_m:right_m+1])
    # 3- l < m > r , l > r
    elif (r_[mid] >= r_[right_m]) and (r_[mid] >= r_[left_m]) and (r_[left_m] >= r_[right_m]):
        return minNumberInRotateArray(r_[mid:])
    # 4- l < m > r , l < r
    elif (r_[mid] >= r_[right_m]) and (r_[mid] >= r_[left_m]) and (r_[left_m] <= r_[right_m]):
        return minNumberInRotateArray(r_[:mid+1])


minNumberInRotateArray([6, 7, 8, 9, 1, 2, 3, 4, 5])

简单二分

def smpl_minNumberInRotateArray(rotateArray: list) -> int:
    """
    简单二分
    """
    if rotateArray == []:
        return 0
    l = 0
    r = len(rotateArray) - 1
    while l < r:
        mid = (l + r)//2
        print(l, r)
        if rotateArray[mid] > rotateArray[r]:
            l = mid + 1
        else:
            r = mid
    return rotateArray[l]

7、斐波那契数列

要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0),n<=39

7.1 思路

递归:
Fib(n) = Fib(n-1) + Fib(n-2)]

7.2 解题

from datetime import datetime 

def Fib(n):
    if n <= 1:
        return n
    return Fib(n-1) + Fib(n-2)

a = datetime.now()
Fib(35)
b = datetime.now() - a
print('Fib(35) cost:',b.total_seconds())

"""
>>> a = datetime.now()
>>> Fib(35)
9227465
>>> b = datetime.now() - a
>>> print('Fib(35) cost:',b.total_seconds())
Fib(35) cost: 4.406252
"""

7.3 优化思路

递归会存在重复计算的情况:
Fib(n) = Fib(n-1) + Fib(n-2)]
将fib的计算结果全部记录到memo字典中,减少不必要的计算

  • 快速fib,减少重复计算
def Fib_q(n, memo):
    if n not in memo:  # 减少了重叠部分的计算
        memo[n] = Fib_q(n-1, memo) + Fib_q(n-2, memo)
    return memo[n]

a = datetime.now()
Fib_q(35, {0:0, 1:1})
b = datetime.now() - a
print('Fib_q(35) cost:', b.total_seconds())

"""
>>> a = datetime.now()
>>> Fib_q(35, {0:0, 1:1})
9227465
>>> b = datetime.now() - a
>>> print('Fib_q(35) cost:', b.total_seconds())
Fib_q(35) cost: 0.119007
"""

Fib_q增加内存占有,降低了时间复杂度。

7.4 优化思路2

参考: https://zhuanlan.zhihu.com/p/75864673

只需定义两个整型变量,b表示后面的一个数字,a表示前面的数字即可。每次进行的变换是: a,b = b,a+b

def Fibonacci(n):
    if n <= 0:
        return 0
    a = b = 1
    for i in range(2,n):
        a,b = b,a+b
    return b

a = datetime.now()
Fibonacci(35)
b = datetime.now() - a
print('Fib_q(35) cost:', b.total_seconds())
""" 测试后该方法 运行较快 也较稳定(<0.3s)
>>> Fibonacci(35)
9227465
>>> b = datetime.now() - a
>>> print('Fibonacci(35) cost:', b.total_seconds())
Fibonacci(35) cost: 0.225013
"""

优化1,虽然也减少了循环次数,但是Fib_q(n-1, memo) + Fib_q(n-2, memo),仍然是对其的两次调用。而优化2,无论怎了都是O(n)的时间复杂度

8、跳台阶

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

8.1 思路

枚举+排列组合:
1- 以2为步数,遍历 1步和两步的组合
2- 对每个组合进行排列组合

m = x t i m e s m = x_{times} m=xtimes

n = x t i m e s + y t i m e s n = x_{times} + y_{times} n=xtimes+ytimes

t p = n ! m ! ( m − n ) ! = ( y t i m e s + 1 , n ) ! x t i m e s ! tp=\frac{n!}{m!(m-n)!}=\frac{(y_{times}+1,n)!}{x_{times}!} tp=m!(mn)!n!=xtimes!(ytimes+1,n)!

3- 输出最后所有排列组合的情况总和

8.2 解题

from toolz import reduce

def jumpFloor(n):
    x_start = 2 - n % 2
    way_ls = []
    for x in range(x_start, n+1, 2):
        x_times = x
        y_times = (n - x) // 2
        all_tp = reduce(lambda x, y: x * y, range(y_times + 1, x_times + y_times + 1)) / \
            reduce(lambda x, y: x * y, range(1, x_times + 1))
        way_ls.append(all_tp)
        print(f'x_times:{x_times}, y_times:{y_times}, ways:{all_tp}')
    return  sum(way_ls)

jumpFloor(23)
"""
>>> jumpFloor(23)
x_times:1, y_times:11, ways:12.0
x_times:3, y_times:10, ways:286.0
x_times:5, y_times:9, ways:2002.0
x_times:7, y_times:8, ways:6435.0
x_times:9, y_times:7, ways:11440.0
x_times:11, y_times:6, ways:12376.0
x_times:13, y_times:5, ways:8568.0
x_times:15, y_times:4, ways:3876.0
x_times:17, y_times:3, ways:1140.0
x_times:19, y_times:2, ways:210.0
x_times:21, y_times:1, ways:22.0
x_times:23, y_times:0, ways:1.0
46368.0
"""

8.3 优化解题

  • 思路

典型的动态规划问题
对于到达第n阶台阶来说,有两种办法
一种是从第n-1阶,爬一个台阶
一种是从第n-2阶,爬两个台阶。
也就是说,到第n台阶是, 到达n-1台阶的方法加上到达n-2台阶的方法总和

F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n) = F(n-1) + F(n-2) F(n)=F(n1)+F(n2)

def jumpFloor(number):
        if number <= 2:
            return number if number >= 0 else 0
        result = [1, 2]  # 1 的时候result[0], 2 的时候result[1]
        for i in range(2, number):
            result.append(result[i-1] + result[i-2])
        return result[-1]

jumpFloor(23)

9、变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

9.1 思路

  • 思路

对于到达第n阶台阶来说,有n种办法
一种是从第n-1阶,上来
一种是从第n-2阶,上来
一种是从第n-3阶,上来

一种是从第1阶,上来

F ( n ) = F ( n − 1 ) + F ( n − 2 ) + F ( n − 3 ) + . . . + F ( 1 ) + 1 F(n) = F(n-1) + F(n-2) + F(n-3) + ... + F(1) + 1 F(n)=F(n1)+F(n2)+F(n3)+...+F(1)+1

F ( 1 ) = 1 F(1) = 1 F(1)=1
F ( 2 ) = 2 F(2) = 2 F(2)=2
F ( 3 ) = F ( 2 ) + f ( 1 ) + 1 = 4 F(3) = F(2) + f(1) + 1 = 4 F(3)=F(2)+f(1)+1=4

F ( 4 ) = F ( 3 ) + F ( 2 ) + f ( 1 ) + 1 = 2 ∗ F ( 3 ) = 8 F(4) = F(3) + F(2) + f(1) + 1 = 2 * F(3) = 8 F(4)=F(3)+F(2)+f(1)+1=2F(3)=8
. . . ... ...
F ( n ) = 2 ∗ F ( n − 1 ) F(n) = 2 * F(n-1) F(n)=2F(n1)

9.2 解题

def jumpFloorII(n):
    if n <= 2:
        return n 
    res = [1, 2]
    for i in range(2, n):
        res.append(res[-1] * 2)
    return res[-1]
    
"""
>>> jumpFloorII(4)
8
"""

9.3 优化解题

F(n) = 2 * F(n-1)化简 :
F ( n ) = 2 ( n − 1 ) F(n) = 2^{(n-1)} F(n)=2(n1)

def jumpFloorII(n):
    return 2 ** (n-1)

10、矩阵覆盖

我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

10.1 思路

  • 思路

动态规划:
到了n,那么上一步就有两种情况
1-横着, F(n-1)的时候加上一块
2-竖着, F(n-2)的时候加上两块
还有一种特殊情况矩阵的形状为 1*2n , 不论n是多少,仅有一种方法

  • 一般情况:

F ( 1 ) = 1 F(1) = 1 F(1)=1

F ( 2 ) = 2 F(2) = 2 F(2)=2

F ( 3 ) = 2 + 1 = F ( 2 ) + F ( 1 ) = 3 F(3) = 2 + 1 = F(2) + F(1) = 3 F(3)=2+1=F(2)+F(1)=3

  • 特殊情况:
    F ( 1 ) = F ( 2 ) = F ( 3 ) = . . . = F ( n ) = 1 F(1) = F(2) = F(3) = ... = F(n) = 1 F(1)=F(2)=F(3)=...=F(n)=1

10.2 解题

def rectCover(n):
    if n <= 0:
        return 0 
    if n == 1:
        return 1
    if n == 2:
        return 2 + 1
    res = [1, 2]
    for i in range(2, n):
        res.append(res[i-1] + res[i-2])
    # 加上特殊情况
    return res[-1] + 1

"""
>>> rectCover(3)
4
"""

11、二进制中1的个数

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。例如,9表示1001,因此输入9,输出2。

11.1 思路

  • 思路

0- 如果整数不等于0,那么该整数的二进制表示中至少有1位是1
1- 情况1:(xxxxx1) 如果最后位是1,那个减去1就可计数一次
再判断减去1之后的数有多少个1,即做一次位运算 x & (x - 1) 的数

2- 情况2:(xxx100)最后位是0,而第m位为1,该数减去1,结果是(xxx011)
那么也可计数一次,再判断之后的数有多少个1,即做一次位运算 x & (x - 1) 的数

3- 结合两种情况,可以和自己做几次 x & (x - 1) 即有多少个1

11.2 解题

def getnumb1(n):
    cnt = 0
    while n:
        n = n & (n - 1)
        cnt += 1
    return cnt
"""
>>> getnumb1(10000)
5
>>> len(bin(10000).split('1')) - 1
5
"""

12、数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方

12.1 思路

  • 思路

0- 循环乘

12.2 解题

def Power(x: float, n: int) -> float:
    if x == 0:
        return 0
    if n == 0:
        return 1
    out = 1
    while n:
        out = out * x if n > 0 else out / x
        n = n - 1 if n > 0 else n + 1
    return out

"""
>>> Power(2.1, 8)
378.22859361000013
>>> 2.1 ** 8
378.22859361000013
"""

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

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

13.1 思路

  • 思路

0- 全部遍历

13.2 解题

def reOrderArray(lst):
    odd_lst = []
    even_lst = []
    for i in lst:
        if i % 2 == 1:
            odd_lst.append(i)
        else:
            even_lst.append(i)
    return odd_lst + even_lst
  • 用lambda
def reOrderArray(lst):
    return sorted(lst, key = lambda x: x%2, reverse=True)

14、链表中的倒数第K个节点

输入一个链表,输出该链表中倒数第k个结点

14.1 思路

  • 思路

0- 由于不知道链表的总长度,所以需要一把K长的尺子做衡量
1- 当尺子后端到达链表末尾,那边尺子的前端就是倒数K节点

14.2 解题

def findKtoTail(l, k):
    # 如果链表为空
    if not l or k <= 0:
        return None
    st, ed = l, l
    lon_t = 0
    while (lon_t < k) and (ed):
        ed = ed.next
        lon_t += 1
    # 尺子不够长时
    if lon_t < k:
        return None
    # 尺子准备就绪, 同步后移
    while ed:
        ed = ed.next
        st = st.next
    return st
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Scc_hy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值