leetcode分类刷题总结

目录

      • 数组
        • 1 两数之和
        • 11 盛最多水的容器
        • 15 三数之和
        • 16 最接近的三数之和
        • 26 删除有序数组中的重复项
        • 27 移除元素
        • 31 下一个排列
        • 33 搜索旋转排序数组
        • 34 排序数组中查找元素的第一个和最后一个位置
        • 35 搜索插入位置
      • 字符串
        • 13 罗马数字转整数
        • 5 回文子串
        • 9 回文数
        • 6 Z字型变换
        • 8 字符串转换成整数
        • 16 最长公共前缀
        • 12 整数转罗马数字
        • 17 电话号码的字母组合
        • 20 有效的括号
        • *22 括号生成
      • 排序
        • 49 字母异位词分组
        • 56 合并区间
        • 75 颜色分类
        • 88 合并两个有序数组
        • @浅拷贝深拷贝
        • 147 对列表进行插入排序
        • 归并排序
        • 148 链表排序
        • 169 多数元素
        • 215 数组中的第k个最大元素
        • 217 存在重复的元素
        • 229 求众数
      • 深度优先搜索
        • 97 二叉树的中序遍历
        • 144 二叉树的前序遍历
        • 145 二叉树的后序遍历
        • 100 相同的树
        • @二叉树算法模板:BST
        • 101 对称二叉树
        • 104 二叉树的最大深度
        • 110 平衡二叉树
        • 111 二叉树的最小深度
        • 112 路径总和
        • 98 验证搜索二叉树
        • 99 恢复搜索二叉树
        • 113 路径总和II
        • 114 二叉树展开为链表
        • 116 填充每个节点的下一个右侧节点指针
      • 动态规划
        • 45 跳跃游戏II
      • 链表
        • 删除链表的倒数第n个节点

数组

1 两数之和

方法一:哈希表

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        dic = {}
        for i, num in enumerate(nums):
            if target - num in dic:
                return [dic[target-num], i]
            dic[num] = i   #关键是遍历一次就将其记录下来,避免重复遍历
        return
#时间复杂度O(N),空间复杂度O(N)
11 盛最多水的容器

方法一:双指针

移动短板,min(height[i], height[j]) 可能增大

移动长板,min(height[i], height[j])只可能减小或者不变

因此每次向内移动短板

class Solution:
    def maxArea(self, height: List[int]) -> int:
        i, j = 0, len(height)-1
        res = 0
        while i < j:
            res = max(min(height[i], height[j]) * (j - i), res)
            if height[i] < height[j]: i +=1
            else: j -= 1
        return res

#答案写法:
class Solution:
    def maxArea(self, height: List[int]) -> int:
        i, j, res = 0, len(height) - 1, 0
        while i < j:
            if height[i] < height[j]:
                res = max(res, height[i] * (j - i))
                i += 1
            else:
                res = max(res, height[j] * (j - i))
                j -= 1
        return res
15 三数之和

找出和为0的三个数

方法一:排序后用双指针

k用来遍历数组,双指针 i 和 j 分别指向k+1和len(nums) - 1,向中间遍历

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        res = []
        for k in range(len(nums)-2):
            if nums[k] > 0: break # 1. because of j > i > k.
            if k > 0 and nums[k] == nums[k-1]: continue # 2. skip the same `nums[k]`.
            i, j = k + 1, len(nums)-1
            while i < j: # 3. double pointer
                s = nums[k] + nums[i] + nums[j]
                if s < 0:
                    i += 1
                    while i < j and nums[i] == nums[i-1]: i +=1 #每次移动指针都要跳过相同的值
                elif s > 0:
                    j -= 1
                    while i < j and nums[j] == nums[j+1]: j -= 1
                else:
                    res.append([nums[k], nums[i], nums[j]])
                    i += 1
                    j -= 1
                    while i < j and nums[i] == nums[i-1]: i += 1
                    while i < j and nums[j] == nums[j+1]: j -= 1
        return res
16 最接近的三数之和
class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        nums.sort()
        best = float('inf')
        for k in range(len(nums)-2):
            if k > 0 and nums[k] == nums[k-1]: continue
            i, j = k + 1, len(nums) - 1
            while i < j:
                s = nums[k] + nums[i] + nums[j]
                if s == target:  
                    return s
                if abs(s-target) < abs(best-target):
                    best = s
                if s > target:  #与15题类似,同样是比较s 和 target,分三种情况
                    j -= 1
                    while i < j and nums[j] == nums[j+1]: j -= 1
                else:
                    i += 1
                    while i < j and nums[i] == nums[i-1]: i += 1
        return best
26 删除有序数组中的重复项

方法一:双指针。

快慢指针

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if len(nums) == 1: return len(nums)
        i = j = 1
        while j < len(nums):
            if nums[j] != nums[j-1]:
                nums[i] = nums[j]
                i += 1
            j += 1
        return i
27 移除元素

移除数组中的指定元素

方法一:类似26题快慢指针

#一次过!
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        slow = fast = 0
        while fast < len(nums):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        return slow
31 下一个排列

方法一:两次双指针

均从后面开始往前找

第一次找相邻的nums[i] < nums[j]

第二次从end到j找[j,end]中第一个大于nums[i]的nums[k]

class Solution:
    def nextPermutation(self, nums: List[int]) -> None:
        if len(nums) <= 1: return
        i, j, k = len(nums) - 2, len(nums) - 1, len(nums) - 1
        #找相邻的i, j, 使nums[i] < nums[j]
        while i >= 0 and nums[i] >= nums[j]:
            i -= 1
            j -= 1
        #从end到j找[j,end]中第一个大于nums[i]的nums[k]
        if i >= 0: #不是最后一个排列
            while nums[i] >= nums[k]:
                k -= 1
            nums[i], nums[k] = nums[k], nums[i]
        #reverse nums[j,end]
        i, j = j, len(nums) - 1
        while i < j:
            nums[i], nums[j] = nums[j], nums[i]
            i += 1
            j -= 1
        # nums[j:].reverse()
33 搜索旋转排序数组

方法一:二分查找

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if not nums:
            return -1
        l, r = 0, len(nums) - 1
        while l <= r:
            mid = (l + r) // 2
            if nums[mid] == target:
                return mid
            if nums[0] <= nums[mid]: #mid在左半边, [l, mid]是有序的,接下来还要确定target所在的位置(区间)
                if nums[0] <= target < nums[mid]:
                    r = mid - 1
                else: #target=nums[mid]
                    l = mid + 1
            else: #nums[mid] < nums[0],mid在右半边, [mid, r]是有序的
                if nums[mid] < target <= nums[len(nums) - 1]:
                    l = mid + 1
                else:
                    r = mid - 1
        return -1
34 排序数组中查找元素的第一个和最后一个位置

方法一:二分查找

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if not nums: return [-1, -1]
        i, j = 0, len(nums) -1
        while i <= j:  #找左边界
            m = (i+j) // 2
            if nums[m] >= target: #等于的时候还要继续减
                j = m - 1
            elif nums[m] < target:
                i = m + 1
        left = i

        i, j = 0, len(nums) -1
        while i <= j:  #找右边界
            m = (i+j) // 2
            if nums[m] > target:
                j = m - 1
            elif nums[m] <= target: 
                i = m + 1
        right = j

        return [-1, -1] if left == len(nums) or nums[left] != target else [left, right]
35 搜索插入位置
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        l, r = 0, len(nums) - 1
        while l <= r:
            m = (l + r) // 2
            if nums[m] == target:
                return m
            elif nums[m] > target:
                r = m - 1
            else:
                l = m + 1
        return l

字符串

13 罗马数字转整数

方法一:模拟

#不要以为难,认真想想就做出来了!
class Solution:
    def romanToInt(self, s: str) -> int:
        res = 0
        i = 0
        n = len(s)
        while i < n:
            if s[i] == 'I':
                res += 1
                if i < n-1 and s[i+1] == 'V':
                    res += 3
                    i += 1
                if i < n-1 and s[i+1] == 'X':
                    res += 8
                    i += 1
            elif s[i] == 'V':
                res +=5
            elif s[i] == 'X':
                res += 10
                if i < n-1 and s[i+1] == 'L':
                    res += 30
                    i += 1
                if i < n-1 and s[i+1] == 'C':
                    res += 80
                    i += 1
            elif s[i] == 'L':
                res += 50
            elif s[i] == 'C':
                res += 100
                if i < n-1 and s[i+1] == 'D':
                    res += 300
                    i += 1
                if i < n-1 and s[i+1] == 'M':
                    res += 800
                    i += 1
            elif s[i] == 'D':
                res += 500
            else:
                res += 1000
            i += 1
        return res

#改进写法:
class Solution:
    def romanToInt(self, s: str) -> int:
        value = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000}
        i = 0
        res = 0
        n = len(s)
        for i in range(n):
            if i < len(s)-1 and value[s[i]] < value[s[i+1]]:
                res -=value[s[i]]
            else:
                res += value[s[i]]
        return res
5 回文子串

方法一:动态规划

dp[i] [j] 根据dp[i+1] [j+1] 和s[i]、s[j]是否相同来判断

class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s) < 2:  return s
        n = len(s)
        max_len = 1
        begin = 0
        dp = [[False] * n for _ in range(n)] #n*n的矩阵
        #dp[i][j]表示s[i:j]是否为回文串
        for i in range(n):
            dp[i][i] = True #单个的为True

        #先枚举长度
        for L in range(2, n + 1):
            #枚举左边界,由边界由长度和左边界得到
            for i in range(n):
                j = L + i -1
                #如果j超出数组边界,终止本次循环
                if j > n-1: break
                if s[i] != s[j]:
                    dp[i][j] = False
                else: #即s[i] == s[j]
                    if L <= 3:  #如果长度小于等于3,则必是回文串
                        dp[i][j] = True
                    else:  #否则看s[i+1][j-1]是否为回文串
                        dp[i][j] = dp[i+1][j-1]
                if dp[i][j] and L > max_len:
                    max_len = L
                    begin = i
        return s[begin : begin + max_len]
9 回文数

判断是否为回文数,从左到右读和从右到左读是否一样

方法一:双指针

class Solution:
    def isPalindrome(self, x: int) -> bool:
        s = str(x)
        i, j = 0, len(s)-1
        while i < j:
            if s[i] != s[j]: return False
            i += 1
            j -= 1
        return True

方法二:反转一半数字

用反转的后一半来和前一半比较

class Solution:
    def isPalindrome(self, x: int) -> bool:
        if x < 0 or (x !=0 and x % 10 == 0):
            return False
        reverteNumber = 0
        while x > reverteNumber: #当原始数字小于或等于反转后的数字,意味着处理了一半位数的数字
            reverteNumber = reverteNumber * 10 + x % 10
            x //= 10
        return x == reverteNumber or x == (reverteNumber//10)
        
        #当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
        #例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,reverteNumber = 123,
        #由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
6 Z字型变换

一开始想的是,导出每个字符索引的表达式,方向就错了

但根本不需要找出图中每个字符的索引,只要想把每一行按什么顺序存放在数组中就好了

方法一:按行存放

class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows < 2: return s
        res = ['' for _ in range(numRows)]
        flag = True
        i = 0
        for c in s:
            res[i] += c
            i += 1 if flag else -1
            if i == 0 or i == numRows-1:
                flag = not flag
        return ''.join(res)
#答案写法:
class Solution:
    def convert(self, s: str, numRows: int) -> str:
        if numRows < 2: return s
        res = ["" for _ in range(numRows)]
        i, flag = 0, -1
        for c in s:
            res[i] += c
            if i == 0 or i == numRows - 1: flag = -flag
            i += flag
        return "".join(res)
8 字符串转换成整数
class Solution:
    def myAtoi(self, s: str) -> int:
        s = s.strip()
        if not s: return 0
        value = {
            '0':0,
            '1':1,
            '2':2,
            '3':3,
            '4':4,
            '5':5,
            '6':6,
            '7':7,
            '8':8,
            '9':9,
        }
        res = 0
        neg = False

        if s[0] == '-':
            neg = True
            s = s[1:]
        elif s[0] == '+':
            s = s[1:]
        for c in s:
            if c not in value: break
            if c == '0':
                res *= 10
            else:
                res = res* 10 + value[c]
        if neg:
            res = -res
            if res < -2**31:
                res = -2**31
        else:
            if res > 2**31 -1:
                res = 2**31 -1
        return res

# 写法改进:int()函数
class Solution:
    def myAtoi(self, s: str) -> int:
        s = s.strip()
        if not s: return 0
        res = 0
        sign = 1

        if s[0] == '-':
            sign = -1
            s = s[1:]
        elif s[0] == '+':
            s = s[1:]
        for c in s:
            if not '0' <= c <= '9': break
            if c == '0':
                res *= 10
            else:
                res = res* 10 + int(c)
        res = sign * res
        if res < 0:
            if res < -2**31:
                res = -2**31
        else:
            if res > 2**31 -1:
                res = 2**31 -1
        return res

方法二:状态机

读入一个字符看进入什么状态,如果是数字和符号的状态就进行相应的处理

【注意】init 写成int出错

INT_MAX = 2 ** 31 - 1
INT_MIN = -2 ** 31
class Automaton:
    def __init__(self):
        self.state = 'start'
        self.sign = 1
        self.ans = 0
        self.table = {
            'start': ['start', 'signed', 'in_number', 'end'],
            'signed': ['end', 'end', 'in_number', 'end'],
            'in_number': ['end', 'end', 'in_number', 'end'],
            'end': ['end', 'end', 'end', 'end'],
        }

    def next_state(self, c):
        if c.isspace():
            return 0
        if c == '+' or c == '-':
            return 1
        if c.isdigit():
            return 2
        return 3 #其他字符都进入end状态
    
    def get(self, c): #只需要处理数字和符号两个状态,如果进入end状态,接下来的状态只能是end
        self.state = self.table[self.state][self.next_state(c)]
        if self.state == 'in_number':  
            self.ans = self.ans * 10 + int(c)
            self.ans = min(self.ans, INT_MAX) if self.sign == 1 else min(self.ans, -INT_MIN)
        elif self.state == 'signed':
            self.sign = 1 if c == '+' else -1

class Solution:
    def myAtoi(self, s: str) -> int:
        automation = Automaton()
        for c in s:
            automation.get(c)
        return automation.ans * automation.sign
16 最长公共前缀

【注意】 sort() 没写括号没报错,出错

#方法一:拿第一个字符串的每个字符去与后面的比较,后面所有字符串都有则将其加入到res中
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        res = ''
        i = 0
        for i in range(len(strs[0])):
            for s in strs[1:]:
                if i >= len(s) or strs[0][i] != s[i]: return res
            res += strs[0][i]
        return res

#方法二:逐个比较单词,更新最长公共子串(不断剪短)
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        res = strs[0]
        for s in strs:
            while s.find(res) != 0: #返回0说明起始位置是0,
                res = res[0 : len(res) - 1] #res逐个减小,看是否满足
                if res == '': return '' #可选
        return res

#方法三:按字典排序数组,比较第一个和最后一个
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        strs.sort()
        s1 = strs[0]
        s2 = strs[-1]
        res = ''
        for i in range(len(s1)):
            if i >= len(s2) or s1[i] != s2[i]: break
            else:res += s1[i]
            #等价于
            # if i < len(s2) and s1[i] == s2[i]:
            #     res += s1[i]
            # else: break
        return res
12 整数转罗马数字

方法一:模拟

#关键是字典所有可能出现的字母都列出来了,
#而且300是CCC一个个加上去的,而不是3C,其他的400、500直接表示,认真想想就知道
class Solution:
    def intToRoman(self, num: int) -> str:
        value = {
            1000:'M',
            900: 'CM',
            500: 'D',
            400:'CD',
            100: 'C',
            90: 'XC',
            50: 'L',
            40: 'XL',
            10: 'X',
            9: 'IX',
            5: 'V',
            4: 'IV',
            1: 'I',
        }
        res = []
        for val, symbol in value.items():
            while num >= val:
                num -= val
                # res += symbol #字符串为不变对象,每次加都要新建,效率低
                res.append(symbol)
            if num == 0:
                break
        return ''.join(res)

方法二:硬编码

模运算和除法得到各位的数字

class Solution:

    THOUSANDS = ["", "M", "MM", "MMM"]
    HUNDREDS = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"]
    TENS = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"]
    ONES = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]

    def intToRoman(self, num: int) -> str:
        return Solution.THOUSANDS[num // 1000] + \
            Solution.HUNDREDS[num % 1000 // 100] + \
            Solution.TENS[num % 100 // 10] + \
            Solution.ONES[num % 10]
17 电话号码的字母组合

方法一:每多一个数字,都是在原来的结果上逐个元素地加上该数字对应的字母

#仔细想想就做出来了!
class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits: return []
        dic = {
            '2': 'abc',
            '3': 'def',
            '4': 'ghi',
            '5': 'jkl',
            '6': 'mno',
            '7': 'pqrs',
            '8': 'tuv',
            '9': 'wxyz',
        }
        res = [i for i in dic[digits[0]]] #比如['a', 'b', 'c']
        for digit in digits[1:]:
            tmp = []
            for s in res: #原来的结果有多少个元素,就要在该元素的基础上,逐个地加入当前数字对应的字母
                tmp += [s + i for i in dic[digit]]
            res = tmp
        return res

方法二:递归

类似于22括号生成

“当题目中出现 “所有组合” 等类似字眼时,我们第一感觉就要想到用回溯。”

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits: return []
        dic = {
            '2': 'abc',
            '3': 'def',
            '4': 'ghi',
            '5': 'jkl',
            '6': 'mno',
            '7': 'pqrs',
            '8': 'tuv',
            '9': 'wxyz',
        }
        def backtrack(conbination, digit):
            if len(digit) == 0: 
                res.append(conbination)
            else: 
                for letter in dic[digit[0]]: 
                    backtrack(conbination + letter, digit[1:])  #digit[1:]相当于不断地取下一个
        res = []
        backtrack('', digits)
        return res

方法三:队列

#方法三:队列
class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits: return []
        dic = {
            '2': 'abc',
            '3': 'def',
            '4': 'ghi',
            '5': 'jkl',
            '6': 'mno',
            '7': 'pqrs',
            '8': 'tuv',
            '9': 'wxyz',
        }
        queue = [''] #相当于方法一中的存储结果
        for digit in digits:
            for _ in range(len(queue)):
                tmp = queue.pop(0)
                for letter in dic[digit]:
                    queue.append(tmp + letter)  #每个结果都要加上当前数字的每个元素
        return queue
20 有效的括号

方法一:栈

思路与答案一致!

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        left = {'{': '}', '[': ']', '(': ')'}
        #遇到左括号则入栈,遇到右括号则查看是否和左括号的栈顶元素相同
        for c in s:
            if c in left:
                stack.append(c)
            else:
                if not stack or c != left[stack[-1]]:
                    return False
                else:
                    stack.pop()
        return True if not stack else False
*22 括号生成

方法一:递归

类似于电话号码的字母组合

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        res = []
        cur_str = ''
        def dfs(cur_str, left, right):
            if left == 0 and right == 0:
                res.append(cur_str)
                return
            if left > right: #说明先先放右括号,不满足
                return
            if left > 0:
                dfs(cur_str + '(', left - 1, right)
            if right > 0:
                dfs(cur_str + ')', left, right - 1)
        
        dfs('', n, n)
        return res

排序

49 字母异位词分组

方法一:排序+字典

sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。

list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list。

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        dic = {}
        res = []
        for s in strs:
            tmp = s
            # s = list(s)
            # s.sort()
            # s = ''.join(s)
            s = ''.join(sorted(s))
            if s in dic:
                dic[s].append(tmp)
            else:
                dic[s] = [tmp]
        for k, v in dic.items():
            res.append(v)
        return res
#写法二:
class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        dic = collections.defaultdict(list) #类型名称作为初始化函数参数,如果key不存在,就把[]赋给key
        for s in strs:
            k = ''.join(sorted(s))
            dic[k].append(s)
        return list(dic.values())
56 合并区间

方法一:排序

先按第一个元素排序,逐个处理区间,根据是否重合修改端点或者直接添加。不需要求出每个端点的索引

content.sort(key=lambda x:x[0])

x是参数入口,返回表达式x[0]这个结果

原理:在排序之前,content里的所有元素都会执行key的函数,这里指的就是lambda函数,计算出值之后,赋值给key。然后sort()是针对key进行排序,然后再根据这个key对应的值替换到排好序的content里。

例子:

strings = [‘foo’, ‘card’, ‘bar’, ‘aaaa’, ‘abab’]

根据字符串中不同字母的数量对一个字符串集合进行排序 strings.sort(key=lambda x: len(set(list(x))))

输出结果为:[‘aaaa’, ‘foo’, ‘abab’, ‘bar’, ‘card’]

list中的每个元素传入到 lambda x: len(set(list(x)))中计算出所含字母的数量,再根据这个数量进行排序。

#错解.大方向不是找每个区间端点的位置
class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        res = []
        i = 0
        left, right = intervals[0][0], intervals[0][1]
        while i < len(intervals):
            j = i + 1
            while j < len(intervals):
                if intervals[i][1] >= intervals[j][0]:
                    right = intervals[j][1]
                    i += 1
                    j += 1
                else:
                    res.append([left, right])
                    i += 1
                    j += 1
                    left, right = intervals[i][0], intervals[i][1]
        return res

#不要试图找到每个区间的左右端点的索引,正确存放到结果就好
class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key = lambda x: x[0])
        res = []
        for cur in intervals:
            if not res or res[-1][1] < cur[0]: #res为空,或者区间不重合,直接添加到结果
                res.append(cur)                #当前结果的最大值(右)都小于当前区间的最小值(左),不重合
            else:   #当结果中最后一个元素的右端点大于当前元素的左端点,则说明当前结果包含当前区间
                res[-1][1] = max(res[-1][1], cur[1])
        return res
75 颜色分类
#错解,写复杂了,不需要像快排那样交换
class Solution:
    def sortColors(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        i, j = 0, len(nums) - 1
        n0 = 0
        while i < j:
            while i < j and nums[i] == 0:
                i += 1
                n0 += 1
            while i < j and nums[j] != 0: j -= 1
            nums[i], nums[j] = nums[j], nums[i]
            n0 += 1
            i += 1
            j -= 1
        i = n0 - 1
        j = len(nums) - 1
        while i < j:
            while i < j and nums[i] == 1: i += 1
            while i < j and nums[j] != 1: j -= 1
            nums[i], nums[j] = nums[j], nums[i]
        return
#设置两个指针,遇到0则与前面的交换就好
class Solution:
    def sortColors(self, nums: List[int]) -> None:
        n = len(nums)
        p = 0
        for i in range(n):
            if nums[i] == 0:
                nums[p], nums[i] = nums[i], nums[p]
                p += 1   #i走在前面,遇到0则交换,p不会比i先遇到新的0
        for i in range(p, n):
            if nums[i] == 1:
                nums[p], nums[i] = nums[i], nums[p]
                p += 1
        return 

方法二:在增加一个指针,p0交换0,p1交换1

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        n = len(nums)
        p0 = p1 = 0
        for i in range(n):
            if nums[i] == 1:
                nums[i], nums[p1] = nums[p1], nums[i]
                p1 += 1
            elif nums[i] == 0:
                nums[i], nums[p0] = nums[p0], nums[i]
                if p0 < p1: #交换到后面的是nums[i]必然是1,所以要将将其换到p1
                    nums[i], nums[p1] = nums[p1], nums[i] 
                p0 += 1
                p1 += 1 #1相当于整体往后移动1格
88 合并两个有序数组

方法一:双指针

j指向要插入的数字,遇到第一个比其大的数则交换

有没充分利用排序数组的条件,nums2不是升序也可以

#一次过!
class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """
        nums1[m:] = nums2  #然后直接nums1.sort()也能通过
        j = m
        while j < m + n:
            for i in range(j):
                if nums1[i] > nums1[j]:
                    nums1[i], nums1[j] = nums1[j], nums1[i]
            j += 1

方法二:按一个个取出来比较,在加入到一个数组lst中,最后将其赋值给nums1

时间O(m + n)

空间复杂度较大 O(m + n)

@浅拷贝深拷贝

a = [[1, 2, 3], [4, 5, 6]]

b = a, 不是拷贝,只是将地址赋给b,改变一个,另一个也变

浅拷贝:目的是拷贝到另一块内存,但只复制了一层

b = a[ : ], 改变b的元素,a不变,如a[0] = 1 ,b不会改变;但改变第二层则b发生改变,如a[0].append(7)

b = a.copy()

b = [i for i in a]

深拷贝:深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝之后,两者互不影响。

b = copy.deepcopy(a)

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        lst = []
        p1 = p2 = 0
        while p1 < m or p2 < n:
            if p1 == m:
                lst[p1+p2:] = nums2[p2:]
                break
            if p2 == n:
                lst[p1+p2:] = nums1[p1:m]
                break
            if nums1[p1] <= nums2[p2]:
                lst.append(nums1[p1])
                p1 += 1
            else:
                lst.append(nums2[p2])
                p2 += 1
        nums1[:] = lst[:]
        #nums1 = lst 得不到正确的结果,因为把lst的地址赋给了nums1,但在测试的时候肯定还是用nums1原来的地址

方法三:逆向双指针

从后面开始取大的一个放到nums1的后半部分,节省空间开销

空间复杂度降为O(1)

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        p1 = m - 1
        p2 = n - 1
        j = m + n - 1
        while p1 >= 0 or p2 >= 0:
            if p1 < 0:
                nums1[j] = nums2[p2]
                p2 -= 1
            elif p2 < 0:
                nums1[j] = nums1[p1]
                p1 -= 1
            elif nums1[p1] >= nums2[p2]:
                nums1[j] = nums1[p1]
                p1 -= 1
            else:
                nums1[j] = nums2[p2]
                p2 -= 1
            j -= 1
147 对列表进行插入排序
#错解,大方向没错。
#pointer.val >= cur.val: #找到第一个比cur.val大的不好插入;
# 应该找到比cur.val大的前一个,用pre.next.val <= curr.val:来判断是否找到,用pre来从头遍历
#同时用lastSorted来标记已排序链表的最后一个元素,相当于cur_pre,cur的前一个
class Solution:
    def insertionSortList(self, head: ListNode) -> ListNode:
        res = pointerx_pre = ListNode(0, head)
        pointer = cur_pre = head
        cur = head.next
        while cur:
            cur_pre.next = cur.next
            if pointer.val>= cur.val:  #左边是有序的,找到第一个比cur.val大的即可  
                cur.next = pointer
                pointerx_pre.next = cur
                pointerx_pre = res
                pointer = res.next
            else:
                if pointer != cur_pre.next:
                    pointer = pointer.next
                    pointerx_pre = pointerx_pre.next
            cur = cur.next
            cur_pre = cur_pre.next
        return res

class Solution:
    def insertionSortList(self, head: ListNode) -> ListNode:
        if not head:return head
        res = ListNode(0, head)
        lastSorted = head
        cur = head.next
        while cur:
            if lastSorted.val <= cur.val:
                lastSorted = lastSorted.next 
            else:
                pre = res #要插入位置的前一个节点
                while pre.next.val <= cur.val: #在lasSorted及其前面必然有一个大于cur.val,因为lastSorted.val > cur.val
                    pre = pre.next
                lastSorted.next = cur.next #因为lastSorted.val > cur.val,所以lastsorted的位置不用变,只需指向下一个删除cur
                cur.next = pre.next
                pre.next = cur
            cur = lastSorted.next
        return res.next
归并排序
148 链表排序

方法一:归并排序

不断地从中间分开,分到最后只剩一个,一个元素的链表一定是有序的,再按两个有序链表的方法合并
用快慢指针的方法将链表分为两部分,一个走一步,另一个走两步

#插入排序,O(n^2),超时
class Solution:
    def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head: return head
        res = ListNode(0, head)
        lastSorted = head
        cur = head.next
        while cur:
            if lastSorted.val <= cur.val:
                lastSorted = lastSorted.next
            else:
                pre = res
                while pre.next.val <= cur.val:
                    pre = pre.next
                lastSorted.next = cur.next
                cur.next = pre.next
                pre.next = cur
            cur = lastSorted.next  #注意不要惯性地cur = cur.next
        return res.next

#归并排序
class Solution:
    def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next: return head
        slow, fast = head, head.next
        while fast and fast.next: #画图就知道,节点为偶数的时候,最后走到fast.next为空,奇数的时候最后fast为空
            slow = slow.next
            fast = fast.next.next
        mid = slow.next
        slow.next = None  #从中间断开
        left = self.sortList(head)
        right = self.sortList(mid)

        cur = dummy = ListNode(0)
        while left and right:
            if left.val <= right.val:
                cur.next = left
                left = left.next
            else:
                cur.next = right
                right = right.next
            cur = cur.next
        cur.next = left if left else right
        return dummy.next
#归并排序,写法二
class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        def sortFunc(head: ListNode, tail: ListNode) -> ListNode:
            if not head:
                return head
            if head.next == tail:  #相当于只有一个节点的时候,这里tail初始化为None
                head.next = None
                return head
            slow = fast = head  
            while fast != tail: 
                slow = slow.next
                fast = fast.next
                if fast != tail:
                    fast = fast.next
            mid = slow
            return merge(sortFunc(head, mid), sortFunc(mid, tail))
            
        def merge(head1: ListNode, head2: ListNode) -> ListNode:
            dummyHead = ListNode(0)
            temp, temp1, temp2 = dummyHead, head1, head2
            while temp1 and temp2:
                if temp1.val <= temp2.val:
                    temp.next = temp1
                    temp1 = temp1.next
                else:
                    temp.next = temp2
                    temp2 = temp2.next
                temp = temp.next
            if temp1:
                temp.next = temp1
            elif temp2:
                temp.next = temp2
            return dummyHead.next
        
        return sortFunc(head, None)
169 多数元素

方法一:排序

时间O(n logn)还不如哈希表只遍历一次

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        nums.sort()
        return nums[len(nums)//2]

方法二:哈希表

时间:O(n)

空间:O(n)

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        dic = {}
        for num in nums:
            if num in dic:
                dic[num] += 1
            else:
                dic[num] = 1
        return max(dic.keys(), key = dic.get)
        #dic.keys()以列表返回字典的所有键
        #max(testlist, key),对于每个testlist的元素,先按key制定的函数处理,然后再比较,返回testlist原来的元素
        #dic,get(key),获取键的值

方法三:摩尔投票

多数元素与非多数元素两两抵消,至少还会剩余一个多数元素

时间:O(n)

空间:O(1)

#摩尔投票
class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        count = 0
        for num in nums:
            if count == 0:
                candidate = num
            count += 1 if num == candidate else -1
        return candidate
215 数组中的第k个最大元素

方法一:最大堆

只需考虑一个元素,而不像返回前k个最小元素那样得到一整个排序数组

用最大堆即可

将num压入堆的时候,会维持顺序,-maxHeap[0]一直是最大的,heappop(maxHeap)出来的也是最大的

最大堆的实现:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/solution/cpython3java-1da-gen-dui-diao-ku-2shou-l-xveq/

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        maxHeap = []
        for num in nums:
            heapq.heappush(maxHeap, -num) #取反是大根堆,否则小跟堆
        for _ in range(k - 1):
            heapq.heappop(maxHeap)
        return -maxHeap[0]

方法二:快速排序

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        def quickSort(nums, l, r):
            if l >= r: return 
            i, j = l, r
            while i < j:
                while i < j and nums[j] <= nums[l]: j -= 1
                while i < j and nums[i] >= nums[l]: i += 1
                nums[i], nums[j] = nums[j], nums[i]
            nums[l], nums[i] = nums[i], nums[l]
            quickSort(nums, l, i - 1)
            quickSort(nums, i + 1, r)
        
        quickSort(nums, 0, len(nums)-1)
        return nums[k-1]

方法三:快速选择(快排的改进)

降序排序

如果哨兵所在的索引 idx 正好是k-1,那么哨兵最是要找的数(因为他的左边都比他小)

如果 idx < k -1,则说明目标还不够小,应该在哨兵的右边,只需要对右边进行排序

否则对左边排序

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
                l = 0
        r = len(nums) - 1
        while True:
            idx = quickSort(nums, l, r)
            if idx == k - 1:
                return nums[idx]
            elif idx < k - 1:
                l = idx + 1
            else:
                r = idx - 1  
                
        def quickSort(nums, l, r):
            i, j = l, r
            while i < j:
                while i < j and nums[j] <= nums[l]: j -= 1
                while i < j and nums[i] >= nums[l]: i += 1
                nums[i], nums[j] = nums[j], nums[i]
            nums[l], nums[i] = nums[i], nums[l]
            return i
217 存在重复的元素

判断是否存在重复的元素

方法一:哈希表

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        dic = {}
        for num in nums:
            if num not in dic:
                dic[num] = 1
            else:
                return True
        return False

方法二:排序

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        nums.sort()
        for i in range(len(nums)-1):
            if nums[i] == nums[i+1]:
                return True
        return False

方法三:利用set()

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        return len(set(nums)) != len(nums)
229 求众数

方法一:哈希表

class Solution:
    def majorityElement(self, nums: List[int]) -> List[int]:
        dic = {}
        n = len(nums) // 3
        res = []
        for num in nums:
            if num in dic:
                dic[num] += 1
            else:
                dic[num] = 1
        for k, v in dic.items():
            if v > n: res.append(k)
        return res

方法二:摩尔投票

注意到最多有两种元素的数量超过n // 3

三个不同的抵消

剩下的才可能是超过n//3的

但不一定,因为没法保证剩下的一定大于n/3,比如有n-1个a和1个b,都会留下来,但是b只有一个

class Solution:
    def majorityElement(self, nums: List[int]) -> List[int]:
        element1 = element2 = 0
        vote1 = vote2 = 0
        for num in nums:
            if vote1 > 0 and num == element1:  #注意先判断完vote > 0 的情况,否则[2,2]这种样例会将得到结果错误结果[2,2]
                vote1 += 1
            elif vote2 > 0 and num == element2:
                vote2 += 1
            elif vote1 == 0:
                element1 = num
                vote1 += 1
            elif vote2 == 0:
                element2 = num
                vote2 += 1

            else: #三者都不同,抵消
                vote1 -= 1
                vote2 -= 1

        res = []
        count1 = count2 = 0
        #检查剩下的元素是否满足大于n//3的条件
        for num in nums:
            if vote1 > 0 and num == element1:
                count1 += 1
            if vote2 > 0 and num == element2:
                count2 += 1
        if vote1 > 0 and count1 > len(nums)//3:
            res.append(element1)
        if vote2 > 0 and count2 > len(nums)//3:
            res.append(element2)
        return res

深度优先搜索

97 二叉树的中序遍历

方法一:递归

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def dfs(root):
            if not root: return 
            dfs(root.left)
            res.append(root.val)
            dfs(root.right)
        dfs(root)
        return res

方法二:利用栈,迭代实现

这题的真正难点在于如何用非递归的方式实现。
递归实现时,是函数自己调用自己,一层层的嵌套下去,操作系统/虚拟机自动帮我们用 栈 来保存了每个调用的函数,现在我们需要自己模拟这样的调用过程。
递归的调用过程是这样的:

dfs(root.left)
	dfs(root.left)
		dfs(root.left)
			为null返回
		打印节点
		dfs(root.right)
			dfs(root.left)
				dfs(root.left)
				........

递归的调用过程是不断往左边走,当左边走不下去了,就打印节点,并转向右边,然后右边继续这个过程。
我们在迭代实现时,就可以用栈来模拟上面的调用过程。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        stack = []
        while stack or root: #小心别写成and!
            if root:
                stack.append(root) 
                root = root.left  #不断往左子树方向走,如果左子树不为空,每走一次就将当前节点保存到栈中
            else:
                tmp = stack.pop() #如果左子树为空,则弹出当前节点,访问,并处理右节点
                res.append(tmp.val)
                root = tmp.right
        return res
144 二叉树的前序遍历

方法一:递归

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def dfs(root):
            if not root:
                return
            res.append(root.val)
            dfs(root.left)
            dfs(root.right)
        dfs(root)
        return res

方法二:迭代

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        stack = []
        while stack or root:
            if root:
                res.append(root.val)
                stack.append(root) #用栈来维护遍历过的节点一遍回溯
                root = root.left
            else:
                tmp = stack.pop()
                root = tmp.right
        return res
145 二叉树的后序遍历

方法一:递归

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def dfs(root):
            if not root: return 
            dfs(root.left)
            dfs(root.right)
            res.append(root.val)
        dfs(root)
        return res

方法二:递归

用前序遍历的模板,先访问根节点

访问顺序为:根,右,左

但每次都插入队头,结果变为

左, 右, 根

#方法二:迭代
class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        stack = []
        while stack or root:
            if root:
                res.insert(0, root.val)
                stack.append(root)
                root = root.right
            else:
                tmp =stack.pop()
                root = tmp.left
        return res
100 相同的树

方法一:深度优先搜索

#错解
#如果两个二叉树都为空,则两个二叉树相同!应直接返True
#而且这是个遍历问题,不涉及到改,在遍历过程中 判断即即可
class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        if not p and not q:
            return
        if (not p or not q) and p.val != q.val:
            return False
        self.isSameTree(p.left, q.left)
        self.isSameTree(p.right, q.right)
        return True

class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        if not p and not q: #到空了还没有False的就返回True
            return True
        elif not p or not q: #在不是两个都为空的条件下,那么至少有一个不为空。如果有一个为空则就是一个为空另一个不为空,那么一定不相同
            return False
        elif p.val != q.val:
            return False
        else:
            return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
@二叉树算法模板:BST

https://leetcode-cn.com/problems/same-tree/solution/xie-shu-suan-fa-de-tao-lu-kuang-jia-by-wei-lai-bu-/

一、在 BST 中查找一个数是否存在

根据我们的指导思想,可以这样写代码:

boolean isInBST(TreeNode root, int target) {
    if (root == null) return false;
    if (root.val == target) return true;

    return isInBST(root.left, target)
        || isInBST(root.right, target);
}

这样写完全正确,充分证明了你的框架性思维已经养成。现在你可以考虑一点细节问题了:如何充分利用信息,把 BST 这个“左小右大”的特性用上?

很简单,其实不需要递归地搜索两边,类似二分查找思想,根据 target 和 root.val 的大小比较,就能排除一边。我们把上面的思路稍稍改动:

boolean isInBST(TreeNode root, int target) {
    if (root == null) return false;
    if (root.val == target)
        return true;
    if (root.val < target) 
        return isInBST(root.right, target);
    if (root.val > target)
        return isInBST(root.left, target);
    // root 该做的事做完了,顺带把框架也完成了,妙
}

于是,我们对原始框架进行改造,抽象出一套针对 BST 的遍历框架:

void BST(TreeNode root, int target) {
    if (root.val == target)
        // 找到目标,做点什么
    if (root.val < target) 
        BST(root.right, target);
    if (root.val > target)
        BST(root.left, target);
}

二、在 BST 中插入一个数

对数据结构的操作无非遍历 + 访问,遍历就是“找”,访问就是“改”。具体到这个问题,插入一个数,就是先找到插入位置,然后进行插入操作。

上一个问题,我们总结了 BST 中的遍历框架,就是“找”的问题。直接套框架,加上“改”的操作即可。一旦涉及“改”,函数就要返回 TreeNode 类型,并且对递归调用的返回值进行接收。

TreeNode insertIntoBST(TreeNode root, int val) {
    // 找到空位置插入新节点
    if (root == null) return new TreeNode(val);
    // if (root.val == val)
    //     BST 中一般不会插入已存在元素
    if (root.val < val) 
        root.right = insertIntoBST(root.right, val);v //这里比检查一个数是否存在,多了等号
    if (root.val > val) 
        root.left = insertIntoBST(root.left, val);
    return root;
}
101 对称二叉树

判断一颗二叉树是否对称

方法一:递归

没有相等就返回Ture这样的操作,而是不等就返回False,都为空在返回Ture

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        def recur(l, r):
            if not l and not r: #都为空,相当于到了根节点,返回Ture
                return True
            elif not l or not r: #排除了都为空,一个为空一个不为空,必然不对称
                return False
            elif l.val != r.val: #值不等也不对称
                return False
            else:
                return recur(l.left, r.right) and recur(l.right, r.left)
        
        if not root: return True
        return recur(root.left, root.right)
104 二叉树的最大深度

方法一:递归

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root: return 0
        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))

方法二:广度优先搜索

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root: return 0
        stack = [root]
        res = 0
        while stack:
            res += 1
            for _ in range(len(stack)):
                node = stack.pop(0)  #记得出队的是队首
                if node.left: stack.append(node.left)
                if node.right: stack.append(node.right)
        return res
110 平衡二叉树

判断一个二叉树是否平衡

方法一:暴力遍历每个节点,判断是否满足条件

对于判断这个主函数来说是自顶向下的

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if not root: return True
        # return abs(self.maxDepth(root.left) -self.maxDepth(root.right)) <= 1 #还要判断每个节点是不是平衡的
        return abs(self.maxDepth(root.left) -self.maxDepth(root.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)

    def maxDepth(self, root):
        if not root: return 0
        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))

方法二:不用每次都计算节点的高度,一遇到不满足的则返回-1标记即可。剪枝

自底向上,在回溯的过程就能判断,不满足条件直接返回-1,不必再计算高度

同样也是计算高度的过程

#方法二:
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        def height(root):
            if not root: return 0
            leftHeight = height(root.left)
            rightHeight = height(root.right)
            if leftHeight == -1 or rightHeight == -1 or abs(leftHeight - rightHeight) > 1:
                return -1
            else:
                return 1 + max(leftHeight, rightHeight) #同样也是计算高度的过程,不满足才会别记为-1
        return height(root) >= 0 #如果满足平衡二叉树则最后返回的是高度,否则-1
111 二叉树的最小深度

方法一:深度优先搜索

关键是判断什么条件下该返回什么

#错解:
#例如是只有连续5个右子节点的情况,应该返回6,但这里会返回一。因为叶子节点的定义是左右子节点都为空
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root: return 0
        return 1 + min(self.minDepth(root.left), self.minDepth(root.right))

class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root: #空时返回零
            return 0
        elif not root.left and not root.right: #左右节点都为空返回 1
            return 1
        elif root.left and root.right: #左右节点都不为空,返回小的那个深度
            return min(self.minDepth(root.left), self.minDepth(root.right)) + 1
        else:
            return self.minDepth(root.left) + 1 if root.left else self.minDepth(root.right) + 1 #有一个为空,需要返回非空的那个深度才满足叶子节点的条件。因为一个子节点为空不是叶子节点,两个为空才是。

方法二:广度优先搜索

112 路径总和

方法一:DFS

#错解
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return
        targetSum -= root.val
        if not root.left and not root.right and targetSum == 0:
            return True
        self.hasPathSum(root.left, targetSum)
        self.hasPathSum(root.right, targetSum)
        return False
    
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False #因为下面用or连接左右节点,如果其中一个节点是空、False,还有另一个节点继续下去。如果两个节点都空就是下面的条件了
        targetSum -= root.val
        if not root.left and not root.right and targetSum == 0:
            return True
        return self.hasPathSum(root.left, targetSum) or self.hasPathSum(root.right, targetSum)
        #一开始喂入的targetsum是多少就是多少,不会因为后面有调用函数而发生改变,因为返回到现场,参数还是用的喂进去的参数。
        #赋值给了参数列表,就不会跟着targetSum的变化而变化

#写法二:
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
        if not root.left and not root.right:
            return targetSum == root.val
        return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)

方法二:回溯,遍历保存路径,遇到路径和与目标相同的就返回True

class Solution(object):
    def hasPathSum(self, root, sum):
        if not root: return False
        return self.dfs(root, sum, [root.val])
        
    def dfs(self, root, target, path):
        if not root: return False
        if sum(path) == target and not root.left and not root.right:
            return True
        left_flag, right_flag = False, False
        if root.left:
            left_flag = self.dfs(root.left, target, path + [root.left.val])
        if root.right:
            right_flag = self.dfs(root.right, target, path + [root.right.val])
        return left_flag or right_flag

方法三:BFS

方法四:栈

98 验证搜索二叉树

方法一:递归

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        def recur(root, lower, upper):
            if not root:
                return True  #都到空了还没有返回False的就返回Ture
            if root.val <= lower or root.val >= upper:
                return False  #关键是找到什么时候不等
            return recur(root.left, lower, root.val) and recur(root.right, root.val, upper)
        
        if not root: return 
        return recur(root, float('-inf'), float('+inf'))

方法二:中序遍历,递归

中序遍历的的结果应该是递增的,即当前节点的值应该大于前一个节点,否则返回False

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        self.pre = float('-inf')
        def recur(root):
            if not root: 
                return True
            l = recur(root.left)  #中序遍历,会一直进入到左子树的尽头,才会进行下面的访问操作
            if root.val <= self.pre:
                return False
            self.pre = root.val
            r = recur(root.right)
            return l and r

        return recur(root)

方法三:中序遍历,迭代

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        stack = []
        pre = float('-inf')
        while stack or root:  
            if root:
                stack.append(root)
                root = root.left
            else:
                tmp = stack.pop()
                if tmp.val <= pre:
                    return False
                pre = tmp.val
                root = tmp.right
        return True

中序遍历迭代法的另一种写法

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        stack, inorder = [], float('-inf')
        
        while stack or root:
            while root:
                stack.append(root)
                root = root.left
            root = stack.pop()
            # 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
            if root.val <= inorder:
                return False
            inorder = root.val
            root = root.right

        return True
99 恢复搜索二叉树

恢复有两个节点位置对调了的搜索二叉树

方法一:中序遍历

将中序遍历的节点保存到列表中,在遍历列表找出位置错误的节点,改变他们的值

class Solution:
    def recoverTree(self, root: Optional[TreeNode]) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        lst = []
        def dfs(root):
            if not root:
                return 
            dfs(root.left)
            lst.append(root)
            dfs(root.right)
        dfs(root)
        x = None
        y = None
        for i in range(len(lst)-1):
            if lst[i].val > lst[i+1].val:
                if not x: #第一次遇到非升序才才将i给x
                    x = lst[i]
                y = lst[i+1] 
                
        if x and y:
            x.val, y.val = y.val, x.val

方法二:中序遍历

在遍历的过程中就找到错误的位置,并记录到x和y。同时要维护一个pre来记录前一个节点

class Solution:
    def recoverTree(self, root: Optional[TreeNode]) -> None:
        self.x = None
        self.y = None
        self.pre = None
        def dfs(root):
            if not root:
                return
            dfs(root.left)
            if not self.pre:
                self.pre = root #用最左边那个初始化pre
            else:
                if self.pre.val > root.val:
                    if not self.x:
                        self.x = self.pre
                    self.y = root
                self.pre = root
            dfs(root.right)
        dfs(root)
        if self.x and self.y:
            self.x.val, self.y.val = self.y.val, self.x.val
113 路径总和II

方法一:递归

class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        self.path = []
        self.res = []
        def dfs(root, targetSum):
            if not root:
                return
            self.path.append(root.val)
            if not root.left and not root.right:
                if root.val == targetSum:
                    self.res.append(self.path[:])  #这里要用切片拷贝
            dfs(root.left, targetSum - root.val)
            dfs(root.right, targetSum - root.val)
            self.path.pop()  
        dfs(root, targetSum)
        return self.res
114 二叉树展开为链表

方法一:先序遍历然后逐个修改指针

#错解,试图用模板解决,想得太复杂了。还不如先暴力得到先序遍历的列表再修改指针
class Solution:
    def flatten(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        self.pre = None
        def dfs(root):
            if not root:
                return
            if not self.pre:
                self.pre = root
            else:
                self.pre.left = None
                self.pre.right = root
                self.pre = root
            dfs(root.left)
            dfs(root.right)
        dfs(root)
#先序遍历,递归
class Solution:
    def flatten(self, root: TreeNode) -> None:
        stack = []
        def dfs(root):
            if not root:
                return
            stack.append(root)
            dfs(root.left)
            dfs(root.right)
        dfs(root)
        for i in range(len(stack)-1):
            stack[i].left = None
            stack[i].right = stack[i+1]

方法二:遍历,根据题目特点灵活地改变指针指向

空间复杂度降为O(1)

class Solution:
    def flatten(self, root: TreeNode) -> None:
        while root:
            if not root.left:  #左子树为空,直接考虑下一个节点
                root = root.right
            else:
                pre = root.left
                while pre.right:
                    pre = pre.right
                pre.right = root.right  #左子树的最右节点的下一个一定是接当前的右子树
                root.right = root.left
                root.left = None
                root = root.right
116 填充每个节点的下一个右侧节点指针

方法一:层序遍历

维持一个pre指针指向前一个节点

#myCode
class Solution:
    def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
        if not root: return
        stack = [root]
        while stack:
            pre = None
            for _ in range(len(stack)):
                node = stack.pop(0)
                if not pre:
                    pre= node
                else:
                    pre.next = node
                    pre = node
                if node.left: stack.append(node.left)
                if node.right: stack.append(node.right)
            pre.next = None
        return root

方法二:递归

https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/solution/dong-hua-yan-shi-san-chong-shi-xian-116-tian-chong/

class Solution(object):
	def connect(self, root):
		"""
		:type root: Node
		:rtype: Node
		"""
		def dfs(root):
			if not root:
				return
			left = root.left
			right = root.right
			# 配合动画演示理解这段,以root为起点,将整个纵深这段串联起来
			while left:
				left.next = right
				left = left.right
				right = right.left
			# 递归的调用左右节点,完成同样的纵深串联
			dfs(root.left)
			dfs(root.right)
		dfs(root)
		return root

动态规划

45 跳跃游戏II

方法一:贪心

maxPos保存这一次所有可能的起跳点能到达的最远位置

从当前到边界,是所有可能的起跳点

而边界是上一次跳得最远的位置

class Solution:
    def jump(self, nums: List[int]) -> int:
        res = 0
        end = 0
        maxPos = 0
        for i in range(len(nums) - 1):
            maxPos = max(i + nums[i], maxPos) # maxPos保存当前这一次跳远能到达的最远的位置
            if i == end: #遍历完上一次跳跃的最远位置,就知道这一次能到达的最远位置
                end = maxPos 
                res += 1
        return res

链表

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

方法一:双指针

#错解
应该增加一个dummy节点,遍历到目标的前一个才好删除
class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        if not head or not head.next: return None
        slow = fast = head 
        for _ in range(n):
            fast = fast.next
        while fast:
            slow = slow.next
            fast = fast.next
        slow.next = slow.next.next
        return head

#双指针,增加一个dummy结点!!!
class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        slow = dummy = ListNode(0, head)
        fast = head
        for _ in range(n):
            fast = fast.next
        while fast:
            fast = fast.next
            slow = slow.next
        slow.next = slow.next.next
        return dummy.next

方法二:先遍历一遍得到长度k

再遍历k-n次

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        node = head
        k = 0
        while node:
            node = node.next
            k += 1
        dummy = cur= ListNode(0, head)  #还是要有dummy才好删除,特别是第一个
        for _ in range(k-n):
            cur = cur.next
        cur.next = cur.next.next
        return dummy.next

你可能感兴趣的:(代码总结,python,算法,数据结构,leetcode)