剑指Offer刷题记录 - part one(python版本)

1. 二维数组中的查找

题目要求

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

代码

# -*- coding:utf-8 -*-
class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        # 可以从右上角或者左下角开始查找
        # 第一种是从左下角开始查找
        # if not matrix:
        #     return False
        # rows = len(matrix)
        # columns = len(matrix[0])
        # column = 0
        # row = rows - 1
        # while column < columns and row >= 0:
        #     if matrix[row][column] < target:
        #         column += 1
        #     elif matrix[row][column] > target:
        #         row -= 1
        #     else:
        #         return True
        # return False
        # 第二种是从右上角开始查找
        # if not matrix:
        #     return False
        # rows = len(matrix)
        # columns = len(matrix[0])
        # row = 0
        # column = columns - 1
        # while row < rows and column >= 0:
        #     if matrix[row][column] > target:
        #         column -= 1
        #     elif matrix[row][column] < target:
        #         row += 1
        #     else:
        #         return True
        # return False

        # 暴力法
        for mat in matrix:
            if target in mat:
                return True
        return False

参考链接
在这里插入图片描述

搜索法是从左下角开始查找,若数组元素>目标数,就向上查找;若小于,则向右查找。
该方法同理可使用于右上角,但是不可在左上角和右下角查找,因为左上角的下面和右边的数值都比当前元素的大,判断不出来。而右下角则是上面和左边元素都比当前元素小,因此也判断不出来。
时间复杂度O(m+n):其中,NN 和 MM 分别为矩阵行数和列数,此算法最多循环 M+NM+N 次。
空间复杂度O(1):row 和 column 指针使用常数大小额外空间。


暴力法是直接判断数组元素与目标元素是否相同,没有判断使用题目所给出的条件,从左到右和从上到下的递增排序。

2. 旋转数组的最小数字

题目要求

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0

思路

看到排序数组首先想到二分法,可以将遍历法的线性级别时间复杂度降低为对数级别

参考链接1参考链接2

代码

# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        # 直接查找法
#         if rotateArray == []:
#             return 0
#         return min(rotateArray)
        # 二分查找法
        if rotateArray == []:
            return 0
        left = 0
        right = len(rotateArray) - 1
        while (left < right):
            mid = left + (right - left) // 2
          #  if rotateArray[left] < rotateArray[mid]:
            #    left += 1  # 注意
            if rotateArray[mid] > rotateArray[right]:
                left = mid + 1  # 注意
            else:
                right = mid   # 注意
        return rotateArray[left]  # 注意
    
while True:
    try:
        s = Solution()
        array = list(eval(raw_input()))
        print(s.minNumberInRotateArray(array))
    except:
        break

直接查找法是暴力查找法,使用的是python自带的最小值函数min().


二分查找法则是通过将旋转数组变成有序的,该链接详细地描述了二分查找算法的步骤和案例。


我们设定为left是最小的数字,即我们所输出的index。
上面标了四处“注意”字样。第一处表示当最左边的数字小于中间数字时,我们可以往后left + 1查找(这个也可以不用)。第二处是当中间的数字大于最右边数字时,将最左边索引变为mid+1,即中间后面一位数字


eval() 函数用来执行一个字符串表达式,并返回表达式的值。
raw_input() 将所有输入作为字符串看待,返回字符串类型。

更新代码(2021.8.19):

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        if numbers == []:  # 要考虑数组中没有元素的情况
            return 0
        left = 0  # 声明两个指针,分别指向数组的首尾
        right = len(numbers) - 1   
        while left < right:
            middle = (left + right) // 2
            if numbers[middle] > numbers[right]:
                left = middle + 1
            elif numbers[middle] < numbers[right]:
                right = middle
            else:
                # right -= 1  # 超出时间限制
                # 遇到nums[m] = nums[j]时,一定有区间[i, m]内所有元素相等 或者 区间 [m, j][m,j] 内所有元素相等(或两者皆满足),直接放弃二分查找,改用线性查找
                return min(numbers[left:right]) 
        return numbers[left]

在这里插入图片描述

  • 时间复杂度:O(logn),在特殊情况下(如整个非递减数组中各元素均相等,其时间复杂度会变成O(n)。
  • 空间复杂度:O(1),因为left、right、middle变量均使用常数大小的额外空间。

3. 获取只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

思路

使用异或运算法则来编写代码。

  1. 任何数与0异或为任何数,数学表示: 0 ^ n => n

  2. 相同的数异或为0,数学表示为:n ^ n => 0

python3 代码

class Solution:
    def singleNumber(self, nums):
        num = 0 
        for i in nums:
            num = num ^ i
        print(num) 
        return num

在这里插入图片描述

遇到的问题

  1. def singleNumber(self, nums: List[int]) -> int:这是什么意思?
    答案: 这是新增的语法,为了说明参数和返回值的数据类型。不过仅仅的给人看的,实际上程序并不检查是否是相符的。
    https://fishc.com.cn/thread-134130-1-1.html

  2. TypeError: singleNumber() missing 1 required positional argument: 'nums' Solution.singleNumber(a) Line 8 in <module> (Solution.py)

4. 合并两个有序的数组

说明

给出两个有序的整数数组A和B ,请将数组B合并到数组A中,变成一个有序的数组。

思路

列表型数组能够进行相加组合(用到一个知识点,“双指针”),然后使用sort()或者sorted()进行排序即可。

python3 代码

输入:[],[1]
输出:[1]
class Solution:
    def merge(self , A, m, B, n):
        # write code here
        A[:] = A[:m] + B
        A.sort()

在这里插入图片描述

“思路碰碰车”

  1. 不能仅使用A = A + B

  2. A[:m]是把所有的原A数组选取,使用A[:]是因为要对合并数组进行选取。

  3. 试过A[:] = A[:] + B会报错,输出的是[0,1]

猜测是因为原A = [],这样数组的长度就为0,A[:] + B直接相加,那么长度应该是1才对,为什么有两个数???

  1. 也试过A[:m+n] = A[:m] + B[:],这样是可以的。其中,[:]表示全部取完,[:m]仅取到m-1之前的数。
  2. A[:m] = sorted(A[:m] + B[:]) 这样输出的是[1,0]

5. 二进制中1的个数

题目要求

请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

解题思路

使用bin先将n转化为2进制,然后再转为字符串,最后使用count()来数出1的个数。

代码

class Solution:
    def hammingWeight(self, n: int) -> int:
        # return bin(n).count("1")
        return collections.Counter(bin(n)[2:])["1"]

一行代码解决它~~~

在这里插入图片描述

6. 0~n-1中缺失的数字

题目要求

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

解题思路

(以下内容感谢leetcode的KK大佬)
在这里插入图片描述
在这里插入图片描述
更新: 题目说到是递增排序的数组,立马就要想到二分法。先假设两个指针,头指针i和尾指针j。在i<=j的循环情况下执行下面的代码,与第9题一样,定义一个中间变量的值m,然后对nums[m]与计算出来的中间值进行判断,因为题目说了是从0开始的递增数组,所以可以使用这样的方法做判断。若满足nums[m] == m,表示0~m的排序没有问题,数值和索引是对应的,则把左边界缩紧;若不满足,则修改右边界的范围。

代码

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

在这里插入图片描述

7. 扑克牌中的顺子

题目要求

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

解题思路

(以下内容感谢leetcode的KK大佬)
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

代码

class Solution:
    def isStraight(self, nums: List[int]) -> bool:
        # 排序+遍历
        # joker = 0  # 用于记录大小王的个数,若存在,则joker++
        # # 首先,先对数组进行排序
        # nums.sort() 
        # # 判断5张牌中是否有重复
        # for i in range(4): # 遍历nums里面的数
        #     if nums[i] == 0: joker += 1  # 若存在大小王,joker就加1
        #     elif nums[i] == nums[i+1]: return False  # 如果数组中存在重复的数,则提前返回false
        # # 获取最大/最小的牌,若满足条件,则可构成顺子
        # return nums[4] - nums[joker] < 5

        # 集合+遍历
        repeat = set()
        # 设置辅助变量mi,ma,用于统计最大值和最小值,来判断数组中“最大值-最小值<5”
        mi, ma = 14, 0
        for num in nums:
            if num == 0: continue
            ma = max(ma, num)
            mi = min(mi, num)
            # 若数组中在repeat集合中,说明存在重复的数,则提前返回false
            if num in repeat: return False
            # 若不存在repeat集合中,那就把数添加到集合repeat中
            repeat.add(num)
        return ma - mi < 5

在这里插入图片描述
在这里插入图片描述

8. 最小的k个数

题目要求

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

解题思路

先排序,然后取前k个即可。

代码

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        arr.sort()
        return arr[:k]

在这里插入图片描述

9. 在排序数组中查找数字 I

题目要求

统计一个数字在排序数组中出现的次数。

解题思路

解法有多种,现在分享代码量最好和效率最高的方法。

解法一: 一行代码直接解。直接计算目标值的数量。(面试肯定不能这样写,虽然是一句话的代码。CPython的C源码的时间复杂度是O(n),明面上并没有二分搜索的O(logn)来得快。但由于list.count()的实现是C语言,所以它实际上很快)

解法二: 排序数组中的搜索问题,首先想到 二分法 解决。这是一个双指针的解法。设i为头指针,j为尾指针。在i<=j的情况下一直循环,若不满足则跳出循环。定义一个表示中间变量的值mean = (i + j) // 2,然后判断这个mean与我们所需查找的数字的差距,若大于所要查找的数字,那就尾指针减一;反之,则头指针加一。

  • 时间复杂度 O(log N): 二分法为对数级别复杂度。
  • 空间复杂度 O(1) : 几个变量使用常数大小的额外空间。

为什么不是/而是//呢?
因为/会有小数点,而//会向下取整

代码

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 解法一
        # return nums.count(target)
        # 解法二
        def  SearchRight(nums, target):       
            i, j = 0, len(nums) - 1
            while i <= j:
                mean = (i + j) // 2
                if nums[mean] <= target:   # 这里是“小于等于”,目的是为了确定右边界,就是说当mid等于target时,因为不确定后面还有没有target,所以同样需要左边收缩范围
                    i = mean + 1
                else:
                    j = mean - 1
            return i

        return SearchRight(nums, target) - SearchRight(nums, target-1)  # searchNum(nums, target - 1)为的是找到左边界

解法一:
在这里插入图片描述
解法二:
在这里插入图片描述

10. 两数之和

题目要求

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

解题思路

解法一: 使用类似哈希表的做法,构建一个字典,用dic.get来获取对应值的索引。

  • 时间复杂度:O(n),其中n是数组中的元素数量。对于每一个元素num,可以O(1)的寻找差值。
  • 空间复杂度:O(n),同样的n也是数组中的元素数量。

解法二: 同样使用哈希表的做法。

代码

解法一:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        dic = {}
        for i, num in enumerate(nums):
            if target - num in dic:  # 检查哈希表中是否有这个差值,如果存在,那就返回索引结果
                return [i, dic[target - num]]
            dic[nums[i]] = i  # 如果不满足,那就以当前的数值作为key,而索引作为value存入哈希表中
        return []

在这里插入图片描述

解法二:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # dic_map = {x: i for i, x in enumerate(nums)}
        dic_map = dict()

        for i in range(len(nums)):
            x = nums[i]
            if (target - x) in dic_map:
                index = dic_map[target - x]
                # if i != index:
                return [i, index]
            dic_map[x] = i
        return []

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值