最近刷算法的一些想法,借此分享,如果有大佬看到,希望多多指教!
1.时间分配
1.1 碎片时间
因为上班时间比较忙,且通勤时间比较长(一天约3小时)所以大部分题都是在通勤路上刷的。
1.2 不死磕
感觉刚拿到一道题,看个5-10分钟之后,确保看懂了,要是还没有思路就直接看题解,题解要是还看不懂的就上b站看视频,要是还看不懂就先放一下,等下一次通勤的时候再看,尽量不死磕,感觉效率太低了。
2.实践
2.1 打断点方便理解
主要是一些题解其实就给了def的过程,其实并没有给出实际的例子,整体的跑下来,我觉得这里需要自己理解了之后,自己把这个过程写下来,包括实现的调用具体的参数,到出参,然后打断点,每一步去看是怎么走的,每一步走出来的样子是不是自己理解的样子,尽量确保自己真理解了。
2.2 整段的时间留给实践
刚刚上面说了,尽量把思考都放在碎片时间,有空有整段时间能坐下来忙自己的事的时候,再来实践已经有一定思路和理解的题。
-- 以上是自己的一些总结,感觉还是比较适合自己(上班族)的,供大家参考哈
-- 以下是一些自己刷过的题以及部分自己的注释和理解,不是很全,因为有的草稿在另外的电脑,没来得及放一起,以后想统一放到git上来弄,但是这篇文章拖了好久,先不追求完美,走到哪是哪吧,希望大家多多指教,谢谢!
0704 堆
# 堆: 了解下就好 # 一、堆是完全二叉树的结构 # 什么是完全二叉树:1.只允许最后一行不满 2.最后一行必须从左往右排,中间不能有间隔 # 二、堆序性 1.小根堆,父节点都要更小 2.大根堆,父节点都要更大 # 三、堆的存储,因为是完全二叉树,所以可以根据层序遍历,来得到一个数组,此时,父节点为i时,左右子节点一定为2i+1/2 # 四、堆有两个基本操作:1.上滤,通常用于插入新元素到根中时,向上调整位置时 # 2.下滤(因为必须要满足堆序性的话,所以对不满足的要操作),把根节点向下调整的操作叫下滤 # 五、自顶向下建堆法:1.插入堆 2.上滤 # 六、自下而上建堆法:对每个父节点进行下滤(从最下面的父节点开始)-- 复杂度O(N) # 七、应用 1.优先队列:弹出最小元素 -- 可以用来实现堆排序,用大根堆排序完,弹出的是正序,小根堆反 2.插入:就是上滤
0703栈 最小栈
# 一个栈同时保留当前值和栈内最小值 class MinStack(object): def __init__(self): """ initialize your data structure here. """ self.stack = [] def push(self, x): """ :type x: int :rtype: void """ if not self.stack: self.stack.append((x, x)) else: self.stack.append((x, min(x, self.stack[-1][1]))) def pop(self): """ :rtype: void """ self.stack.pop() def top(self): """ :rtype: int """ return self.stack[-1][0] def getMin(self): """ :rtype: int """ return self.stack[-1][1] # Your MinStack object will be instantiated and called as such: # obj = MinStack() # obj.push(x) # obj.pop() # param_3 = obj.top() # param_4 = obj.getMin()
0702 栈 判断括号
# !栈和列表(数组的概念)其实是一样的,排列的时候也是一样的 # !只是平时print的时候也只是查看而已 # !pop的时候自动内置了从-1位开始顶出,而且每次都只pop出一位 # append和clear都是都是一样的用 # 创建一个空栈 stack = [] # 将元素推入栈顶 stack.append(1) stack.append(2) stack.append(3) # 弹出栈顶元素 top_element = stack.pop() print(top_element) # 输出:3 # 查看栈顶元素,但不弹出 top_element = stack[-1] print(top_element) # 输出:2 # 检查栈是否为空 is_empty = len(stack) == 0 print(is_empty) # 输出:False # 清空栈 stack.clear() ####################################################从这里才开始 class Solution: def isValid(self, s: str) -> bool: dic = {'{': '}', '[': ']', '(': ')', '?': '?'} stack = ['?'] for c in s: if c in dic: stack.append(c) elif dic[stack.pop()] != c: return False return len(stack) == 1
0701二叉树的:
# 理解:主要都是运用的递归的思路,例前序,方法就是先把根节点调用自己 1 2 3 # 1已经调用完了,2再调用自己 就是2 4 5 那么把 2 替换成这个 就是 1 2 4 5 # 再到3,在调用自身的时候,已经没有左子树和右子树了,同时相当于碰到出发截止的条件了 # 最后就是 1 2 4 5 3 # 1 # / \ # 2 3 # / \ # 4 5 class TreeNode: def __init__(self, x): self.val = x self.left = None self.right = None class Solution: def preorderTraversal(self, root: TreeNode) -> list[int]: if not root: return [] # 前序递归 根-左右 return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right) # # 中序递归 左根右 # return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right) # # 后序递归 左右根 # return self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val] # 构建二叉树节点 root = TreeNode(1) node2 = TreeNode(2) node3 = TreeNode(3) node4 = TreeNode(4) node5 = TreeNode(5) # 设置节点之间的连接关系 root.left = node2 root.right = node3 node2.left = node4 node2.right = node5 a = Solution() b = a.preorderTraversal(root) print(b) # 还有一个看着可能更好理解的写法,但是也还是递归 # def preorderTraversal(self, root:TreeNode): -> list[int] # if root is None: # return [] # res = [] # 结果列表 # res.append(root.val) # 将根节点的值添加到结果列表中 # res += self.preorderTraversal(root.left) # 递归地遍历左子树,并将结果连接到结果列表中 # res += self.preorderTraversal(root.right) # 递归地遍历右子树,并将结果连接到结果列表中 # return res # 返回结果列表
0629反转链表:
## !这一段要好好看下 ## !同时以后不知道 怎么执行的时候也可以这么问:(这段代码能否引入实际的参数,实际的跑一遍看一下) class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next class Solution: def reverseList(self, head: ListNode) -> ListNode: cur = head pre = None while(cur != None): temp = cur.next cur.next = pre pre = cur cur = temp return pre # 创建一个链表 head = ListNode(1) node2 = ListNode(2) node3 = ListNode(3) node4 = ListNode(4) head.next = node2 node2.next = node3 node3.next = node4 # 输出反转前的链表 current_node = head while current_node: print(current_node.val) current_node = current_node.next # 创建Solution对象,调用reverseList方法来反转链表 solution = Solution() reversed_head = solution.reverseList(head) # 输出反转后的链表 while reversed_head: print(reversed_head.val) reversed_head = reversed_head.next
0628 矩阵置零:
# ?写了两遍了,但是还有一个常数复杂度的还没看明白 再看 # 思路一: 用 # O(m+n)额外空间 # 两遍扫matrix,第一遍用集合记录哪些行,哪些列有0;第二遍置0 # !!在原地修改意味着在给定的矩阵上直接修改数据,而不创建新的数据结构来存储修改结果。因此,返回结果通常是不必要的, # !! 因为修改后的矩阵已经在计算过程中被直接改变了。 # !! 一些算法设计的目标是尽量减少额外空间的使用,以提高算法的效率。在这种情况下,直接在原地进行修改是更好的选择。 from typing import List class Solution: def setZeroes(self, matrix: List[List[int]]) -> List[List[int]]: """ Do not return anything, modify matrix in-place instead. """ row = len(matrix) col = len(matrix[0]) row_zero = set() col_zero = set() for i in range(row): for j in range(col): if matrix[i][j] == 0: row_zero.add(i) col_zero.add(j) for i in range(row): for j in range(col): if i in row_zero or j in col_zero: # 已经是 收集好位置的额外空间,重新再过一遍双遍历,看这些数有没有在这个额外空间出现 matrix[i][j] = 0 return matrix matrix = [[1,1,1],[1,0,1],[1,1,1]] s = Solution() ans = s.setZeroes(matrix) print(ans) # 之所以打印出来是None,如果需要的话,在返回结果那里可以修改输出的数据类型 #现在是已经修改过后的
0627 合并区间(数组)
# 当出现 NameError: name 'List' is not defined 的错误提示时, # 通常是因为你在代码中错误地使用了 List,而 Python 解释器并不认识它,因为它只认识 list 作为关键字。 # 要解决这个错误,你应该将错误的 List 改为正确的 list 关键字使用。 # 或是 需要导入 List 类型,并结合类型提示来使用。例如:from typing import List # 数组里的题,但是不确定是不是动态规划的感觉? class Solution: def merge(self, intervals: list[list[int]]) -> list[list[int]]: intervals.sort(key=lambda x: x[0]) merged = [] for interval in intervals: # 如果列表为空,或者当前区间与上一区间不重合,直接添加 if not merged or merged[-1][1] < interval[0]: merged.append(interval) else: # 否则的话,我们就可以与上一区间进行合并 merged[-1][1] = max(merged[-1][1], interval[1]) return merged intervals = [[1,3],[2,6],[8,10],[15,18]] a = Solution() b = a.merge(intervals) print(b)
0626 最大数组和(动态规划)
# 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 # 子数组 是数组中的一个连续部分。 # 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] # 输出:6 # 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。 # !!其实,就是要返回 from typing import List class Solution: def maxSubArray(self, nums: List[int]) -> int: size = len(nums) if size == 0: return 0 dp = [0 for _ in range(size)] dp[0] = nums[0] for i in range(1, size): if dp[i - 1] >= 0: dp[i] = dp[i - 1] + nums[i] else: dp[i] = nums[i] return max(dp) nums = [1, -2, 3, 4, -1, 2, 1, -5, 4] solution = Solution() result = solution.maxSubArray(nums) print(result)
0622 相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
例:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
思路就是只要相交了,两边同时走的长度都是一样的,a+c+b = b + c + a
class Solution: def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: A, B = headA, headB while A != B: A = A.next if A else headB B = B.next if B else headA return A
-- 但是这样出不来值,因为ListNode没有自带这样的数据结构,所以需要自定义 ListNode
-- 下面这个不知道要怎么结合实际的跑? -- 而且 出来的值也很奇怪
class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next # 创建链表1 list1 = [4,1,8,4,5] headA = ListNode(list1[0]) currA = headA for i in range(1, len(list1)): currA.next = ListNode(list1[i]) currA = currA.next # 创建链表2 list2 = [5,6,1,8,4,5] headB = ListNode(list2[0]) currB = headB for i in range(1, len(list2)): currB.next = ListNode(list2[i]) currB = currB.next pA, pB = headA, headB while pA != pB: pA = pA.next if pA else headB pB = pB.next if pB else headA intersection_node = pA if intersection_node: print("相交节点值为:", intersection_node.val) else: print("链表不相交") # 不知道为什么就是跑不出来
-- gpt题解:
要解决相交链表问题,你可以按照以下步骤进行:
- 定义一个链表节点类ListNode:
python 复制 class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next
- 根据给定的链表值,创建两个链表listA和listB:
python 复制 # 创建链表1 list1 = [4,1,8,4,5] headA = ListNode(list1[0]) currA = headA for i in range(1, len(list1)): currA.next = ListNode(list1[i]) currA = currA.next # 创建链表2 list2 = [5,6,1,8,4,5] headB = ListNode(list2[0]) currB = headB for i in range(1, len(list2)): currB.next = ListNode(list2[i]) currB = currB.next
- 找到两个链表的相交节点,可以使用双指针法。定义指针pA和pB分别指向链表headA和headB的头节点,同时遍历两个链表,当其中一个指针到达链表尾部时,将其指向另一个链表的头节点,直到两个指针相等或者都为None为止。
python 复制 pA, pB = headA, headB while pA != pB: pA = pA.next if pA else headB pB = pB.next if pB else headA intersection_node = pA
- 最后,打印相交节点的值。
python 复制 if intersection_node: print("相交节点值为:", intersection_node.val) else: print("链表不相交")
完整的代码如上:
0621 无重复字符最长子串(滑动窗口)
#写的时候if先,但其实这一层判断的时候是左指针了,但是需要布局好 下面的 while时右指针先玩明白了才会出来给左指针
#哈希集合,空集合的时候只能用set()而不能用{},因为后者默认是字段
class Solution: def zxzfc(self, s: str) -> int: occ = set() n = len(s) # rk就是右边界,之所以要从-1开始,是因为下面的while条件有要+1才好循环 rk, ans = -1, 0 # 0 也是初始值 for i in range(n): if i != 0: #需要先构思好,while的时候就会一直移动右指针 occ.remove(s[i - 1]) # 这里的rk初始值是0 while rk + 1 < n and s[rk + 1] not in occ: occ.add(s[rk + 1]) rk += 1 ans = max(ans, rk - i + 1) #要么就是这一次循环的的长度的最大,要么就是上一次 return ans s1 = Solution() r = s1.zxzfc("aaaaabcccccdeeeeefhjkl") print(r)
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
class Solution: #前提是不管哪一段最长重复,总要从一个位置开始,一个位置结束 #那么每个位置的开始都需要去遍历 #然后从该位置后的下一个位置也都需要去遍历 def cfzfc(self, s: str) -> int: occ = set() n = len(s) rk, ans = -1, 0 #!!这里的rk是右指针,因为下面的条件就是移动+1,所以起始位置才能是0 # ans是answer呀,输出的东西 for i in range(n): # !!这里的i才是左指针 if i != 0: occ.remove(s[i-1]) #从occ集合里移除s字符串的第i-1位 #所以上面要加上i != 0,因为=的话就移除第一位了 #所以range的第一步出来的0直接往后的while走 while rk + 1 < n and s[rk + 1] not in occ: #这里的条件的意思是边界值不能超过s的长度,以及s[rk + 1]没有在集合里,所以才有下面的加 occ.add(s[rk + 1]) rk += 1 ans = max(ans, rk - i + 1) # !!这里也很关键,就是这里才开始对比,前一次的不重复长度,和下一次的不重复长度的 return ans sl = Solution() r = sl.cfzfc("abc") print(r)
--
0620 字母异位词分组
class Solution: def groupAnagrams(self, strs: List[str]) -> List[List[str]]: mp = collections.defaultdict(list) for st in strs: key = "".join(sorted(st)) mp[key].append(st) return list(mp.values()) a = Solution() a1 = a.groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]) print (a1)
没跑出来还,报 Type "help", "copyright", "credits" or "license" for more information.
-- 好像是因为 没有开头那行
name 'collections' is not defined
-- 后面开头的时候又引入了这一行 import collections
-- 然后跑文件的时候跑不动,不知道为什么选段又可以了
-- 然后后面就都可以跑了
0619 -- 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
1.暴力解 两次 循环
class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: n = len(nums) for i in range(n): for j in range(i + 1, n): if nums[i] + nums[j] == target: return [i, j] return [] a = Solution() a1 = a.twoSum([1,2,3],5) print (a1)
2.哈希表 -- 看的不是很懂还 -- 看懂了 主要是相当于有一个容器来装,反正两个数都必须从这里面出来
class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: hashtable = dict() for i, num in enumerate(nums): if target - num in hashtable: return [hashtable[target - num], i] hashtable[nums[i]] = i return [] a = Solution() a1 = a.twoSum([1,2,3,10,0,22,55],32) print (a1) # 输出 [3, 5]
enumerate函数 是返回key-value对的感觉,对于其他类型也是一样
-- hashtable = dict() 不理解
-- 为什么 后面可以直接用 这里面感觉也没东西呀