Leecode刷题记录(Python)(按代码随想录顺序)

1 数组

图、知识点、大部分题解源代码随想录

代码随想录

704 二分查找

二分查找的前提:有序数组、无重复元素

二分查找注意事项:

1、中间值mid取法

应该用 mid = l + (r-l)/ 2 ,而不是 mid = (l + r) / 2,因为如果 l 和 r都很大,那么(l + r)将会溢出整数范围。

2、递归结束条件

左边界>有边界

3、边界更新

left=mid+1或right=mid-1

(第二种递归结束和边界更新:while (left >= right)时结束边界,left=mid或right=mid)

我的解法:

时间复杂度 O(log⁡N): 其中 N 为数组 nums 长度。二分查找使用对数级别时间。
空间复杂度 O(1) : 变量 i , j 使用常数大小空间。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        right = len(nums) - 1
        left = 0
        self.nums = nums
        self.target = target
        return self.Recursion(left, right)

    def Recursion(self, left, right):
        mid = int(left + (right-left)/2)
        # 注意递归结束条件
        if left > right:
            return -1
        else:
            if self.nums[mid] > self.target:
                right = mid - 1
                # 一定要注意递归要return啊!!!!!
                return self.Recursion(left, right)
            elif self.nums[mid] < self.target:
                left = mid + 1
                return self.Recursion(left, right)
            elif self.nums[mid] == self.target:
                return mid
☆☆☆题解1:更简洁

原来一个while就可以递归quq...

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        i, j = 0, len(nums) - 1
        while i <= j:
            # //整数除法,向下取整
            m = (i + j) // 2
            if nums[m] < target: i = m + 1
            elif nums[m] > target: j = m - 1
            else: return m
        return -1

 35 搜索插入位置

☆☆☆我的解法:二分查找

关键在于不存在时最后的return,最后一轮循环时一定存在left=right=mid,然后分成两种情况讨论,最后的结果都是刚好在left上

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

69 x的平方根

☆☆☆我的解法:二分查找

分析思路同35

class Solution:
    def mySqrt(self, x: int) -> int:
        left, right = 1, x
        while left <= right:
            mid = left + (right - left) // 2
            mid2 = mid * mid
            if mid2 < x:
                left = mid + 1
            elif mid2 > x:
                right = mid - 1
            else:
                return mid
        return right

367 有效的完全平方数

☆☆☆我的解法:二分查找
class Solution:
    def isPerfectSquare(self, num: int) -> bool:
        left, right = 1, num
        while left <= right:
            mid = left + (right - left) // 2
            mid2 = mid * mid
            if mid2 < num:
                left = mid + 1
            elif mid2 > num:
                right = mid - 1
            else:
                return True
        return False

34 在排序数组中查找元素的第一个和最后一个位置

☆☆☆我的解法:二分查找
# 1、首先,在 nums 数组中二分查找 target;
# 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
# 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        left, right = 0, len(nums) - 1
        index = -1
        while left <= right:
            mid = left + (right - left) // 2
            if target < nums[mid]:
                right = mid - 1
            elif target > nums[mid]:
                left = mid + 1
            else:
                index = mid
                break
        if index == -1:
            return [-1, -1]
        i, j = index, index
        # 当i-1没有超出边界,且i-1也符合条件时
        while i - 1 >= 0 and nums[i-1] == target:
            i -= 1
        while j + 1 <= len(nums) - 1 and nums[j+1] == target:
            j += 1
        return [i, j]
题解:两个二分查找,分别找左右边界

考虑 target 开始和结束位置,其实我们要找的就是数组中「第一个等于 target 的位置」(记为 leftIdx)和「第一个大于 target 的位置减一」(记为 rightIdx)。

寻找target在数组里的左右边界,有如下三种情况:

  • 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
  • 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
  • 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
# 解法4
# 1、首先,在 nums 数组中二分查找得到第一个大于等于 target的下标leftBorder;
# 2、在 nums 数组中二分查找得到第一个大于等于 target+1的下标, 减1则得到rightBorder;
# 3、如果开始位置在数组的右边或者不存在target,则返回[-1, -1] 。否则返回[leftBorder, rightBorder]
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def binarySearch(nums:List[int], target:int) -> int:
            left, right = 0, len(nums)-1
            while left<=right: # 不变量:左闭右闭区间
                middle = left + (right-left) //2 
                # 这里是>=说明,左边可能还会有target(最差情况也是mid表示的接下来的右边界至少一个相等的),左边还有,所以往左缩小,找左边第一个
                if nums[middle] >= target: 
                    right = middle - 1
                else: # nums[middle] < target说明左边没有target,左边界也在右边,所以left右移
                    left = middle + 1
            return left  # 若存在target,则返回第一个等于target的值 

        leftBorder = binarySearch(nums, target) # 搜索左边界
        rightBorder = binarySearch(nums, target+1) -1  # 搜索右边界
        if leftBorder == len(nums) or nums[leftBorder]!= target: # 情况一和情况二
            return [-1, -1]
        return [leftBorder, rightBorder]

27 移除元素

我的解法:双向指针

思路是双指针指向两边,当左边的元素为目标值,而右边不是目标值时,交换两个元素,当左右下标相遇说明遍历了一遍数组。

需要单独判断空数组是因为最后return时的判断条件nums[left]超出边界了

时间复杂度:O(n),其中 n 为序列的长度。我们只需要遍历该序列至多一次。

空间复杂度:O(1)。我们只需要常数的空间保存若干变量。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        if len(nums)==0:
            return 0
        left, right = 0, len(nums)-1
        while left < right:
            if nums[left] == val:
                if nums[right] != val:
                    # 交换两个元素
                    nums[left], nums[right] = nums[right], nums[left]
                    # python没有自增自减运算符!!!
                    left = left + 1
                    right = right - 1
                else :
                    right = right - 1
            else:
                left = left + 1
        return left+1 if nums[left]!=val else left
☆☆☆题解:双指针法(快慢指针法)

也是双指针但是都是从前往后移动:

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

让 a 一直往后移动,相当于 nums[a] 从数组第一个数遍历到最后一个数。
当且仅当我们发现 nums[a] != val 的时候,我们把这个数拷贝到 b 指向的位置,默认 b 是从 0 开始的,然后 b += 1 指向下一个位置。

时间复杂度:O(n),其中 n 为序列的长度。我们只需要遍历该序列至多两次。

空间复杂度:O(1)。我们只需要常数的空间保存若干变量。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 快慢指针
        fast = 0  # 快指针
        slow = 0  # 慢指针
        size = len(nums)
        while fast < size:  # 不加等于是因为,a = size 时,nums[a] 会越界
            # slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        return slow
☆☆☆题解2:更好的双向指针法

无需单独判断空数组

# 相向双指针法
# 时间复杂度 O(n)
# 空间复杂度 O(1)
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        n = len(nums)
        left, right  = 0, n - 1
        while left <= right:
            while left <= right and nums[left] != val:
                left += 1
            while left <= right and nums[right] == val:
                right -= 1
            if left < right:
                nums[left] = nums[right]
                left += 1
                right -= 1
        return left
                

26 删除排序数组中的重复项

我的解法:pop
class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        i = 0
        while i < len(nums):
            if i+1 < len(nums) and nums[i+1] == nums[i]:
                nums.pop(i+1)
            elif i+1 < len(nums) and nums[i+1] != nums[i]:
                i += 1
            else:
                return len(nums)
        return 0
☆☆☆题解:双指针
class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if not nums:
            return 0
        
        n = len(nums)
        # 从1下标开始,因为如果数组只有一个元素,它自己肯定不会重复
        fast = slow = 1
        while fast < n:
            # 注意是和fast-1比
            if nums[fast] != nums[fast - 1]:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        # 返回slow,slow是下标,下标-1+1表示元素个数,即slow
        return slow

283 移动零

☆☆☆我的解法:双指针(不简洁danwon方法)

图源:

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        i, j = 0, 0
        while j < len(nums):
            # 找到最左边的0
            if i + 1 < len(nums) and nums[i] != 0:
                i += 1
                # j一定在i的右侧
                j = i + 1
                continue
            # 找到0右边的非零数,交换
            if nums[j] != 0:
                nums[i], nums[j] = nums[j], nums[i]
                j += 1
                i += 1
            else:
                j += 1

844 比较含退格的字符串

我的解法:求最终s和t然后比较
class Solution:
    def backspaceCompare(self, s: str, t: str) -> bool:
        i, j = 0, 0
        s = list(s)
        t = list(t)
        while i < len(s):
            if s[i] == "#" and i>0:
                s.pop(i)
                s.pop(i-1)
                i = i - 1
            elif s[i] == "#" and i==0:
                s.pop(i)
            else:
                i += 1
        while j < len(t):
            if t[j] == "#" and j>0:
                t.pop(j)
                t.pop(j-1)
                j = j - 1
            elif t[j] == "#" and j==0:
                t.pop(j)
            else:
                j += 1
        return True if s == t else False
☆☆☆题解:双指针从后往前遍历

class Solution:
    def backspaceCompare(self, S: str, T: str) -> bool:
        i, j = len(S) - 1, len(T) - 1
        skipS = skipT = 0

        while i >= 0 or j >= 0:
            while i >= 0:
                if S[i] == "#":
                    skipS += 1
                    i -= 1
                elif skipS > 0:# 不是退格且skip不空,则当前元素应该被跳过
                    skipS -= 1
                    i -= 1
                else:# 不是空格,且skip空,当前元素应该保留去做对比
                    break
            while j >= 0:# T同理
                if T[j] == "#":
                    skipT += 1
                    j -= 1
                elif skipT > 0:
                    skipT -= 1
                    j -= 1
                else:
                    break
            if i >= 0 and j >= 0:
                if S[i] != T[j]:
                    return False
            elif i >= 0 or j >= 0:
                return False
            i -= 1
            j -= 1
        
        return True

作者:御三五 🥇
链接:https://leetcode.cn/problems/backspace-string-compare/solutions/683776/shuang-zhi-zhen-bi-jiao-han-tui-ge-de-zi-8fn8/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

977 有序数组的平方

我的解法:暴力排序

先平方后排序,时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度

时间复杂度:O(nlog⁡n),其中 n 是数组 nums 的长度。

空间复杂度:O(log⁡n)。除了存储答案的数组以外,我们需要 O(log⁡n) 的栈空间进行排序。

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        for i in range(len(nums)):
            nums[i] = nums[i] ** 2
        nums.sort()
        return nums
☆☆☆题解:双指针——归并排序

数组其实是有序的, 只不过负数平方之后可能成为最大数了。

那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。

此时可以考虑双指针法了,i指向起始位置,j指向终止位置。

定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。

如果A[i] * A[i] < A[j] * A[j] 那么result[k--] = A[j] * A[j];

如果A[i] * A[i] >= A[j] * A[j] 那么result[k--] = A[i] * A[i];

时间复杂度:O(n),其中 n 是数组 nums 的长度。

空间复杂度:O(1)。除了存储答案的数组以外,我们只需要维护常量空间。

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        # 需要提前定义列表,用来存放结果
        ans = [0] * n
        i, j = 0, n - 1
        # for循环:n-1,n-2,n-3...0,即从n-1到-1的左闭右开,即n-1到0的左闭右闭区间
        for p in range(n - 1, -1, -1):
            x = nums[i] * nums[i]
            y = nums[j] * nums[j]
            if x > y:  # 更大的数放右边
                ans[p] = x
                # x是统计负数的,指针从左向右走
                i += 1
            else:
                ans[p] = y
                # x是统计正数的,指针从右向左走
                j -= 1
        return ans
题解2:暴力排序+列表推导法
class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        return sorted(x*x for x in nums)

209 长度最小的子数组

我的解法:暴力法

两个指针,一个记录子数组首元素下标,另一个记录子数组尾元素下标,两个for循环

时间复杂度:O(n2),其中 n 是数组的长度。需要遍历每个下标作为子数组的开始下标,对于每个开始下标,需要遍历其后面的下标得到长度最小的子数组。

空间复杂度:O(1)。

未通过,18测试用例会超时...

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        Min = 1000
        Sum = 0
        i,j = 0,0
        for i in range(len(nums)):
            for j in range(i, len(nums)):
                Sum += nums[j] 
                if Sum >= target 
                    if j-i+1 < Min:
                        Min = j-i+1
                    break
                else:
                    continue
            Sum = 0
        return Min if Min<1000 else 0
☆☆☆题解:滑动窗口

时间复杂度:O(n),其中 n 是数组的长度。指针 start 和 end 最多各移动 n 次。

空间复杂度:O(1)。

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        l = len(nums)
        left = 0
        right = 0
        min_len = float('inf')
        cur_sum = 0 #当前的累加值
        
        while right < l:# 遍历表示结束的指针
            cur_sum += nums[right]
            
            while cur_sum >= s: # 当前累加值大于目标值
                min_len = min(min_len, right - left + 1)
                cur_sum -= nums[left]# 窗口累加值缩小
                left += 1# 窗口缩小,缩小后继续判断cur_sum >= s,直到不满足为止再前移结束指针
            
            right += 1# 前移结束指针
        
        return min_len if min_len != float('inf') else 0
滑动窗口:

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了),然后判断是否满足窗口,若满足,继续缩小(起始位置向前移动),若不满足,结束位置前移(即窗口扩大)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

为什么时间复杂度是O(n)

不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

题解2:前缀+二分查找(不如滑动窗口)

方法一的时间复杂度是 O(n2),因为在确定每个子数组的开始下标后,找到长度最小的子数组需要 O(n)的时间。如果使用二分查找,则可以将时间优化到 O(log⁡n)。

为了使用二分查找,需要额外创建一个数组 sums 用于存储数组 nums 的前缀和,其中 sums[i] 表示从 nums[0]到 nums[i−1] 的元素和。得到前缀和之后,对于每个开始下标 i,可通过二分查找得到大于或等于 i 的最小下标 bound\,使得 sums[bound]−sums[i−1]≥s,并更新子数组的最小长度(此时子数组的长度是 bound−(i−1))。

因为这道题保证了数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分的正确性。如果题目没有说明数组中每个元素都为正,这里就不能使用二分来查找这个位置了。

现成的库和函数来为我们实现这里二分查找大于等于某个数的第一个位置的功能,Python 中的 bisect.bisect_left

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        if not nums:
            return 0
        
        n = len(nums)
        ans = n + 1
        sums = [0]
        for i in range(n):
            sums.append(sums[-1] + nums[i])
        
        for i in range(1, n + 1):
            target = s + sums[i - 1]# s是目标值,target是满足 目标值 + i-1前缀和 的bound前缀和
            bound = bisect.bisect_left(sums, target)
            if bound != len(sums):# 下标没有len(nums)
                ans = min(ans, bound - (i - 1))
        
        return 0 if ans == n + 1 else ans

904 水果成篮

☆☆☆我的解法:滑动窗口
class Solution:
    def totalFruit(self, fruits: List[int]) -> int:
        length = 0
        left, right = 0, 0
        kind = dict()
        while right < len(fruits):
            kind[fruits[right]] = kind.get(fruits[right],0) + 1
            while len(kind.keys()) > 2:
                if kind[fruits[left]] > 1:
                    kind[fruits[left]] -= 1
                else:
                    kind.pop(fruits[left])
                left += 1
            if right-left+1>length:
                length = right-left+1
            right += 1
        return length

79 最小覆盖子串

我的解法:滑动窗口
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        left, right = 0, 0
        son = dict()
        ton = list(t)
        MIN = s+'A'
        td = dict()
        for i in ton:
            td[i] = td.get(i,0)+1
        while right < len(s):
            son[s[right]] = son.get(s[right],0) + 1
            
            # 左一定要小于右 并且(左不在t中  或者  左在t中但数量超了)
            while left<right and ((s[left] not in ton) or son[s[left]]>td[s[left]]):
                if son[s[left]] > 1:
                    son[s[left]] -= 1
                else:
                    son.pop(s[left])
                left += 1
            
            # 长度小 并且 t中的每个都有
            if right - left +1 < len(MIN) and all(son.get(i,0)>=td[i] for i in ton):
                MIN = "".join(s[left:right+1])

            right +=1
        return MIN if len(MIN)<=len(s) else ""
题解:滑动窗口

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        ans_left, ans_right = -1, len(s)
        left = 0
        # counter计数器,是dict的子类
        cnt_s = Counter()  # s 子串字母的出现次数
        cnt_t = Counter(t)  # t 中字母的出现次数
        for right, c in enumerate(s):  # 移动子串右端点
            cnt_s[c] += 1  # 右端点字母移入子串
            # 这里居然能直接比较!!!(python 3.10 以上才行)
            # 考虑满足的时候就滑出直到不满足  而不是滑到满足
            while cnt_s >= cnt_t:  # 涵盖
                if right - left < ans_right - ans_left:  # 找到更短的子串
                    ans_left, ans_right = left, right  # 记录此时的左右端点
                cnt_s[s[left]] -= 1  # 左端点字母移出子串
                left += 1  # 移动子串左端点
        return "" if ans_left < 0 else s[ans_left: ans_right + 1]

作者:灵茶山艾府
链接:https://leetcode.cn/problems/minimum-window-substring/solutions/2713911/liang-chong-fang-fa-cong-o52mn-dao-omnfu-3ezz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
☆☆☆题解2:滑动窗口(优化)

看代码就懂了,less指的是t的元素,在s的子串中数量不够的有几个,比如s的子串是abcdef,t是aaack,那么less=2,因为a和k数量不够。

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        ans_left, ans_right = -1, len(s)
        left = 0
        cnt_s = Counter()  # s 子串字母的出现次数
        cnt_t = Counter(t)  # t 中字母的出现次数
        less = len(cnt_t)  # 有 less 种字母的出现次数 < t 中的字母出现次数
        for right, c in enumerate(s):  # 移动子串右端点
            cnt_s[c] += 1  # 右端点字母移入子串
            if cnt_s[c] == cnt_t[c]:
                less -= 1  # c 的出现次数从 < 变成 >=
            while less == 0:  # 涵盖:所有字母的出现次数都是 >=
                if right - left < ans_right - ans_left:  # 找到更短的子串
                    ans_left, ans_right = left, right  # 记录此时的左右端点
                x = s[left]  # 左端点字母
                if cnt_s[x] == cnt_t[x]:
                    less += 1  # x 的出现次数从 >= 变成 <(下一行代码执行后)
                cnt_s[x] -= 1  # 左端点字母移出子串
                left += 1  # 移动子串左端点
        return "" if ans_left < 0 else s[ans_left: ans_right + 1]

作者:灵茶山艾府
链接:https://leetcode.cn/problems/minimum-window-substring/solutions/2713911/liang-chong-fang-fa-cong-o52mn-dao-omnfu-3ezz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

59 螺旋矩阵II

我的解法:找规律(左开右闭)

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        # 创建二维空列表
        l1 = [[] for i in range(n)]
        for i in range(n):
            l1[i] = [None] * n
        i, j, k = 0, 0, 1
        while k <= n ** 2:
            # →
            while i + j <= n - 1 :
                l1[i][j] = k
                k += 1
                j += 1
            j -= 1
            i += 1
            # ↓
            while i <= j :
                l1[i][j] = k
                k += 1
                i += 1
            i -= 1
            j -= 1
            # ←
            while i + j >= n - 1 :
                l1[i][j] = k
                k += 1
                j -= 1
            j += 1
            i -= 1
            # ↑
            while i > j :
                l1[i][j] = k
                k += 1
                i -= 1
            i += 1
            j += 1
        return l1
☆☆☆题解:左闭右开原则

这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。

这也是坚持了每条边左闭右开的原则。

  • 时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
  • 空间复杂度 O(1)
class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        nums = [[0] * n for _ in range(n)]
        startx, starty = 0, 0               # 起始点
        loop, mid = n // 2, n // 2          # 迭代次数、n为奇数时,矩阵的中心点
        count = 1                           # 计数

        for offset in range(1, loop + 1) :      # 每循环一层偏移量加1,偏移量从1开始
            for i in range(starty, n - offset) :    # 从左至右,左闭右开
                nums[startx][i] = count
                count += 1
            for i in range(startx, n - offset) :    # 从上至下
                nums[i][n - offset] = count
                count += 1
            for i in range(n - offset, starty, -1) : # 从右至左
                nums[n - offset][i] = count
                count += 1
            for i in range(n - offset, startx, -1) : # 从下至上
                nums[i][starty] = count
                count += 1                
            startx += 1         # 更新起始点
            starty += 1

        if n % 2 != 0 :			# n为奇数时,填充中心点
            nums[mid][mid] = count 
        return nums
题解2:定义四个边界

好像和我的思路差不多,左开右闭,for循环比我写的while好...

class Solution(object):
    def generateMatrix(self, n):
        if n <= 0:
            return []
        
        # 初始化 n x n 矩阵
        matrix = [[0]*n for _ in range(n)]

        # 初始化边界和起始值
        top, bottom, left, right = 0, n-1, 0, n-1
        num = 1

        while top <= bottom and left <= right:
            # 从左到右填充上边界
            for i in range(left, right + 1):
                matrix[top][i] = num
                num += 1
            top += 1

            # 从上到下填充右边界
            for i in range(top, bottom + 1):
                matrix[i][right] = num
                num += 1
            right -= 1

            # 从右到左填充下边界

            for i in range(right, left - 1, -1):
                matrix[bottom][i] = num
                num += 1
            bottom -= 1

            # 从下到上填充左边界

            for i in range(bottom, top - 1, -1):
                matrix[i][left] = num
                num += 1
            left += 1

        return matrix

54 螺旋矩阵

我的解法:左闭右开
class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        res = []
        row, column = len(matrix), len(matrix[0])
        starx, stary = 0, 0
        count, offset = 0, 0
        while count < row* column-1:
            offset += 1
            for i in range(stary, column-offset):
                res.append(matrix[starx][i])
                count += 1
                if count == row * column:
                    break
            for i in range(starx, row-offset):
                res.append(matrix[i][column-offset])
                count += 1
                if count == row * column:
                    break
            for i in range(column-offset, stary, -1):
                res.append(matrix[row-offset][i])
                count += 1
                if count == row * column:
                    break
            for i in range(row-offset, starx, -1):
                res.append(matrix[i][stary])
                count += 1
                if count == row * column:
                    break
            starx += 1
            stary += 1
        if column == row and row %2!=0:
            res.append(matrix[row//2][row//2])
        # while len(res)>row* column:
        #     res.pop()
        return res
☆☆☆题解:左开右闭(除第一行),比较边界相遇情况

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix: return []
        l, r, t, b, res = 0, len(matrix[0]) - 1, 0, len(matrix) - 1, []
        while True:
            for i in range(l, r + 1): res.append(matrix[t][i]) # left to right
            t += 1
            if t > b: break
            for i in range(t, b + 1): res.append(matrix[i][r]) # top to bottom
            r -= 1
            if l > r: break
            for i in range(r, l - 1, -1): res.append(matrix[b][i]) # right to left
            b -= 1
            if t > b: break
            for i in range(b, t - 1, -1): res.append(matrix[i][l]) # bottom to top
            l += 1
            if l > r: break
        return res

作者:Krahets
链接:https://leetcode.cn/problems/spiral-matrix/solutions/2362055/54-luo-xuan-ju-zhen-mo-ni-qing-xi-tu-jie-juvi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

LCR 146 螺旋遍历二维数组 (和54一模一样)

我的解法:
class Solution:
    def spiralArray(self, array: List[List[int]]) -> List[int]:
        if not array:
            return []
        left, right = 0, len(array[0])-1
        top, bottom = 0, len(array)-1
        res = []
        while True:
            for i in range(left, right+1):
                res.append(array[top][i])
            top += 1
            if top > bottom:
                break

            for i in range(top,bottom+1):
                res.append(array[i][right])
            right -= 1
            if left > right:
                break

            for i in range(right, left-1,-1):
                res.append(array[bottom][i])
            bottom -= 1
            if top > bottom:
                break

            for i in range(bottom,top-1,-1):
                res.append(array[i][left])
            left += 1
            if left > right:
                break
        return res
            

2 链表

 tips:

  • 许多操作需要单独处理头结点,或设置虚拟头结点统一解决

知识点:

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链表的入口节点称为链表的头结点也就是head。

链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上。

  • 单链表:

        单链表中的指针域只能指向节点的下一个节点。

  • 双链表:

    每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

    双链表 既可以向前查询也可以向后查询。

  • 循环链表

        链表首尾相连。循环链表可以用来解决约瑟夫环问题。

  • 删除节点

        只要将C节点的next指针 指向E节点就可以了。

        Python有自己的内存回收机制,不用自己手动释放D。

        要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。

  • 添加节点

  • 与数组对比

        数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

        链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

  • python定义链表:

        用类

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

203 移除链表元素

我的题解:单独处理头结点

写的太复杂了

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        while head:
            if head.val == val:
                head = head.next
            else:
                break
        n_node = head
        if n_node:
            while n_node.next:
                if n_node.next.val != val:
                    n_node = n_node.next
                else:
                    n_node.next = n_node.next.next
            return head
        else:
            return None
☆☆☆题解:虚拟头结点
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        # 创建虚拟头部节点以简化删除过程
        dummy_head = ListNode(next = head)
        
        # 遍历列表并删除值为val的节点
        current = dummy_head
        while current.next:
            if current.next.val == val:
                current.next = current.next.next
            else:
                current = current.next
        
        return dummy_head.next

707 设计链表

我的题解:单链表,创建了虚拟头结点

注意查询、添加、删除的题目要求,需要比较index和链表长度

class ListNode:
    def __init__(self, val = 0, next = None):
        self.val = val
        self.next = next
class MyLinkedList:

    def __init__(self):
        self.dummy_head = ListNode()
        self.len = 0

    def get(self, index: int) -> int:
        current = self.dummy_head
        if index < self.len:
            for i in range(0, index):
                    current = current.next
            return current.next.val
        else:
            return -1

    def addAtHead(self, val: int) -> None:
        new_head = ListNode(val, self.dummy_head.next)
        self.dummy_head.next = new_head
        self.len += 1

    def addAtTail(self, val: int) -> None:
        current = self.dummy_head
        while current.next:
            current = current.next
        new_tail = ListNode(val, None)
        current.next = new_tail
        self.len += 1

    def addAtIndex(self, index: int, val: int) -> None:
        current = self.dummy_head
        if index <= self.len:
            for i in range(0, index):
                current = current.next
            new_node = ListNode(val, current.next)
            current.next = new_node
            self.len += 1

    def deleteAtIndex(self, index: int) -> None:
        current = self.dummy_head
        if index < self.len:
            for i in range(0, index):
                current = current.next
            current.next = current.next.next
            self.len -= 1

# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
☆☆☆题解:单链表

这个对index的判断更加严谨,创建新节点直接赋值更方便

(版本一)单链表法
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
        
class MyLinkedList:
    def __init__(self):
        self.dummy_head = ListNode()
        self.size = 0

    def get(self, index: int) -> int:
        if index < 0 or index >= self.size:
            return -1
        
        current = self.dummy_head.next
        for i in range(index):
            current = current.next
            
        return current.val

    def addAtHead(self, val: int) -> None:
        self.dummy_head.next = ListNode(val, self.dummy_head.next)
        self.size += 1

    def addAtTail(self, val: int) -> None:
        current = self.dummy_head
        while current.next:
            current = current.next
        current.next = ListNode(val)
        self.size += 1

    def addAtIndex(self, index: int, val: int) -> None:
        if index < 0 or index > self.size:
            return
        
        current = self.dummy_head
        for i in range(index):
            current = current.next
        current.next = ListNode(val, current.next)
        self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        if index < 0 or index >= self.size:
            return
        
        current = self.dummy_head
        for i in range(index):
            current = current.next
        current.next = current.next.next
        self.size -= 1


# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
☆☆☆题解2:双链表
class ListNode:
    def __init__(self, val=0, prev=None, next=None):
        self.val = val
        self.prev = prev
        self.next = next

class MyLinkedList:
    def __init__(self):
        # 要同时记住头和尾巴是哪个节点
        self.head = None
        self.tail = None
        self.size = 0

    def get(self, index: int) -> int:
        if index < 0 or index >= self.size:
            return -1
        # 判断离头近还是离尾近,节约耗时
        if index < self.size // 2:
            current = self.head
            for i in range(index):
                current = current.next
        else:
            current = self.tail
            for i in range(self.size - index - 1):
                current = current.prev
                
        return current.val

    def addAtHead(self, val: int) -> None:
        # 创建自身指针
        new_node = ListNode(val, None, self.head)
        if self.head:# 已经有头结点
            # 更新后节点的指针
            self.head.prev = new_node
        else:# 没有头结点,新加入的既是头也是尾
            self.tail = new_node
        # 更新头结点
        self.head = new_node
        self.size += 1

    def addAtTail(self, val: int) -> None:
        # 创建自身指针
        new_node = ListNode(val, self.tail, None)
        if self.tail:
            # 更新前节点的指针
            self.tail.next = new_node
        else:
            self.head = new_node
        # 更新尾巴结点
        self.tail = new_node
        self.size += 1

    def addAtIndex(self, index: int, val: int) -> None:
        if index < 0 or index > self.size:
            return
        
        if index == 0:
            self.addAtHead(val)
        elif index == self.size:
            self.addAtTail(val)
        else:
            if index < self.size // 2:
                current = self.head
                for i in range(index - 1):
                    current = current.next
            else:
                current = self.tail
                for i in range(self.size - index):
                    current = current.prev
            # 创建自身指针
            new_node = ListNode(val, current, current.next)
            # 更新前后节点的指针
            current.next.prev = new_node
            current.next = new_node
            self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        if index < 0 or index >= self.size:
            return
        
        if index == 0:
            # 删除当前节点
            self.head = self.head.next
            if self.head:
            # 后面还有其他节点,更新后节点指针
                self.head.prev = None
            else:
                self.tail = None
        elif index == self.size - 1:
            # 删除当前节点
            self.tail = self.tail.prev
            if self.tail:
            # 前面还有其他节点,更新前节点指针
                self.tail.next = None
            else:
                self.head = None
        else:
            if index < self.size // 2:
                current = self.head
                for i in range(index):
                    current = current.next
            else:
                current = self.tail
                for i in range(self.size - index - 1):
                    current = current.prev
            # 前后节点相连
            current.prev.next = current.next
            current.next.prev = current.prev
        self.size -= 1



# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)

206 反转链表

我的题解:双指针迭代

从前往后反转,head单独处理(看题解1吧,我这个写的好多此一举)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        current = head
        pre = None
        if head:
            while current.next:
                ne = current.next
                current.next = pre
                # 顺移pre和current
                pre = current
                current = ne
            current.next = pre
            return current
        else:
            return head
☆☆☆题解1:双指针
# Definition for singly-linked list.
# 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:
            temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur.next = pre #反转
            #更新pre、cur指针
            pre = cur
            cur = temp
        return pre
题解2:递归

从前往后翻,同双指针法

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        return self.reverse(head, None)
    def reverse(self, cur: ListNode, pre: ListNode) -> ListNode:
        if cur == None:
            return pre
        temp = cur.next
        cur.next = pre
        return self.reverse(temp, cur)

24 两两交换链表中的节点

我的题解:

注意:每次交换有三个指针需要改变,第一个节点的next、第二个节点的next、前面节点的next

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy_head = ListNode(next = head)
        current = head
        i = 0
        while current:
            if current.next:
                current = self.changeTwo(current, current.next)
                if i == 0:
                    # 更新头结点
                    dummy_head.next = current
                    i = 1
                else:
                    # 更新前节点的指针
                    pre.next = current
                pre = current.next
                current = current.next.next
            else:
                break
        return dummy_head.next

    def changeTwo(self, fir, sec):
        fir.next = sec.next
        sec.next = fir
        return sec
☆☆☆题解:更简洁

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        dummy_head = ListNode(next=head)
        current = dummy_head
        
        # 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
        while current.next and current.next.next:
            temp = current.next # 防止节点修改
            temp1 = current.next.next.next
            # crr指向2
            current.next = current.next.next
            # 2指向1
            current.next.next = temp
            # 1指向3
            temp.next = temp1
            # curr顺移
            current = current.next.next
        return dummy_head.next
题解2:递归!!!!
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head is None or head.next is None:
            return head

        # 待翻转的两个node分别是pre和cur
        pre = head
        cur = head.next
        next = head.next.next
        
        cur.next = pre  # 交换
        pre.next = self.swapPairs(next) # 将以next为head的后续链表两两交换
        # 返回的是交换后的位于前面的节点
        return cur

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

我的题解:笨方法

先遍历计算链表长度,再遍历找到目标节点

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        virtual_head = ListNode(next = head)
        current = head
        num = 0
        while current:
            num += 1
            current = current.next
        if n <= num:
            current = virtual_head
            for i in range(num - n):
                current = current.next
            current.next = current.next.next
            return virtual_head.next
☆☆☆题解:双指针

双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

注意:先让fast先走n步,再让slow出发,这样保证fast走到结尾时,slow刚好位于要删掉的n处,但删除操作需要用到前一个节点,因此slow应该再晚一步出发,即fast先走n+1步

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        # 创建一个虚拟节点,并将其下一个指针设置为链表的头部
        dummy_head = ListNode(0, head)
        
        # 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
        slow = fast = dummy_head
        
        # 快指针比慢指针快 n+1 步
        for i in range(n+1):
            fast = fast.next
        
        # 移动两个指针,直到快速指针到达链表的末尾
        while fast:
            slow = slow.next
            fast = fast.next
        
        # 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
        slow.next = slow.next.next
        
        return dummy_head.next

☆☆☆题解2:栈

在遍历链表的同时将所有节点依次入栈。根据栈「先进后出」的原则,我们弹出栈的第 nnn 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。这样一来,删除操作就变得十分方便了。

用list实现栈!!!

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        dummy = ListNode(0, head)
        stack = list()
        cur = dummy
        while cur:
            stack.append(cur)
            cur = cur.next
        
        for i in range(n):# 弹出n个元素
            stack.pop()

        prev = stack[-1]# 最后一个就是第n-1个元素
        prev.next = prev.next.next
        return dummy.next

面试题 02.07. 链表相交

我的题解:

分别统计两个链表长度,然后将长的推进,再同时向前比较节点是否相等,注意是比较节点,而不是节点的值和指针

(注释的是哈希表方法,43用例超时了)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        # hash_Table = dict()
        # currentA = headA
        # i = 0
        # while currentA:
        #     hash_Table[i] = currentA
        #     i += 1
        #     currentA = currentA.next
        # currentB = headB
        # while currentB:
        #     if currentB in hash_Table.values():
        #         return currentB
        #     currentB = currentB.next
        # return None
        nA, nB = 0, 0
        currentA = headA
        while currentA:
            nA += 1
            currentA = currentA.next
        currentB = headB
        while currentB:
            nB += 1
            currentB = currentB.next
        if nA >= nB:
            currentLang = headA
            currentShort = headB
            n = nA -nB
        else:
            currentLang = headB
            currentShort = headA
            n = nB -nA
        for i in range(n):
            currentLang = currentLang.next
        while currentShort:
            if currentLang == currentShort:
                return currentLang
            currentLang = currentLang.next
            currentShort = currentShort.next
        return None

☆☆☆题解:代码复用精简
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        dis = self.getLength(headA) - self.getLength(headB)
        
        # 通过移动较长的链表,使两链表长度相等
        if dis > 0:
            headA = self.moveForward(headA, dis)
        else:
            headB = self.moveForward(headB, abs(dis))
        
        # 将两个头向前移动,直到它们相交
        while headA and headB:
            if headA == headB:
                return headA
            headA = headA.next
            headB = headB.next
        
        return None
    
    def getLength(self, head: ListNode) -> int:
        length = 0
        while head:
            length += 1
            head = head.next
        return length
    
    def moveForward(self, head: ListNode, steps: int) -> ListNode:
        while steps > 0:
            head = head.next
            steps -= 1
        return head
题解2:等比例法,没懂
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None


class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        # 处理边缘情况——任一个是空链表
        if not headA or not headB:
            return None
        
        # 在每个链表的头部初始化两个指针
        pointerA = headA
        pointerB = headB
        
        # 遍历两个链表直到指针相交
        while pointerA != pointerB:
            # 将指针向前移动一个节点
            # ?????
            pointerA = pointerA.next if pointerA else headB
            pointerB = pointerB.next if pointerB else headA
        
        # 如果相交,指针将位于交点节点,如果没有交点,值为None
        return pointerA

142 环形链表ii

我的题解:字典哈希表

慢的要死

时间复杂度:O(N),其中 N 为链表中节点的数目。我们恰好需要访问链表中的每一个节点。

空间复杂度:O(N),其中 N 为链表中节点的数目。我们需要将链表中的每个节点都保存在哈希表当中。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        hash_Table = dict()
        current = head
        i = 0
        while current:
            if current in hash_Table.values():
                return current
            hash_Table[i] = current
            i += 1
            current = current.next
        return
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值