python刷题汇总

一、链表

1、链表基础理论

1、单链表、双链表、循环链表

在这里插入图片描述

2、经常用到的四种指针、虚拟节点

#前指针
pre
# 当前指针
cur
# 后指针
post
#临时指针
temp

# 虚拟头节点
dummy_head    # 涉及有可能对头节点进行的操作时,千万设立一个虚拟头节点
# 使用方法
# 先定义节点
dummy_head = ListNode(0) # 
# 再指向链表
dummy_head.next = head

2、创建节点、遍历节点、改变节点、删除节点、插入节点

# 创建类
class ListNode(object):
	self __init__(self, val, next = None):
		self.val = val
		self.next = None
# 创建一个值为0的节点
cur = ListNode(0)  #
# 指针下移
cur =cur.next 
cur.next = post  # 改变节点
# 遍历
while cur!= None:
	cur= cur.next
# 删除节点(可能需要两个指针) 想要删除cur
pre.next = cur.next 
或者
pre.next = pre.next.next
# 插入节点(在cur之后插入 node1)
node1.next = cur.next
cur.next = node1

链表题目

:删除链表的节点(指定节点的值)

前后指针法

:反转链表

        # 前后指针+临时指针 (三指针:pre用来遍历原始链表,temp保存pre的下一个节点,cur用来组建反转后的链表)
        # 注意起始要有一个none节点(初始cur为none,然后不断将pre遍历到的插入cur之前)

:两两交换链表中的节点

:链表中倒数第 k 个节点

快慢指针法 :快指针先走k步,慢指针再走

:两个链表的第一个公共节点

    # 从两个头指针处分别出发,走到头后返回另一个头指针,第一次相等处即为第一个公共节点
    # 如果没有公共节点,则第一次会在 null相遇,此时返回,同样满足题意
    # 理解:把长度分成3段理解,交点之后公共部分走的一样长设为c,交点之前的长度分别设为a和b
    # 那么 a=b,时,直接相遇;不等时, 第二次走到交点处 :a+c+b  = b + c + a 

:环形链表

# 判断是否有环用快慢指针
        # 快慢指针法
        # 起点相同
        fast = slow = head
        # fast一次前进两步,所以要判断更新后的当前位置和下一位置是否为空
        # 对快指针而言,循环终止条件是当前指针为空或者下一指针为空
        # 即 满足循环的条件为:当前节点和下一节点都不为空
        # 对于fast.next.next,其实就是下一次循环的fast,固不需在本次循环中判断
        # != None 可不写
        while fast and fast.next:
            fast = fast.next.next # 每次前进两步
            slow = slow.next # 每次前进一步

            if fast == slow:  # 比较两个指针指向的地址
                return True

        return False
# 环的入口
# 快慢指针
# 第一次相遇,其中一个返回头节点,再同速,再次相遇点为起点

        fast = slow = head
        # fast一次前进两步,所以要判断更新后的当前位置和下一位置是否为空
        # != None 可不写
        while fast != None and fast.next != None:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                fast = head
                while fast != slow:
                    fast = fast.next
                    slow = slow.next
                return fast
        return None

:合并两个排序的链表

用cur保存小的那个节点,设立伪头节点

        # 伪头节点 dum + 先比较(小于等于的先合并至cur)+ 被合并的向下遍历一个节点继续遍历 + 最后剩余的部分直接合并

        dum = ListNode(0)  # 最后返回dum.next
        cur = dum  # cur 为合并后的链表的当前节点
        
        # 先比较合并
        while l1 != None and l2 != None:
            if l1.val <= l2.val:
                cur.next = l1
                l1 = l1.next
            else:
                cur.next = l2
                l2 = l2.next
            cur = cur.next # cur也要向下移动      
       
        # 再合并剩余
        if l1 != None:
            cur.next = l1
        else:
            cur.next = l2
        return dum.next

:设计链表(插入、删除等各种操作等,与第二点相似)

:删除重复元素链表

1)链表升序,且保留1个重复的

遍历链表,和下一个节点值相同则删除(删除后,cur不动,和下一个继续比),不相同则下移,重复操作,直到none
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def deleteDuplicates(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        # 遍历链表,和下一个节点值相同则删除(删除后,cur不动,和下一个继续比),不相同则下移,重复操作,直到none

        # 考虑为空的情况
        if head == None:
            return head

        cur = head
        while cur and cur.next:
            if cur.val == cur.next.val:
                cur.next = cur.next.next
            else:
                cur = cur.next

        return head

2)升序,且不保留重复的
基础操作:#向下遍历#cur = cur.next #删除链表元素# cur.next = cur.next.next

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def deleteDuplicates(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        # dummy指向头节点(避免head被删除的情况)
        dummy = ListNode(0, head) # 要传递两个参数,不能只给一个,val的值任意给
        cur = dummy 
        while cur.next and cur.next.next:
            if cur.next.val == cur.next.next.val:   # cur相当于前指针,把后一个和后两个拿来比,不断删除重复的(重复值可以单独用一个数存储,方便比较);重复值的下一个节点不同时,cur不用动;只有后一个和后两个直接比较不同时,cur才移动
                # 保存重复的元素值
                x = cur.next.val
                # 不断删除重复的
                while cur.next and cur.next.val == x:
                    cur.next = cur.next.next
            else:
                # 向下遍历一位
                cur = cur.next
        # 返回dummy的下一个
        return dummy.next

二、动态规划

1、题目

:剑指 Offer 47. 礼物的最大价值

# 针对在第一行、第一列、其他情况下的dp[i][j]进行分析

:剑指 Offer 49. 丑数

# 具体参考动画有助于理解
a = b = c = 0
 dp[n] = min(dp[a]*2, dp[b]*3, dp[c]*5)
 且判断具体是那个最小,其索引(指 a,b,c)加1,如果出现一样小的情况,这时两者都要+1

:剑指 Offer 63. 股票的最大利润

        # max(前n-1天的最大收益dp[n-1],今天收益-之前的最小花费)
        # 这里要注意prices为空的情况

:剑指 Offer 10 - I. 斐波那契数列

:剑指 Offer 10 - II. 青蛙跳台阶问题

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

:剑指 Offer 46. 把数字翻译成字符串

:最长回文子串(实际是属于 动态规划)

        # 动规:dp[i][j]表示s[i:j] 是否为回文串(储存布尔值)
        # 状态转移方程: dp[i][j] = dp[i+1][j-1] and (s[i] == s[j]) 针对长度>=3
        # 边界条件:长度为一时:s[i][i] = True
        # 长度为二时: s[i][i+1] = (s[i] == s[i+1])

2、模板: 动规五部曲

动规五部曲:

这里我们要用一个一维dp数组来保存递归的结果

1、确定dp数组以及下标的含义
dp[i]的定义为:第i个数的斐波那契数值是dp[i]

2、确定递推公式
为什么这是一道非常简单的入门题目呢?

因为题目已经把递推公式直接给我们了:状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];

3、dp数组如何初始化(边界条件)
题目中把如何初始化也直接给我们了,如下:

dp[0] = 0;
dp[1] = 1;

4、确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的

5、举例推导dp数组(检验)
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:

0 1 1 2 3 5 8 13 21 34 55

如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是一致的。

6、细节:
1)dp数组分配空间的长度,例如n还是n+1 并且要考虑特殊情况如 输入长度为空的处理
2)循环的起点和终点
3)最终返回,例 dp[n] 或者是 dp[n-1],甚至max(dp)

2、

八、综合

1、大数处理:

1、题目会规定取模1e9+7(1000000007)

xxx % 1000000007

2、正无穷:float(“inf”)

float(“inf”)表示正无穷

3、负数除法

负数除法一般题意要求向0取整
而python 从来都是向下取整,为实现向0取整,可以:

对 Python 的整数除法问题,可以用 int(num1 / float(num2)) 来做,即先用浮点数除法,然后取整。

涉及小数的除法,除数用浮点数类型可以很好的解决问题

三、递归

1、题目

:剑指 Offer 64. 求1+2+…+n

在这里插入代码片

2、模板:递归三部曲

1、确定递归函数的参数和返回值
2、确定终止条件
3、确定单层递归的逻辑

2、实例

递归三部曲

  1. 确定递归函数的参数和返回值
    因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。
    返回值自然是bool类型。

代码如下:

bool compare(TreeNode* left, TreeNode* right)
  1. 确定终止条件
    要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。

节点为空的情况有:

左节点为空,右节点不为空,不对称,return false
左不为空,右为空,不对称 return false
左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:

左右都不为空,比较节点数值,不相同就return false
此时左右节点不为空,且数值也不相同的情况我们也处理了。

代码如下:

if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true; 
else if (left->val != right->val) return false; // 注意这里我没有使用else

注意上面最后一种情况,我没有使用else,而是elseif, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。

3)确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 右节点都不为空,且数值相同的情况。

比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
如果左右都对称就返回true ,有一侧不对称就返回false 。
代码如下:

bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
bool isSame = outside && inside;                    // 左子树:中、 右子树:中(逻辑处理)
return isSame;

如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。

四、单调栈

  1. 单调减栈,从栈底到栈顶,元素从大到小,
    遍历数组时,如果当前元素大于栈顶,就把栈顶弹出,接着比,直到小于等于新的栈顶,压入当前元素进栈
# 单调减栈
stack = []
for i in range(len(list1)):
    if stack and stack[-1] < list1[i]:
        stack.pop()
    stack.append(list1[i])

五、贪心算法

1、贪心一般解题步骤

将问题分解为若干个子问题
找出适合的贪心策略
求解每一个子问题的最优解
将局部最优解堆叠成全局最优解

思考过程:想清楚局部最优,想清楚全局最优,感觉局部最优是可以推出全局最优,并想不出反例,那么就试一试贪心。

:题目

:分发饼干

: 最大子序和

六、字符串

: 题目

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值