1. pandas 条件筛选
import pandas as pd
# 接受名为 world 的 pd.DataFrame 参数,并返回一个 pd.DataFrame 类型的结果。
def big_countries(world: pd.DataFrame) -> pd.DataFrame:
df = world[(world['area'] >= 3000000) | (world['population'] >= 25000000)]
return df[['name', 'population', 'area']]
使用
world_data = pd.read_csv('world_data.csv')
big_countries_data = big_countries(world_data)
print(big_countries_data)
之所以用双方框是因为:
- df['product_id']的表达式中,单个方括号用于选取数据框df中的一列,这个列的名字叫‘product_id’,结果是一个单列的pandas series对象
- df[['product_id']],在双重方括号中选取数据框df中的某几列,并将这些列构成一个新的数据框。双重方括号里的['product_id']是一个包含一个列名称的列表。
-
数据框(Data Frame)和单列(Series)是 pandas 库中常用的两种数据结构,它们有一些区别。
结构:数据框是由多个列组成的二维数据结构,每列可以是不同类型的数据(例如数字、字符串、日期等),而单列是只包含同类型数据的一维数据结构。
操作:数据框可以进行整体的行列操作、切片、合并、拆分等复杂操作。而单列则常用于单个列的数据处理、计算和可视化等简单操作。
存储:数据框可以将整个数据集以表格形式存储在内存中,并可以方便地读写到不同的数据文件格式(例如 CSV、Excel、SQL 数据库等)。而单列通常是作为数据框的某一列存在,它不单独持有数据的结构信息。
属性和方法:数据框具有许多特定于数据框的属性和方法,如列名、索引、列操作、统计摘要、缺失值处理等。而单列具有特定于单列的属性和方法,如计算统计量、排序、过滤等。综上所述,数据框适用于更复杂、结构化的数据集,适合进行包含多个列的操作和分析。而单列则更适合于针对某一列的简单计算和处理。
def find_products(products: pd.DataFrame) -> pd.DataFrame:
df = products[(products['low_fats'] == 'Y') & (products['recyclable'] == 'Y')]
return df[['product_id']]
主要是[].isin 和[].rename的方法
def find_customers(customers: pd.DataFrame, orders: pd.DataFrame) -> pd.DataFrame:
# 选择 orders['customerId'] 中 'id' 不存在的行。
df = customers[~customers['id'].isin(orders['customerId'])]
# 创建一个只包含 name 列的数据框架
# 并将列 name 重命名为 Customers。
df = df[['name']].rename(columns={'name':'Customers'})
return df
注意:排序
如果要降序
ascending = False,inplace = True是原地更改
def article_views(views: pd.DataFrame) -> pd.DataFrame:
df = views[ views['author_id'] == views['viewer_id']]
# 仅保留包含唯一的 'author_id' 的行。
df.drop_duplicates(subset=['author_id'], inplace=True)
# 按 'author_id' 列升序排序 DataFrame。
df.sort_values(by=['author_id'], inplace=True)
# 将 'author_id' 列重命名为 'id'。
df.rename(columns={'author_id':'id'}, inplace=True)
df = df[['id']]
return df
2. pandas字符串函数
1683. 无效的推文剑指 Offer 06. 从尾到头打印链表【简单】
注意df中的.str.len()的计算方式
def invalid_tweets(tweets: pd.DataFrame) -> pd.DataFrame:
# df = tweets[ len(tweets['content']) < 15 ]
# 对于 DataFrame,我们可以使用 str.len() 系列方法来计算列中字符串的长度。
is_valid = tweets['content'].str.len() > 15
# is_valid 作为过滤器选择,这个也是df类型
df = tweets[is_valid]
return df[['tweet_id']]
3. 剑指offer 排序
方法一:集合Set遍历 时间O(N),空间O(N)
- 遍历五张牌,遇到大小王(即 0 )直接跳过。
- 判别重复: 利用 Set 实现遍历判重, Set 的查找方法的时间复杂度为 O(1);
- 获取最大 / 最小的牌: 借助辅助变量 ma和 mi,遍历统计即可。
-
class Solution:
def isStraight(self, nums: List[int]) -> bool:
# 初始化集合
repeat = set()
# 初始化最大最小值
ma, mi = 0, 14
# 遍历5张牌
for num in nums:
if num == 0: continue # 跳过大小王
ma = max(ma, num) # 最大牌
mi = min(mi, num) # 最小牌
if num in repeat: return False # 若有重复,提前返回 false
repeat.add(num) # 添加牌至 Set
return ma - mi < 5 # 最大牌 - 最小牌 < 5 则可构成顺子
方法二:排序+遍历 时间O(NlogN),空间O(1),joker的额外空间
- 先对数组进行排序
- 判别重复:排序完成后,数组中的相同元素位置相邻,num[i]=num[i+1]则重复,不能构成顺子
- 获取最大/最小的牌:排序后,数组末位元素nums[4]为max,元素nums[joker]为最小牌,joker是大小王的数量,之所以这么做,是要除开0的个数来计算max-min
class Solution:
def isStraight(self, nums: List[int]) -> bool:
# 初始化joker
joker = 0
# 数组升序排列
nums.sort()
# 遍历数组
for i in range(4):
if nums[i] == 0:
joker += 1
elif nums[i] == nums[i+1]: # 若有重复,则返回False
return False
# 返回结果
return nums[4] - nums[joker] < 5
4. 剑指offer 链表
方法一:迭代(双指针),时间O(N),空间O(1)
# 迭代法(双指针)
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
# 初始化结点
pre,cur = None,head
# 迭代
while cur:
tmp = cur.next # 暂存后继节点
cur.next = pre # 转换当前节点的引用指向
pre = cur # 暂存当前节点
cur = tmp #访问下一节点
return pre
方法一:辅助栈,时间O(N),空间O(N)
链表只能从前到后访问每个节点,但是倒序,可以借助栈来实现。入栈、出栈
# 辅助栈
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
stack = []
cur = head
while cur:
stack.append(cur.val)
cur = cur.next
return stack[::-1]
class Solution {
public int[] reversePrint(ListNode head) {
//初始化辅助栈
LinkedList<Integer> stack = new LinkedList<>();
//入栈
while(head != null){
stack.addLast(head.val);
head = head.next;
}
//出栈
int[] res = new int[stack.size()];
for(int i = 0;i < res.length; i++){
res[i] = stack.removeLast();
}
//返回结果
return res;
}
}
方法一:排序
# 直接排序
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
arr.sort()
return arr[:k]
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] vec = new int[k];
Arrays.sort(arr);
for (int i = 0; i < k; i++){
vec[i] = arr[i];
}
return vec;
}
}
方法二:快排的思想排序,时间复杂度 O(NlogN),空间复杂度 O(N) : 快速排序的递归深度最好(平均)为 O(logN),最差情况(即输入数组完全倒序)为 O(N)
作者:Krahets
链接:https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/solutions/594591/jian-zhi-offer-40-zui-xiao-de-k-ge-shu-j-9yze/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
快速排序算法有两个核心点,分别为“哨兵划分”和“递归”
哨兵划分操作:以数组某个元素(一般选取首个元素)为基准数,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边
递归:对左子数组和右子数组递归执行哨兵划分,直至子数组的长度为1的时候,终止递归,即可完成对整个数组的排序。
选择从右侧开始查找的原因是,我们希望尽可能地将大的元素移动到右侧,这样可以更快地将数组划分为两个部分。如果我们从左侧开始查找,那么在找到第一个大于基准元素的元素之前,我们需要将所有小于基准元素的元素都移动到右侧,这样就没有利用到快速排序算法的优势,造成了不必要的交换操作。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
def quick_sort(arr,l,r):
#递归终止条件:子数组长度为1
if l >= r : return
# 哨兵划分,基准数为左边第一个
i , j = l , r # 初始化哨兵的位置
# 循环交换
while i < j:
# 从右向左交换,查找首个 小于基准数的值
while i < j and arr[j] >= arr[l]: j -= 1
# 从左向右交换,查找首个 大于基准数的值,如果小于等于,则直接移动i,跳过这个数,位置正确
while i < j and arr[i] <= arr[l]: i += 1
# 交换i,j位置上的值
arr[i], arr[j] = arr[j], arr[i]
# 交换基准数和分界的数,此时分界的数位于i
arr[i],arr[l]= arr[l],arr[i]
#递归执行子数组
quick_sort(arr,l,i-1)
quick_sort(arr,i+1,r)
#对数组进行升序排列
quick_sort(arr, 0, len(arr)-1)
return arr[:k]
方法三:基于快速排序的数组划分 ,时间复杂度 O(N),空间复杂度 O(logN)
快排的划分函数每次执行完后都能将数组分成两个部分,小于等于分界值 pivot 的元素的都会被放到数组的左边,大于的都会被放到数组的右边,然后返回分界值的下标。与快速排序不同的是,快速排序会根据分界值的下标递归处理划分的两侧,而这里只处理划分的一边。
如果某次哨兵划分后,基准数==k+1个数,把么基准数左边的所有数就是题目所要求的最小的k个数。所以,在每次哨兵划分后,判断基准数在数组中的索引是否==k,返回true
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
if k >= len(arr) : return arr
def quick_sort(arr, l ,r):
# 终止条件
if l > r: return
# 初始化哨兵
i,j = l,r
# 循环交换
while i < j :
while i < j and arr[j] >= arr[l]: j -= 1
while i < j and arr[i] <= arr[l] : i += 1
arr[i],arr[j] = arr[j],arr[i]
arr[i],arr[l] = arr[l],arr[i]
if k > i: return quick_sort(arr,i+1,r)
if k < i : return quick_sort(arr,l,i-1)
return arr[:k]
return quick_sort(arr,0,len(arr)-1)
双指针法算法流程 时间O(N),空间O(1)
- 特例处理:head.val == val,则return head.next
- 初始化:pre = head,cur = head.next
- 定位节点:当cur == null(while) ,cur.val == val(if),pre = cur,cur = cur.next
- 删除节点(if):cur指向某节点,pre.next = cur.next 【只要移动一个】或者cur == null,不包含val
- 返回值:返回head
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
# 头节点就是要的值
if head.val == val:
return head.next
# 头节点不是要的值
pre = head
cur = pre.next
while cur :
if cur.val == val :
pre.next = cur.next
pre = cur
cur = cur.next
return head
5. 动态规划
动态规划,循环求余法,时间复杂度 O(N),空间复杂夫O(1)
容易越界
转移方程的含义是,对规模为n的问题进行排序的时间复杂度==对规模为k的子问题进行排序的时间复杂度、对规模为n-1的子问题的进行排序的时间复杂度、以及将基准元素放置到正确位置所需的操作次数的总和
清晰的状态转移,把q赋值给p,再用把sum赋值给q,计算新的sum值
class Solution:
def fib(self, n: int) -> int:
MOD = 10 ** 9 + 7
# 特殊情况
if n <= 1 : return n
# 初始状态
p,q = 0,1
# 转移方程
sum = 1
for i in range(2,n):
p = q
q = sum
sum = (p + q)% MOD
return sum
优化
# 循环求余法
class Solution:
def fib(self, n: int) -> int:
if n <= 1: return n
a,b = 0,1
for _ in range(n):
a,b = b, a + b
return a % 1000000007
总结
主要学习了pandas中的条件筛选、字符串函数,dataframe的结构不熟悉,[[]]的结构不熟悉,字符串函数str.len()判断df中的字符串长度。
排序的算法中,熟悉了快速排序的运用,快速排序是不稳定的算法,当出现相同的数字时,这些数字会排在不一样的位置
链表中,要弄清楚各个节点的转换!最好是画图