代码随想训练营(两个月)

代码随想训练营


前言:感觉基础算法的本质就是对数据结构的操作:增删改查

Day1 数组:二分搜索 + 移除元素

Leetcode 704 二分查找

第一次知道还要考虑区间,但是感觉双闭的区间和代码更对称,更好理解一些。就是while有没有等号的区别。

重点:leftrightmid的更新

  • mid:只有left <= right的时候更新,是最简单的,每次循环都会更新,left == right的时候就是要结束的时候了。
  • leftright:因为while的条件包含=,所以是双闭区间,在当前循环中,已经比较过nums[mid]target了,下次循环就不需要再考虑mid本身了。所以要向前或者向后缩小范围。

考虑到leftright两个整数相加得到的整数可能会溢出,所以采用mid = left + (right - left) // 2这种写法。

# 左闭右闭
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)-1
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
            else:
                return mid
        return -1

Leetcode 27 移除元素

看到“原地”,就知道只能在原数组操作了,一开始只想到快慢指针的思路,但是相向双指针也可以。

本质:双指针交换元素

  • 同向双指针(快慢双指针):可以理解为在遍历两个数组,fast在遍历旧数组(因为跑得快,遍历完整个数组了,slow只跑了一段),slow遍历的是旧数组。因fast相当于是探路的,所以要对fast的值进行判定(if语句),然后根据结果,告诉slow少走一些弯路。
  • 相向双指针(对向):一个遇到val停下,一个没有遇到val就停下,然后交换。
#快慢指针
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        slow, fast = 0, 0
        while fast < len(nums):
            # if相当于更新slow,新数组的一个操作,fast理解为遍历旧数组
            # fast不管怎么都要遍历,而slow只有更新了之后才会向前(遍历)
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
                fast += 1
            fast += 1
        return slow
# 相向双指针
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        if nums is None or len(nums) == 0:
            return 0
        left, right = 0, len(nums)-1
        while left < right:
            # 这造成left只有遇到val才会跳出循环
            # right只有不是val的时候才会跳出
            # 刚好可以互换
            while nums[left] != val and left < right:
                left += 1
            while nums[right] == val and left < right:
                right -= 1
            nums[left], nums[right] = nums[right], nums[left]
        # 在中间边界存在一个问题:left == right情况
        if nums[left] == val:
            return left    
        else:
            return left + 1

感觉这种解法没有快慢指针那么优雅,所以还是放弃了,还要额外对left==right情况进行判定。

Day2 数组:有序数组平方 + 长度最小子数组 + 螺旋矩阵生成

Leetcode 977 有序数组的平方

一看到题目,就想着用双指针,只不过一开始有点被昨天的题目影响了,想在原数组进行操作,结果没操作出来。看了一眼答案,发现新定义了一个数组result来返回结果,只要在旧数组往中间遍历就行了。这样就简单多了。

不过一开始还是没注意,把平方写在条件判断外,导致多次平方。

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        left, right = 0, n-1
        result = [-1] * n
        k = n - 1
        while left <= right:
            if nums[left]**2 > nums[right]**2:
                result[k] = nums[left]**2
                left += 1
                k -= 1
            else:
                result[k] = nums[right]**2
                right -= 1
                k -= 1
        return result

Leetcode 209 长度最小的子数组

滑动窗口可解,也是快慢指针,要有一个total来记录窗口内的元素之和,还要有一个index来记录窗口索引长度。

这么看这次要更新的有4个参数:slowfasttotalindex

  • slowtotal > target时更新
  • fast:跟着while循环更新
  • totalslow更新之前要减去nums[slow]
  • indextotal > target,要对当前的索引差与之前的最小值比较更新

可以看出来,基本都和total的变化相关,所以while内部,对total的值进行判定。

做题的时候,忘记考虑(输入数组为空)和(输入数字的全部数都小于target)的这两种情况了。

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        slow, fast = 0, 0
        total, index =0, len(nums)+1
        while fast < len(nums):
            total += nums[fast]
            while total >= target:
                index = min(index, fast-slow+1) # 为啥+1?
                total -= nums[slow]
                slow += 1
            fast += 1
        return 0 if index == len(nums)+1 else index

Leetcode 59 螺旋数组II

记得前段时间写过这道题,但是现在也忘了(拍脑门)

但是有思路,应该是要模拟,要考虑边界问题,就像704二分查找的第二种解法,应该是左闭右开的沿着边走;

  • python里range()就是一个完美的左闭右开范围。
  • 声明一个n*n的数组
  • leftright,还有topbottom相等的时候,已经是到中心点了,循环就要结束了。
class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        left, right = 0, n-1
        top, bottom = 0, n-1
        res = [[0] * n for _ in range(n)] # 初始化数组
        num = 1 
        while left <= right and top <=bottom:
            for column in range(left, right+1):
                res[top][column] = num
                num += 1
            for row in range(top+1, bottom+1):
                res[row][right] = num
                num += 1
            if left < right and top < bottom:
                for column in range(right-1, left, -1):
                    res[bottom][column] = num
                    num += 1
                for row in range(bottom, top, -1):
                    res[row][left] = num
                    num += 1
            left, right, top, bottom = left+1, right-1, top+1, bottom-1
        return res

周末要整理一下怎么初始化数组。

Day3 链表:移除链表元素 + 设计链表 + 链表翻转

Leetcode 203 移除链表元素

和Day1 27题的移除元素(数组)差不多,不过这个是在链表上进行操作。

重点:虚拟头结点,因为链表的遍历是一次性的,到了结尾,如果没有设置备份和虚拟头结点的话,要返回处理过后的链表很麻烦。

class Solution:
   def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
       dummy = ListNode(0, head)  # 新建一个虚拟头结点,方便返回
       cur = dummy
       while cur.next:
           if cur.next.val == val:
               cur.next = cur.next.next
           else:
               cur = cur.next
       return dummy.next

Leetcode 707 设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index有效,则删除链表中的第 index 个节点。

这题得把题目放这,说实话,之前一看到这种题,我都是直接跳过了,因为太麻烦了(现在也非常抗拒),要写一个比较完整的的类了。为了让自己印象深刻一点,还是写详细一些这次。

这个类有3个属性,valnextindex和5个函数:

  • get(index):根据索引获取val
  • addAtHead(val):头部加入节点,之前链表的index也要跟着变化
  • addAtTail(val):尾部加入节点,
  • addAtIndex(index,val):根据索引值index,在中间插入节点
  • deleteAtIndex(index):删除某个索引的节点

就是说设计链表其实包含和插入链表和删除节点两个题了。

需要两个类,一个是节点Node类,另一个是链表MyLinkList类,它们的属性有:

  • class Node:节点的值val,节点指向的下一个节点next
  • class MyLinkList:链表的虚拟头_head,链表的长度_length
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

class MyLinkedList:
    def __init__(self):
        self._head = Node(0)
        self._length = 0
    
    def get(self, index: int) -> int:
    def addAtHead(self, val: int) -> None:
    def addAtTail(self, val: int) -> None:
    def addAtIndex(self, index: int, val: int) -> None:
    def deleteAtIndex(self, index: int) -> None:

首先是根据索引获得值val

    def get(self, index: int) -> int: 
        if index < 0 or index >= self._length: # 根据index取值,需要遍历
            return -1
        
        post = self._head
        for _ in range(index + 1): # 需要考虑虚拟头结点
            post = post.next
        return post.val

接下来的三个成员函数实际上是一个功能,只要实现了addAtIndex就可以简单实现另外两个,增加和删除链表都要注意长度的变化。

   def addAtIndex(self, index: int, val: int) -> None:
        if index < 0: index = 0 # 前面条件判断
        elif index > self._length: return None 
        
        self._length += 1
        insert = Node(val)

        pre, post = None, self._head  # 前一个节点和后一个节点(index)
        for _ in range(index + 1):
            pre, post = post, post.next # 相当于在遍历两个链表, pre从self._head.next开始遍历
        else:
            pre.next, insert.next = insert, post

	def addAtHead(self, val: int) -> None:
        self.addAtIndex(0, val)

    def addAtTail(self, val: int) -> None:
        self.addAtIndex(self._length, val)

这里我不小心将self._length的增加操作放在范围判断之前了,导致出错。

    def deleteAtIndex(self, index: int) -> None:
       
        if index < 0 or index >= self._length: return None
        self._length -= 1
        
        pre, post = None, self._head
        for _ in range(index+1):  # 需要考虑虚拟头结点
            pre, post = post, post.next
        else:
            pre.next = post.next

Leetcode 206 链表翻转

感觉做完上一题设计链表,确实对链表的遍历理解加深了。prepost有种快慢链表的味道,只不过他们一直相差1,同时对它们俩进行遍历。

pre就相当于虚拟头结点dummy了,最后可以直接返回。

感觉就是每次的操作,只是将后一个节点的next先保存,然后指向前一个节点pre ,然后更新两个节点。

重点:要将

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        pre, post = None, head  
        while post:
            temp, post.next = post.next, pre  # 只将后一个节点的next先保存,然后指向前一个节点pre 
            pre, post = post, temp  # 都向后移动一位
        return pre  # 因为pre在head的前一个节点,所以相当于虚拟头结点了,这题因为翻转了,可以这么返回

Day4 链表 两两交换节点 + 删除倒数第n个节点 + 链表相交

Leetcode 24 两两交换链表中的节点

感觉画图会更好理解一点,其实根据原理和链表翻转类型,可以视为进阶题目。

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = dummy = ListNode(0, head)
        while cur.next and cur.next.next:
            node_1, node_2 = cur.next, cur.next.next
            node_1.next, node_2.next, cur.next = node_2.next, node_1, node_2   # 从后往前更新
            cur = node_1  # 更新cur
        return dummy.next 

重点:是逆着箭头的走向进行更新。

Leetcode 19 删除链表的倒数第n个节点

之前刷过一次,使用了两次扫描,第一次得到链表的长度,第二次进行删除,删除操作和设计链表的删除功能差不多。

# 两次扫描
class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        def getLength(head:ListNode)->int:
            length = 0
            while head:
                length += 1
                head = head.next
            return length
        cur = dummy = ListNode(0, head)  # dummy是虚拟头结点
        size = getLength(head)
        for i in range(1, size - n + 1):
            cur = cur.next
        cur.next = cur.next.next
        return dummy.next

这次试试单次扫描的方法,确实没想到,使用快慢指针,先让fast移动n步,然后fastslow同时移动,fast到结尾了,就删除slow的节点。
出错点:将dummy.next用来初始化slowfast了,感觉对边界还是不是很熟悉

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy = ListNode(0,head)
        slow = fast = dummy
        while n:  # 删除倒数第n个
            n -= 1 
            fast = fast.next
        
        while fast.next:
            slow, fast =slow.next, fast.next
        else:
            slow.next = slow.next.next
            return dummy.next

面试题 02.07. 链表相交

一开始确实没想到,感觉更像数学题目:

  • 遍历两次,求长度差,然后一个先走,一个后走,类似快慢指针
  • 这个方法太巧妙了,类似直接走headAheadB的总长度,如果有交点的话,第二段的最后肯定是相同。
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        if not headA or not headB:
            return None
        cura, curb = headA, headB
        while cura != curb:
            cura = cura.next if cura else headB
            curb = curb.next if curb else headA
        return cura

Leetcode 142 环形链表

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        slow = fast = head
        while fast and fast.next:
            slow, fast = slow.next, fast.next.next
            if slow == fast:  # 相等,表示有环
                p, q = head, slow  # 重新设置起点,再次相遇,即为环的起点
                while p!=q:
                    p, q = p.next, q.next
                return p
        return None

Day6 哈希表:有效的字母异位词 + 两个数组的交集 + 快乐数 + 两数之和

Leetcode242 有效的字母异位词

一次就ac了,感觉还是第一次,没啥好说的。

  • 一维list的初始化,想说一下,但是还是有时间再说吧。
  • all和any函数的用法,感觉相当于for + if的感觉
class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        res = [0 for _ in range(26)]
        for i in s:
            res[ord(i) - ord("a")] += 1
        for i in t:
            res[ord(i) - ord("a")] -= 1
        return all([i == 0 for i in res])

Leetcode 349 两个数组的交集

set在Python里的相关用法,学习一下

  • 组合:set3 = set1.union(set2)或者set3 = set1 | set2
  • 添加:set1.add(item)
  • 相交:set3 = set1 & set2
  • 不同:set3 = set1.difference(set2),set1里有的而set2没有的
  • 清除:set1.clear()
  • 移除:set1.remove(item)
class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        set1, set2 = set(), set()
        for i in nums1:
            set1.add(i)
        
        for j in nums2:
            set2.add(j)
                
        return list(set1 & set2)

Leetcode 202 快乐数

Leetcode 202 快乐数
虽然挺简单的,但是一开始还是没思路

class Solution:
    def isHappy(self, n: int) -> bool:
        def sum_cal(num: int):
            _sum = 0
            while num:
                _sum += (num % 10) ** 2
                num = num // 10
            return _sum

        res = set()

        while True:
            n = sum_cal(n)
            if n == 1:
                return True
            elif n in res:
                return False
            else:
                res.add(n)
     if n in res:
     	return False
     else:
        res.add(n)
	return False if n in res else res.add(n)

这两种的输出结果为啥不一样。下面是相当于将res.add(n)作为一个参数返回了吗,所以是null

Leetcode 1 两数之和

python中的哈希map就是字典dict,将字典的构建和检索放到一块进行。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        nums_dict = dict()
        # 构建字典
        for index, num in enumerate(nums):
            if target - num not in nums_dict:
                nums_dict[num] = index
            else:
                return [nums_dict[target-num], index]

Day7 哈希表:四数相加II + 赎金信 + 三数之和 + 四数之和

Leetcode 四数相加II

第一眼还没想出来,看了视频,其实感觉和两数之和差不多,可能是双重循环ptsd了,感觉非常抗拒(笑),觉得答案不可能这么不优雅。

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        foursum = dict()
        for i in nums1:
            for j in nums2:
                if i+j in foursum:
                    foursum[i+j] += 1
                else:
                    foursum[i+j] = 1
        count = 0
        for i in nums3:
            for j in nums4:
                if 0-i-j in foursum:
                    count += foursum[-i-j]
        return count

Leetcode 383 赎金信

这题挺简单的,基本和242差不多。

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        m = dict()
        for i in magazine:
            if i in m:
                m[i] += 1
            else:
                m[i] = 1

        for j in ransomNote:
            if j in m:
                m[j] -= 1
            else:
                return False
        
        return False if any(i < 0 for i in m.values()) else True

Leetcode 15 三数之和

感觉这题的关键在于去重,一开始没注意审题,以为只要索引不同就行了,原来是要三元数组也要不一样才满足条件

  • nums[i]:相同的跳过就行了,但是考虑到是三元数组内是允许有相同元素的,所以还要考虑只有比遍历过的才跳过,所以要和nums[i-1]的进行比较。
  • nums[left]nums[right]也执行差不多的去重步骤。
# 这个是没有去重的,框架比较清晰
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n, res =len(nums), []
        nums.sort()
        for i in range(n):
            left, right = i + 1, n - 1
            while left < right:
                total = nums[i] + nums[left] + nums[right]
                if total < 0: left +=1
                elif total > 0: right -= 1
                else:
                    res.append([nums[i], nums[left], nums[right]])
                    left += 1
                    right -= 1
        return res
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n, res =len(nums), []
        nums.sort()
        for i in range(n):
            left, right = i + 1, n - 1
            if nums[i] > 0: break # 剪枝
            if i >= 1 and nums[i] == nums[i-1]: continue # 去重 nums[i]
            while left < right:
                total = nums[i] + nums[left] + nums[right]
                if total < 0: left +=1
                elif total > 0: right -= 1
                else:
                    res.append([nums[i], nums[left], nums[right]])
                    while left != right and nums[left] == nums[left + 1]: left += 1 # 去重nums[left]
                    while left != right and nums[right] == nums[right - 1]: right -= 1 # 去重nums[right]
                    left, right = left + 1, right -1
        return res

看到之前的解法,是用中间扩散法+后处理的去重,时间确实慢很多。

Leetcode 18 四数之和

确实和三数之和比较像,就是多了一层循环。

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        n, res = len(nums), []
        if n < 4: return [] # 剪枝
        for i in range(n):
            if i > 0 and nums[i] == nums[i - 1]: continue # 去重 a
            for k in range(i+1, n):
                if k > i + 1 and nums[k] == nums[k-1]: continue # 去重 b
                p, q = k + 1, n - 1
                while p < q:
                    if nums[i] + nums[k] + nums[p] + nums[q] > target: q -= 1
                    elif nums[i] + nums[k] + nums[p] + nums[q] < target: p += 1
                    else:
                        res.append([nums[i], nums[k], nums[p], nums[q]])
                        while p < q and nums[p] == nums[p + 1]: p += 1 # 去重 c
                        while p < q and nums[q] == nums[q - 1]: q -= 1 # 去重 d
                        p, q = p + 1, q - 1
        return res

Day8 字符串:反转字符串 + 反转字符串II + 替换空格 + 翻转字符串里的单词 + 左旋转字符串

Leetcode 344 反转字符串

感觉这题挺简单的

class Solution:
    def reverseString(self, s: List[str]) -> None:
        n = len(s)
        left, right = 0, n-1
        while left < right:
            s[left], s[right] = s[right], s[left]
            left, right = left + 1, right - 1

Leetcode 541 反转字符串II

这题也挺简单的,学了join()的用法

class Solution:
    def reverseStr(self, s: str, k: int) -> str:
        res = list(s)
        for cur in range(0, len(s), 2 * k):
            res[cur: cur + k] = reversed(res[cur: cur + k])
        return ''.join(res)

剑指offer 05 替换空格

新建了一个数组,挺简单的,如果有时间的话,做一下就在原数组上操作的解法。

class Solution:
    def replaceSpace(self, s: str) -> str:
        count = s.count(" ")
        res = [0 for _ in range(len(s) + count*2)]
        j = 0
        for i in s:
            if i == ' ':
                res[j:j+3] = '%20'
                j += 3
            else:
                res[j] = i
                j += 1
        return ''.join(res)

Leetcode 151 反转字符串中单词(没做)

这题简直超级加倍,一旦不能使用库函数split之后,就变得特别的麻烦,和设计链表有得一拼,一题更胜3题。

  • 去除数组内多余元素
  • 数组翻转
    我先跳过了,周日再做吧

剑指Offer58-II.左旋转字符串

就是反转再反转,如果使用切片或者反转函数reversed的话,还是很简单的。如果都不给用的话,就只能手写一个reversed函数了。

class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        def reversedsub(lst, left, right):
            while left < right:
                lst[left], lst[right] = lst[right], lst[left]
                left, right = left+1, right-1
            return lst

        res = list(s)
        end = len(res) - 1
        reversedsub(res, 0, n-1)
        reversedsub(res, n, end)
        reversedsub(res, 0, end)
        return ''.join(res)

终于把28号的题目大致刷完了。感觉可以对数组字符反转类的做一个总结。

Day9 KMP

Day10 字符串:重复的子字符串(KMP算法应用)

Day11 栈与队列:用栈实现队列 + 用队列实现栈 + 有效的括号 + 删除字符串中所有相邻重复项

又是两道模拟题,我估计最讨厌的就是模拟类型的题目了。

第一和第二题的相互实现,其实可以放在一起来讲。

首先要知道,队列和栈分别有什么操作:

  • queue.push
  • queue.pop
  • queue.peek
  • queue.empty

以上是队列的操作,栈的操作有 :

  • stack.push
  • stack.pop
  • stack.top
  • stack.empty

然后就是两种数据结构之间的操作是如何映射的。

Leetcode232 用栈实现队列

看了代码随想录的动画,要实现队列的实现需要两个栈,一个stack-in,一个stack-out,分别对进出队列进行处理。至于队列的操作和两个栈的更新有什么映射操作,我观察:

  • queue.push:只需要stack-in正常push就行了。(这就是stack-in的更新)
  • queue.pop:出队列就需要pop操作stack-out栈了,如果stack-out栈是空的话,就需要先将stack-in栈的元素给pop出来然后pushstack-out,如果stack-in也是空的话,那队列就是empty了。
  • queue.peek:就相当于stack-out.top了。
  • queue.emptystack-instack-out都是空的话,队列就是空的。

操作的重点在queue.pop上。

回到python的实现上,python没有内置的stack,只能使用list来代替实现。

class MyQueue:

    def __init__(self):
        self.stack_in = []
        self.stack_out = []

    def push(self, x: int) -> None:
        self.stack_in.append(x)

    def pop(self) -> int:
        if self.empty(): return None

        if self.stack_out: return self.stack_out.pop()
        else:
            for _ in range(len(self.stack_in)):
                self.stack_out.append(self.stack_in.pop())
            return self.stack_out.pop()

    def peek(self) -> int:
        ans = self.pop()
        self.stack_out.append(ans)
        return ans

    def empty(self) -> bool:
        return not (self.stack_in or self.stack_out)

Leetcode225 使用队列来实现栈

感觉python也只能用list来模拟队列

栈的操作:

  • stack.push:queue.push就可以实现
  • stack.poppop要将队尾的元素弹出,翻转队列,然后弹出,再翻转回来可以吗 (最优解:除了队尾的,全部pop一遍然后再push一遍,最后弹出头)
  • stack.empty:就是队列为空了
  • stack.size:就是队列的长度
class MyStack:

    def __init__(self):
        self.queue = deque()

    def push(self, x:int) -> None:
        self.queue.append(x)

    def pop(self):
        if self.empty(): return None
        for _ in range(len(self.queue) - 1):
            tmp = self.queue.popleft()
            self.queue.append(tmp)
        return self.queue.popleft()

    def empty(self):
        return len(self.queue) == 0

    def top(self):
        if self.empty(): return None
        return self.queue[-1]

Leetcode20 有效括号

括号只有配对了才有效:

  • 遇到左括号,就进栈
  • 遇到右括号,就将对应左括号出栈。不是的话就false

最后为空就true,不为空就false

主要就是左右括号的配对使用什么数据结构来存放。我想用dict,也就是set,然后将key存放右括号,value存放左括号

python的stack操作怎么实现,list

class Solution(object):
    def isValid(self, s):
        path = {")":"(", "]":"[", "}":"{"}
        stack = []

        for i in s:
            if i in path.keys():
                if stack and stack[-1] == path[i]:
                    stack.pop()
                else:
                    return False
            else:
                stack.append(i)

        return True if not stack else False

Leetcode1047 删除字符串中的所有相邻重复项

其实感觉和删除括号那题差不多。都是对称匹配的问题,使用stack来解决特别方便,遇到相同的就pop,遇到不同的就push

class Solution(object):
    def removeDuplicates(self, s):
        stack = []
        for i in s:
            if stack and stack[-1] == i:
                stack.pop()
            else:
                stack.append(i)
        return ''.join(stack)

终于把day11的补上了,周日勉强喘息了一下,但是kmp的两天还没有时间去看了。准备之后慢慢补,目前就按着进度继续打卡好了。

Day13 栈与队列:逆波兰表达式求值 + 滑动窗口最大值 + 前K个高频元素

Leetcode150 逆波兰表达式求值

好像之前做过,其实了解了这个逆波兰表达式的运算步骤的话,挺简单的了,但是还是出了一些小问题,没有对eval的结果取整,导致有部分测试的结果差1。

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        op, res= {"+", "-", "*", "/"}, []
        for i in tokens:
            if i in op:
                a, b = res.pop(), res.pop()
                res.append(int(eval(f"{b}{i}{a}")))
            else:
                res.append(i)
        return int(res.pop())

Leetcode239 滑动窗口最大值

第一眼看见题目,就想着使用暴力法求解,但是我看了一眼今日的标题,看起来事情并没有这么简单,得往栈和队列那里凑。好吧,想不出来。

看了题解,主要有两种常见的思路:

  • 优先队列(堆):大根堆,还是第一次听说,python有自带的小根堆
  • 单调队列:python没有,得自己实现。

优先队列

  • 优先队列的队首是优先级最高的。
  • 实现:可以使用不同的数据结构实现
    • 堆:使用堆来实现,入队和出队的复杂度都是O(logn)
    • 列表:入队是O(n),出队是O(1)的复杂度

Day38 斐波那契数列 + 爬楼梯 + 使用最小花费爬楼梯

Leetcode509 斐波那契数列

动态规划入门的基础题了,但是一开始还是没搞对,只考虑了n >= 2的情况,没有考虑n < 2的情况。果然是考虑不周到。动态规划比较重要的一点就是要将所有情况都考虑在内。

class Solution(object):
    def fib(self, n):
        f = [0, 1]
        if n < 2:
            return f[n]
        for i in range(2, n+1):
            f.append(f[i-1] + f[i-2])
        return f[-1]

这是看到的另外一个解法,就是仅仅针对一维数组的动态规划,双变量轮转法。时间和空间都非常好。

class Solution(object):
    def fib(self, n):
        if n < 2:
            return n
        a, b = 0, 1
        for i in range(2, n + 1):
            a, b = b, a + b
        return b

Leetcode70 爬楼梯

来了一道动态规划的题目,因为不熟悉,打算使用闫式DP分析法来分析一波,理清一下思路。

  • 应该是一维的动态规划,所以是f(n)
  • f(n)表示什么呢? -> 集合:所有爬上n阶楼梯的方法
  • 我们这题要求解的是什么呢? -> 属性:count:不同爬梯方法的计数
  • 状态方程
    • 最后一步上1阶的情况:f(n-1)
    • 最后一步上2阶的情况:f(n-2)

这么一通分析下来,其实和斐波那契数列509那题差不多,只是f[0]=1,这也是我考虑边界的时候出错的地方,以为上0节台阶,应该方法就为0,实际上不动也当成是一种方式。

当然这题因为是一维的动态规划,所以也可以用双变量轮转法。

class Solution(object):
    def climbStairs(self, n):
        f = [1, 1]  # 对于0阶,1阶,就是1和1
        if n < 2:
            return f[n]
        for i in range(2, n+1):
            f.append(f[i-1] + f[i-2])
        return f[-1]

Leetcoda746 使用最小花费爬楼梯

自己的错误思路正确的思路
集合爬上n阶楼梯的所有方案集合,
f(i)表示前i步最小的cost
同左,但是有一点没有考虑,cost数组长度为n
数组索引为n-1,就是说上n阶台阶,就要将n-1cost算上。
属性min:最小的cost同左
状态方程1. 通过1步到达第i阶楼梯的:f(i-1)
2. 通过2步到达第i阶楼梯的:f(i-2)
1. f(i-1) + cost(i-1)
2. f(i-2) + cost(i-2)
finalf(i) = min(f(i-1) + cost(i-1), f(i-2) + cost(i-2))
边界条件没考虑清楚0层和1层不需要爬,
因为可以直接到达2层,所以f(0)=f(1)=0
这个也是没考虑到的地方。

如果把上面的都理清楚的话,题解就呼之欲出了:

class Solution(object):
    def minCostClimbingStairs(self, cost):
        n = len(cost) + 1
        f = [0] * n
        for i in range(2, n):
            f[i] = min(f[i-1] + cost[i-1], f[i-2] + cost[i-2])
        return f[-1]

Day39 不同路径 + 不同路径II

Leetcode62 不同路径

  • 这个一眼看过去就是二维动态规划f(m, n)
  • 集合:到达网格m-1,n-1的唯一线路集合
  • 属性:count,唯一线路计数
  • 状态方程:f(m, n) = f(m-1, n) + f(m, n-1)
  • 边界条件:f(0, 0), f(0, 1), f(1, 0)都是1

出错点:遍历我一开始从2开始遍历,实际上要从1开始。

class Solution(object):
    def uniquePaths(self, m, n):
        f = [[1] * n for _ in range(m)]
        for i in range(1, m):
            for j in range(1, n):
                f[i][j] = f[i-1][j] + f[i][j-1]
        return f[-1][-1]

但是上面这个是没有经过优化的,我们要对动态规划进行空间的优化,使用滚动数组进行优化:

我们分析状态方程f(m, n) = f(m-1, n) + f(m, n-1)可以知道,当前网格,只与正上方的网格和左边的网格相关。也就是说,我们可以将其优化成f(n) = f(n) + f(n-1), 因为:

  • 左边的f(n)表示当前值,右边的f(n)表示上一行的f(n)
  • f(n-1)则是左边的网格。
class Solution(object):
    def uniquePaths(self, m, n):
        f = [1] * n
        for i in range(1, m):
            for j in range(1, n):
                f[j] = f[j] + f[j-1]
        return f[-1]

Leetcode63 不同路径II

  • 带障碍的路径,还是f(m, n)
  • 集合:到达网格m-1, n-1的路径和
  • 属性:count
  • 状态方程:f(m, n) = f(m-1, n) + f(m, n-1), ob(m-1, n) or ob(m, n-1) != 1

上面这个分析还行,和上面一题差不多,但是边界条件感觉被教育了一翻,大概是二维动态规划的边界条件和一维的还是有很大区别的。

我下面这段代码是错误的,因为没有正确的考虑第一行和第一列的边界条件,觉得只要决定f[0][0]f[0][1]f[1][0]的情况就行了。其实整一个第一列和第一行的边界都要考虑的,为的是避免输入矩阵只有一行或者一列的情况。

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        f = [[1] * n for _ in range(m)]
        # 初始边界条件没考虑好:
        f[0][1] = 0 if obstacleGrid[0][1] == 1 else 1
        f[1][0] = 0 if obstacleGrid[1][0] == 1 else 1
        # 只能从1,1开始,没办法从0,1和1,0开始
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 1:
                    f[i][j] = 0
                else:
                    f[i][j] = f[i-1][j] + f[i][j-1]
        return f[-1][-1]

正确的代码:

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        f = [[0] * n for _ in range(m)]
        
        # 第0,0个格子
        f[0][0] = 0 if obstacleGrid[0][0] == 1 else 1
        if f[0][0] == 0: return 0

        # 第一行:1~n
        for i in range(1, n):
            if obstacleGrid[0][i] == 1:
                break
            f[0][i] = 1

        # 第一列:1~m
        for i in range(1, m):
            if obstacleGrid[i][0] == 1:
                break
            f[i][0] = 1

        # 只能从1,1开始,没办法从0,1和1,0开始
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 0:
                    f[i][j] = f[i-1][j] + f[i][j-1]
        return f[-1][-1]

就是我们在这些网格中进行动态规划的话,必须要将边界条件分成3部分来考虑:

  • 第0,0个格子
  • 第一列,1~m个格子
  • 第一行:1~n个格子

Day41 整数拆分 + 不同的二叉搜索树

Leetcode343 整数拆分

自己的错误思路正确的思路
集合f(n)表示分成k个数的最大乘积同左
属性max:最大的乘积同左
状态方程f(n)可以分成1n-1,感觉有很多种分法都可以到达最后一步,
f(n) = max(f(1)*f(n-1), ..., f(n//2)f(n-n//2))
从小到大遍历的话应该可以
- 两个数相乘::f(i) = i-j * j
- 三个及以上数相乘:f(i) = f(i-j) * j)
finalf(i) = max(f(i), max((i-j)*j, f(i-j)*j))
边界条件不清楚f(0)=f(1)=0, f(2)=1
class Solution(object):
    def integerBreak(self, n):
        f = [0] * (n + 1) 
        f[2] = 1
        for i in range(3, n + 1):
            for j in range(1, i//2+1):
                f[i] = max(f[i], max(j*(i-j), j*f[i-j]))
        return f[n]

首先,这题踩的坑还是有点多的,也有很多疑问点:

  • 为什么要分为2和3以上两种情况进行讨论呢?
    • 存在n本身比拆分n之后相乘还要大的情况。
  • 为什么f要初始化成n+1个元素呢?(初始条件)
    • 实际f[0]是没有任何意义的,也就是索引和n差了一位,就是f[n]对应数字n
    • 所以相比于之前设置f[0],f[1],这题的初始条件相当于是f[1], f[2]
  • 为什么还要将f[i]也要放进去比较大小?
    • max函数内的f[i]只是用来记录上一个状态的,但是是否所有的动态规划求极值都要有上一个状态?这个值得思考。

Leetcode96 不同的二叉搜索树

就是给定一个数字n,看1~n可组成几棵不同的二叉搜索树。

首先回顾一下二叉搜索树,left < root < right

这算是一道动态规划 + 二叉搜索树的题目。

这个分析我确实想不出来:

  • 集合:f(n)表示组成二叉搜索树的数量
  • 属性:count
  • 状态方程:f(n) = f(0)f(n-1) + f(1)f(n-2) + ... + f(n-1)f(0)

f(0) 表示根的左边没有元素,其实问题就是左右子树分配多少个元素。因为二叉搜索树的特性,左右子树的序列肯定是递增的,所以关键不在于root是什么元素,而在于左右子树分别分配多少长度的序列。不同长度的序列,组合成的子树结构数量是确定的。

但是这种状态方程,我也是第一次见到:

class Solution(object):
    def numTrees(self, n):
        f = [0] * (n + 1)  # 同343原因一样,因为f[0]没有任何实际意义
        f[0] = f[1] = 1
        for i in range(2, n+1):
            for j in range(1, i+1):
                f[i] += f[j-1] * f[i-j]
        return f[n]

以上纯递归的解法:

但是听说这种状态转移方程,在数学上叫做“卡特兰数”,有一个非常简单的递推公式可以方便的求得f(n)
C 0 = 1 , C n + 1 = 2 ( 2 n + 1 ) n + 2 ⋅ C n C_0 = 1, \qquad C_{n+1} = \frac{2(2n + 1)}{n+2} \cdot C_n C0=1,Cn+1=n+22(2n+1)Cn
使用这个公式来解答的话,感觉就更简单了:

class Solution(object):
    def numTrees(self, n):
        c = 1
        for i in range(0, n):
            c = c * 2 * (2 * i + 1) / (i + 2)
        return int(c)

Day42 01背包 + 分割等和子集

Leetcode416 分割等和子集

一开始碰到这道题,我也知道要用0-1背包,但是要怎么用,却有点小问题(我可能比较薄弱的环节就是问题抽象化和转化)。

其实可以转换成背包容量为sum(nums) // 2的背包问题,因为如果存在子序列的和为sum(nums) // 2,那么剩下的子序列的和也必然是sum(nums) // 2。这样就能等和的分割子集了。但是还有很多问题。

按照分析法梳理思路:

  • 集合:f(i, j)是指在0~i范围内,物体重量小于j的选择集合。也就是i表示物品选择的范围,某种意义是索引的上限,j也是表示范围,也是重量的上限,在别的题目里,可以是某种约束的上限。所以i, j其实都表示上限,默认的下限都是0
  • 属性:bool,如果存在就为True,不存在就为False
  • 状态方程:
    • j > nums[i]:就是要放入背包的物品重量没有超过了重量的上限。在这里就是选择的第i个数小于sum(nums) // 2了。
      • i个物品放入背包,也就是说相当于在0~i-1范围内选剩下的,重量的上限也变成了j - nums[i],因为部分背包的重量已经被第i个物品占据了:f(i, j) = f(i-1, j-nums[i])
      • i个物品不放入背包,也就是说在0~i-1范围内选择,但是重量的上限还是jf(i, j) = f(i-1, j)
    • j < nums[i]:第i个物体的重量已经超过背包上限了,所以必然不可能放进背包里面。
      • 同上面第二种情况:f(i, j) = f(i-1, j)
  • 边界条件:
    • f(0, 0) = True,因为不选择任何物体,物体限制也是0,所以肯定是True
    • f(0, 0~j) 第一行:只能够选择物体0,也就是只有重量nums[0]为True,f(0, nums[0]) = True,其他为False
    • f(0~i, 0) 第一列:重量限制为0,存在啥都不选的情况,使得结果为True,所以f(0~i, 0) = True
class Solution(object):
    def canPartition(self, nums):
        # 不满足的情况
        s, n, m, mid = sum(nums), len(nums), max(nums), sum(nums)//2
        if n < 2 or s & 1 or m > mid:
            return False
        f = [[False] * (mid+1) for _ in range(n)]  # 初始化二维数组, n x (target + 1)

        # 边界条件初始化
        for i in range(n):
            f[i][0] = True  # 00和第一列
        f[0][nums[0]] = True  # 第一行

        # 正式开始计算
        for i in range(1, n):
            for j in range(1, mid+1):
                if j >= nums[i]:
                    f[i][j] = f[i-1][j] | f[i-1][j-nums[i]]
                else:
                    f[i][j] = f[i-1][j]
        return f[-1][-1]

进行空间的优化:

上述代码的空间复杂度是 O ( n × m i d ) O(n×mid) O(n×mid)。但是可以发现在计算 f(i, j) 的过程中,每一行的 f(i, j) 值都只与上一行(i-1)行的 f(i-1, j) 值有关,因此只需要一个一维数组即可将空间复杂度降到 O ( m i d ) O(mid) O(mid)。此时的转移方程为:f[j]=f[j] ∣ f[j−nums[i]]

且需要注意的是第二层的循环我们需要从大到小计算,因为如果我们从小到大更新 j 值,那么在计算 f[j] 值的时候,f[j−nums[i]] 已经是被更新过的状态,不再是上一行的 j 值。

class Solution(object):
    def canPartition(self, nums):
        # 不满足的情况(和动态规划无关的条件)
        s, n, m, mid = sum(nums), len(nums), max(nums), sum(nums) // 2
        if n < 2 or s & 1 or m > mid:
            return False

        f = [False] * (mid + 1)  # 初始化数组, (mid + 1)

        f[nums[0]] = True  # 边界条件初始化

        # 正式开始计算
        for i in range(1, n):
            for j in range(mid, 0, -1):
                if j >= nums[i]:
                    f[j] = f[j] | f[j - nums[i]]
        return f[-1]

Day43 最后一块石头的重量II + 目标和 + 一和零

Leetcode474 一和零

有了上面对0-1背包的理解,立即就想到了使用0-1背包来解,而且也知道是三维动态规划。

分析一波:

  • 集合:f(i, jm, jn)表示0~i索引范围内, 0 0 0数量不大于jm 1 1 1数量不大于jn的子集
  • 属性:count
  • 状态方程:
    • stras[jm] < m and stras[jn] < n:
      • if[i][m][n] = f[i-1][m-jm][n-jn] + 1
      • 不选i: f[i][m][n] = f[i-1][m][n]
    • stras[jm] > m or stras[jn] > n
      • 不选i: f[i][m][n] = f[i-1][m][n]
  • 边界条件:就是三条边都要初始化
    • f[0][0][0] = 0
    • f[0][:m][0] = 0
    • f[0][0][:n] = 0
    • f[i][0][0] = 0

但是我忽略了max这个条件。

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        f = [[[0] * (n+1) for _ in range(m+1)] for _ in range(len(strs)+1)]  # 初始化数组,顺序和遍历顺序相关
        # 为啥有些是len + 1,有些直接len
        
        for i in range(1, len(strs)+1):
            ones, zeros = strs[i-1].count('1'), strs[i-1].count('0')  # 为啥是i-1的
            for jm in range(m+1):
                for jn in range(n+1):
                    if jm < zeros or jn < ones:
                        f[i][jm][jn] = f[i-1][jm][jn]
                    else:
                        f[i][jm][jn] = max(f[i-1][jm][jn], f[i-1][jm-ones][jn-zeros] + 1)  # 没考虑到max
        
        return f[-1][-1][-1]

感觉现在对空间优化也小有心得:

  • 降维
  • 如果j有减操作,就将遍历顺序从大到小翻转
class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        length = len(strs)
        dp = [[0] * (n+1) for _ in range(m+1)]

        for i in range(1, length+1):
            c0 = strs[i-1].count('0')     
            c1 = len(strs[i-1]) - c0      
            for j in range(m, -1, -1):    
                for k in range(n, -1, -1):
                    if j >= c0 and k >= c1:  
                        dp[j][k] = max( dp[j][k], dp[j-c0][k-c1] + 1 )
        return dp[-1][-1]

Leetcode494 目标和

使用动态规划的前提是先要进行一点小小的数学转换,将问题转化成动态规划问题。

就是我们假设,数组里面的数已经被赋予了符号,正数的和 p o s pos pos与负数的和 n e g neg neg(暂时都是正数): p o s − n e g = t a r g e t p o s + n e g = s u m pos - neg = target \\ pos + neg = sum posneg=targetpos+neg=sum所以我们可以得到: n e g = ( s u m + t a r g e t ) 2 neg = \frac{(sum + target)}{2} neg=2(sum+target)目标就变成了

  • 边界条件没有考虑周全
  • 使用len(nums) + 1来初始化数组,但是在使用索引遍历nums的时候,忘记-1
  • 忘记考虑neg为负数的情况就直接return 0
class Solution(object):
    def findTargetSumWays(self, nums, target):
        s = sum(nums)
        neg = (s + target) // 2
        if (s + target) % 2 == 1 or neg < 0:
            return 0

        f = [[0] * (neg+1) for _ in range(len(nums)+1)]

        f[0][0] = 1  # 边界条件

        for i in range(1, len(nums)+1):
            for j in range(neg + 1):
                if j < nums[i-1]:
                    f[i][j] = f[i-1][j]
                else:
                    f[i][j] = f[i-1][j-nums[i-1]] + f[i-1][j]
        return f[-1][-1]

空间优化后:

class Solution(object):
    def findTargetSumWays(self, nums, target):
        s = sum(nums)
        neg = (s + target) // 2
        if (s + target) % 2 == 1 or neg < 0:
            return 0

        f = [0] * (neg+1)
        f[0] = 1  # 边界条件

        for i in range(1, len(nums)+1):
            for j in range(neg, -1, -1):
                if j >= nums[i-1]:
                    f[j] = f[j-nums[i-1]] + f[j]
        return f[-1]

Leetcode1049 最后一块石头的重量II

这题和上一题挺像的,但是题目描述更贴近实际,就是抽象成数学问题要多走一步。

  • 集合:和leetcode 494差不多,也就是相当于将集合分成正负序列,因为对撞
  • 属性:max:最接近sum的一半
  • 状态转移:

出错点:

  • target值设置为了s//2+1,其实s//2才对。
  • f[j] = max(f[j], f[j-stones[i-1]] + stones[i-1])一开始这里忘记加回石头的重量了。
class Solution(object):
    def lastStoneWeightII(self, stones):
        s = sum(stones)
        target = s // 2  # 我原本设置为 s//2 + 1
        f = [[0] * (target + 1) for _ in range(len(stones) + 1)]

        for i in range(1, len(stones) + 1):
            for j in range(target + 1):
                if j < stones[i-1]:
                    f[i][j] = f[i-1][j]
                else:
                    f[i][j] = max(f[i-1][j], f[i-1][j-stones[i-1]] + stones[i-1])  # 这里也忘了加回石头重量
        return s - 2 * f[-1][-1]

空间优化:

class Solution(object):
    def lastStoneWeightII(self, stones):
        s = sum(stones)
        target = s // 2  # 我原本设置为 s//2 + 1
        f = [0] * (target + 1)

        for i in range(1, len(stones) + 1):
            for j in range(target, -1, -1):
                if j >= stones[i-1]:
                    f[j] = max(f[j], f[j-stones[i-1]] + stones[i-1])  # 这里也忘了加回石头重量
        return s - 2 * f[-1]

Day44 完全背包 + 零钱兑换II + 组合总和IV

Leetcode518 零钱兑换II

一开始按照0-1背包的思路去解,有一个k表示物品可以取无限次,没想到还能通过数学推导的方式化简掉。

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        
        n = len(coins)
        dp = [[0]*(amount+1) for _ in range(n+1)] 
        dp[0][0] = 1 
        
        for i in range(1, n+1):         
            for j in range(amount+1): 
                if j < coins[i-1]:      
                    dp[i][j] = dp[i-1][j]
                else:               
                    dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]]
        
        return dp[n][amount]

空间优化:

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        
        dp = [0]*(amount+1)         
        dp[0] = 1  
  
        for coin in coins:                 
            for j in range(coin, amount+1): 
                dp[j] += dp[j-coin]
        
        return dp[amount]

在完全背包的空间优化中,我理解了:

  • 内循环顺序:0-1背包和完全背包内循环顺序不一样,以及为啥不一样。
  • 内外循环是背包或者物体:主要根据要求的count是排列还是组合来进行区分。
    • 排列数:先遍历背包,再遍历物品
    • 组合数:先遍历物品,再遍历背包

但是amount内循环从coin开始遍历,我还是没理解完全。

Leetcode377 组合总和IV

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值