23/8/25 刷题记录

 1. pandas 条件筛选

595. 大的国家【简单】

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)

1757. 可回收且低脂的产品 【简单】

之所以用双方框是因为:

  1. df['product_id']的表达式中,单个方括号用于选取数据框df中的一列,这个列的名字叫‘product_id’,结果是一个单列的pandas series对象
  2. df[['product_id']],在双重方括号中选取数据框df中的某几列,并将这些列构成一个新的数据框。双重方括号里的['product_id']是一个包含一个列名称的列表。
  3. 数据框(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']]

183. 从不订购的客户 【简单】

主要是[].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

1148. 文章浏览 I【简单】

注意:排序

如果要降序

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  排序

剑指 Offer 61. 扑克牌中的顺子【简单】

方法一:集合Set遍历 时间O(N),空间O(N)

  • 遍历五张牌,遇到大小王(即 0 )直接跳过。
  • 判别重复: 利用 Set 实现遍历判重, Set 的查找方法的时间复杂度为 O(1);
  • 获取最大 / 最小的牌: 借助辅助变量 ma和 mi,遍历统计即可。
  • Picture1.png

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 链表

剑指 Offer 24. 反转链表【简单】

 方法一:迭代(双指针),时间O(N),空间O(1)

Picture1.png

# 迭代法(双指针)
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

剑指 Offer 06. 从尾到头打印链表【简单】

 方法一:辅助栈,时间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;
    }
}

剑指 Offer 40. 最小的k个数【简单】

 方法一:排序

# 直接排序
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(Nlog⁡N),空间复杂度 O(N) : 快速排序的递归深度最好(平均)为 O(log⁡N),最差情况(即输入数组完全倒序)为 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的时候,终止递归,即可完成对整个数组的排序。

为什么快速排序要从最右边开始移动?

选择从右侧开始查找的原因是,我们希望尽可能地将大的元素移动到右侧,这样可以更快地将数组划分为两个部分。如果我们从左侧开始查找,那么在找到第一个大于基准元素的元素之前,我们需要将所有小于基准元素的元素都移动到右侧,这样就没有利用到快速排序算法的优势,造成了不必要的交换操作。

Picture1.png

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(log⁡N)

快排的划分函数每次执行完后都能将数组分成两个部分,小于等于分界值 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)
            

剑指 Offer 18. 删除链表的节点【简单】

双指针法算法流程 时间O(N),空间O(1)

  1. 特例处理:head.val == val,则return head.nextPicture1.png
  2. 初始化:pre = head,cur = head.next 
  3. 定位节点:当cur == null(while) ,cur.val == val(if),pre = cur,cur = cur.next
  4. 删除节点(if):cur指向某节点,pre.next = cur.next 【只要移动一个】或者cur == null,不包含val
  5. 返回值:返回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. 动态规划

剑指 Offer 10- I. 斐波那契数列【简单】

动态规划,循环求余法,时间复杂度 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中的字符串长度。

排序的算法中,熟悉了快速排序的运用,快速排序是不稳定的算法,当出现相同的数字时,这些数字会排在不一样的位置

链表中,要弄清楚各个节点的转换!最好是画图

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花花橙子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值