[剑指Offer]40~43

[剑指Offer]40~43

学习使用工具

剑指Offer http://itmyhome.com/sword-means-offer/sword-means-offer.pdf

LeetCode的剑指Offer题库 https://leetcode.cn/problemset/all/

数位DP介绍 https://oi-wiki.org/dp/number/

剑指 Offer 40. 最小的k个数

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

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

限制:

  • 0 <= k <= arr.length <= 10000
  • 0 <= arr[i] <= 10000

解法:

排序后输出前k个数即可。

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

不使用自带sort的话就使用二分和快排结合的思想。

def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if not arr or k == 0:
            return []
        
        def partition(l, r):
            key = arr[l]
            while l < r:
                while l < r and arr[r] >= key:
                    r -= 1
                arr[l] = arr[r]
                while l < r and arr[l] <= key:
                    l += 1
                arr[r] = arr[l]
            arr[l] = key
            return l

        l = 0
        r = len(arr) - 1
        while l < r:
            index = partition(l, r)
            if index == k:
                break
            elif index < k:
                l = index + 1
            else:
                r = index - 1

        return arr[0:k]

剑指 Offer 41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。

示例 1:

输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

限制:

  • 最多会对 addNum、findMedian 进行 50000 次调用。

解法:

维护一个列表,保证这个列表一直是升序序列。addNum采用插入排序,时间复杂度O(N)(改用二分就是O(logN));findMedian时间复杂度O(1)。

class MedianFinder:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.arr = []


    def addNum(self, num: int) -> None:
        if not self.arr or num > self.arr[len(self.arr) - 1]:
            self.arr.append(num)
            return

        for i in range(len(self.arr)):
            if self.arr[i] >= num:
                self.arr.insert(i, num) 
                return
        

    def findMedian(self) -> float:
        if not self.arr:
            return 0
        
        mid = len(self.arr) // 2

        if len(self.arr) % 2 == 1:
            return self.arr[mid]
        else:
            return (self.arr[mid] + self.arr[mid - 1]) / 2.0


# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()

剑指 Offer 42. 连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

提示:

  • 1 <= arr.length <= 10^5

  • -100 <= arr[i] <= 100

解法:

简单的动态规划问题。定义ans数组,令ans[i]为以nums[i]结尾的最大字段和。则ans[0]=nums[0],且有ans[i]=max(nums[i],nums[i]+ans[i-1]),i>0。最后返回ans的最大值即可。

def maxSubArray(self, nums: List[int]) -> int:
        ans = [-999] * len(nums)
        ans[0] = nums[0] 

        for i in range(1, len(nums)):
            ans[i] = max(ans[i - 1]+nums[i], nums[i])

        return max(ans)

剑指 Offer 43. 1~n 整数中 1 出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1的数字有1、10、11和12,1一共出现了5次。

示例 1:

输入:n = 12
输出:5

示例 2:

输入:n = 13
输出:6

限制:

  • 1 <= n < 2^31

解法:

递归思想。首先有:k位数(k≥2)最多包含的1的个数为\(base(k)=(k - 1)*10^{k-2}\)

对一个k位数\(n=n_kn_{k-1}…n_1​\)来说,令函数\(count(n_kn_{k-1}…n_1)​\)为这些整数中含有的1的个数,则:

  • 如果n=0,则函数值为0;如果n<10,则函数值为1。
  • 如果\(n_k=1\),则\(count(n_kn_{k-1}…n_1)=base(k-1) +int(n_{k-1}…n_1)+1+count(n_{k-1}…n_1)\)
  • 如果\(n_k>1\),则\(count(n_kn_{k-1}…n_1)=base(k-1)*n_k +int(n_{k-1}…n_1)+count(n_{k-1}…n_1)\)
def countDigitOne(self, n: int) -> int:
        def count(n: int):
            if n == 0:
                return 0
            if n < 10:
                return 1

            l = len(str(n))
            m = n
            cur = 1
            while m >= 10:
                m //= 10
                cur *= 10
            
            base = (l - 1) * pow(10, l - 2)

            if m == 1:
                return base + 1 + n - cur + count(n - m * cur)
            else:
                return base * m + cur + count(n - m * cur)

        return count(n)

官方给的数学规律:如果该数为k+1位数,则那么数字1出现的次数为:

\(\lfloor {n\over 10^{k+1}} \rfloor*10^k + min(max(n~mod~10^{k+1}-10^k +1,0),10^k)\)

def countDigitOne(self, n: int) -> int:
        k, mulk = 0, 1
        ans = 0
        while n >= mulk:
            ans += (n // (mulk * 10)) * mulk + min(max(n % (mulk * 10) - mulk + 1, 0), mulk)
            k += 1
            mulk *= 10
        return ans
posted @ 2023-03-06 14:34  无机呱子  阅读(7)  评论(0编辑  收藏  举报