[剑指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