剑指offer-持续更新.....

题1

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

它考察的是程序员的沟通能力,先问面试官要时间/空间需求!!!
只是时间优先就用字典,
还有空间要求,就用指针+原地排序数组,
如果面试官要求空间O(1)并且不能修改原数组,还得写成二分法!!!。

Python3 方法一: 先排序,然后看相邻元素是否有相同的,有直接return。 不过很慢,时间O(nlogn)了,空间O(1)

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
       nums.sort()
       pred = nums[0]
       for i in range(1,len(nums)):
           if nums[i] == pred:
               return nums[i]
           else:
               pred = nums[i]   

方法二:哈希表 时间O(n),空间O(n)

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
       repeatDict = {}
       for i in nums:
           if i not in repeatDict:
               repeatDict[i] = 1
           else:
               return i    

方法三:时间复杂度O(n),空间复杂度O(1)。可以看做是一种原地哈希,不过没有用到字典。具体做法就是因为题目中给的元素是 < len(nums)的,所以我们可以让 位置i 的地方放元素i。如果位置i的元素不是i的话,那么我们就把i元素的位置放到它应该在的位置,即 nums[i] 和nums[nums[i]]的元素交换,这样就把原来在nums[i]的元素正确归位了。如果发现 要把 nums[i]正确归位的时候,发现nums[i](这个nums[i]是下标)那个位置上的元素和要归位的元素已经一样了,说明就重复了,重复了就return

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
        for i in range(len(nums)):
            while i != nums[i]:
                if nums[i] == nums[nums[i]]:
                    return nums[i]
                tmp = nums[i]  # tmp表示元素 num[i]的正确位置
                nums[i] = nums[tmp]
                nums[tmp] = tmp   

题2

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

解法一:找规律,从左下角开始遍历,当该值小于 target 值时,向右搜索;大于 target 值时,向上搜索。如果找到 target 则返回 True,否则返回 False。

class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        if not matrix: return False
        m, n = len(matrix), len(matrix[0])
        row, col = m - 1, 0
        while row >= 0 and col < n:
            if matrix[row][col] < target:
                col += 1
            elif matrix[row][col] > target:
                row -= 1
            else:
                return True
        return False

  • 时间复杂度:O(m+n)。m 和 n 分别为行数和列数。最坏情况下,我们从左下角移动到右上角,经过的路径长度为。
  • 空间复杂度:O(1)O(1)。

解法二:

首先在对角线上迭代,对于每一个对角线元素,对该元素所在的行和列使用二分搜索。时间复杂度:O(lgk!),k=min(m,n)。空间复杂度:O(1)O(1)

class Solution:
    def binary_search(self, matrix, target, start, vertical):
        lo = start
        hi = len(matrix) - 1 if vertical else len(matrix[0]) - 1 # 垂直搜索:hi = 行数 - 1

        while lo <= hi:
            mid = (lo + hi) // 2
            if vertical:  # 垂直搜索
                if matrix[mid][start] < target:
                    lo = mid + 1
                elif matrix[mid][start] > target:
                    hi = mid - 1
                else: 
                    return True
            else:   # 水平搜索
                if matrix[start][mid] < target:
                    lo = mid + 1
                elif matrix[start][mid] > target:
                    hi = mid - 1
                else:
                    return True

        return False

    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        if not matrix: return False   # 边界条件

        for i in range(min(len(matrix), len(matrix[0]))):
            vertical_found = self.binary_search(matrix, target, i, True) # 垂直方向是否找到
            horizontal_found = self.binary_search(matrix, target, i, False) # 水平是否找到
            if vertical_found or horizontal_found:  # 任一方向找到即可
                return True

        return False


题3

题目:

解法一:

初始化一个 list ,记为 res ;遍历列表 s 中的每个字符 c :
当 c 为空格时:向 res 后添加字符串 “%20”;当 c 不为空格时:向 res 后添加字符 c ;
将列表 res 转化为字符串并返回。

class Solution:
    def replaceSpace(self, s: str) -> str:
        res = []   # res=''
        for c in s:
            if c == ' ': res.append("%20")	# res+='%20'
            else: res.append(c)
        return "".join(res)

时间复杂度 O ( N ) O(N) O(N) 空间复杂度 O ( N ) O(N) O(N)

解法二:使用内置函数

return s.replace(" ","%20");

题4

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

方法一:递归法
解题思路:
利用递归: 先走至链表末端,回溯时依次将节点值加入列表 ,这样就可以实现链表值的倒序输出。

递推阶段: 每次传入 head.next ,以 head == None(即走过链表尾部节点)为递归终止条件,此时返回空列表 [] 。
回溯阶段: 利用 Python 语言特性,递归回溯时每次返回 当前 list + 当前节点值 [head.val] ,即可实现节点的倒序输出。

时间复杂度 O(N),空间复杂度 O(N)

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        return self.reversePrint(head.next) + [head.val] if head else []

方法二:辅助栈法

时间复杂度 O(N),空间复杂度 O(N)

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        stack = []
        while head:
            stack.append(head.val)
            head = head.next
        return stack[::-1]
# stack[i::-1]表示从第i个元素开始翻转读取

题5:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

参考链接

题6:两个栈实现队列

class CQueue:

    def __init__(self):
        self.stack1 = []
        self.stack2 = []

    def appendTail(self, value: int) -> None:
        # 1 -> 2
        while self.stack1:
            self.stack2.append(self.stack1.pop())
        # add value
        self.stack1.append(value)
        # 1 <- 2
        while self.stack2:
            self.stack1.append(self.stack2.pop())
        return self.stack1

    def deleteHead(self) -> int:
        if not self.stack1: return -1
        return self.stack1.pop()

题7:斐波那契数列

动态规划

class Solution:
    def fib(self, n: int) -> int:
        a, b = 0, 1
        for _ in range(n):
            a, b = b, a + b
        return a % 1000000007

递归:

class Solution:
    def fib(self, n: int) -> int:

           if n==0:return 0
           if n==1:return 1
           return (self.fib(n-1)+self.fib(n-2))%1000000007

时间复杂度:2**N。
空间复杂度:N,即为递归栈的最大深度。
应用:青蛙跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
设跳上 nn 级台阶有 f(n)f(n) 种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 11 级或 22 级台阶。
当为 11 级台阶: 剩 n-1n−1 个台阶,此情况共有 f(n-1)f(n−1) 种跳法;
当为 22 级台阶: 剩 n-2n−2 个台阶,此情况共有 f(n-2)f(n−2) 种跳法。
f(n)f(n) 为以上两种情况之和,即 f(n)=f(n-1)+f(n-2)f(n)=f(n−1)+f(n−2) ,以上递推性质为斐波那契数列。本题可转化为 求斐波那契数列第 nn 项的值 ,与 面试题10- I. 斐波那契数列 等价,唯一的不同在于起始数字不同。
青蛙跳台阶问题: f ( 0 ) = 1 , f ( 1 ) = 1 , f ( 2 ) = 2 ; f(0)=1 , f(1)=1, f(2)=2; f(0)=1,f(1)=1,f(2)=2
斐波那契数列问题: f ( 0 ) = 0 , f ( 1 ) = 1 , f ( 2 ) = 1 f(0)=0 , f(1)=1 , f(2)=1 f(0)=0,f(1)=1,f(2)=1

题8 旋转数组中的最小数字

寻找旋转数组的最小元素即为寻找 右排序数组 的首个元素 n u m s [ x ] nums[x] nums[x],称 x 为 旋转点 。
在这里插入图片描述

排序数组的查找问题首先考虑使用 二分法 解决,其可将 遍历法 的 线性级别 时间复杂度降低至 对数级别

算法流程:
初始化: 声明 i , j i,j i,j双指针分别指向 n u m s nums nums数组左右两端;
循环二分: 设 m = ( i + j ) / 2 m = (i + j) / 2 m=(i+j)/2 为每次二分的中点( “/” 代表向下取整除法,因此恒有 i ≤ m < j i \leq m < j im<j),可分为以下三种情况:
(1)当 n u m s [ m ] > n u m s [ j ] nums[m] > nums[j] nums[m]>nums[j]时: m m m 一定在左排序数组中,即旋转点 x x x一定在 [ m + 1 , j ] [m + 1, j] [m+1,j]闭区间内,因此执行 i = m + 1i=m+1;
当 nums[m] < nums[j]nums[m]<nums[j] 时: mm 一定在 右排序数组 中,即旋转点 xx 一定在[i, m][i,m] 闭区间内,因此执行 j = m j=m j=m
n u m s [ m ] = n u m s [ j ] nums[m] = nums[j] nums[m]=nums[j] 时: 无法判断 m m m 在哪个排序数组中,即无法判断旋转点 x x x [ i , m ] [i, m] [i,m]还是 [ m + 1 , j ] [m + 1, j] [m+1,j] 区间中。解决方案: 执行 j = j − 1 j = j - 1 j=j1 缩小判断范围。
返回值: 当 i = j i =j i=j 时跳出二分循环,并返回 旋转点的值 n u m s [ i ] nums[i] nums[i]即可

实现代码如下:

class Solution:
    def minArray(self, numbers: [int]) -> int:
        i, j = 0, len(numbers) - 1
        while i < j:
            m = (i + j) // 2
            if numbers[m] > numbers[j]: i = m + 1
            elif numbers[m] < numbers[j]: j = m
            else: j -= 1
        return numbers[i]

实际上,当出现 n u m s [ m ] = n u m s [ j ] nums[m] = nums[j] nums[m]=nums[j] 时,一定有区间 [ i , m ] [i, m] [i,m]内所有元素相等或 区间 [ m , j ] [m, j] [m,j]内所有元素相等(或两者皆满足)。对于寻找此类数组的最小值问题,可直接放弃二分查找,而使用线性查找替代。

class Solution:
    def minArray(self, numbers: [int]) -> int:
        i, j = 0, len(numbers) - 1
        while i < j:
            m = (i + j) // 2
            if numbers[m] > numbers[j]: i = m + 1
            elif numbers[m] < numbers[j]: j = m
            else: return min(numbers[i:j])
        return numbers[i]


复杂度分析:
时间复杂度 O ( log ⁡ 2 N ) O(\log_2 N) O(log2N),在特例情况下(例如 [1, 1, 1, 1]),会退化到 O(N)。
空间复杂度 O(1) : i , j , m i,j,m i,j,m变量使用常数大小的额外空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值