Leetcode简单题总结

简单题

  • 双指针
    • 26. 删除排序数组中的重复项
    • 27. 移除元素
    • 28. 实现 strStr()
    • 38. 外观数列
    • 58. 最后一个单词的长度
    • 89. 合并两个有序数组
    • 125. 验证回文串
    • 141. 环形链表
    • 228. 汇总区间
  • 查找及插入问题
    • 二分查找及其变种
      • 35. 搜索插入位置
      • 69. x的平方根
      • 704. 二分查找
      • 852. 山脉数组的峰顶索引
  • 队列、栈
    • 155. 最小栈
  • 数组
    • 66. 加一
    • 67. 二进制求和
    • 189. 旋转数组 middle
  • 链表
    • 83. 删除排序链表中的重复元素(hash)
    • 141. 环形链表
    • 160. 相交链表
    • 100. 相同的树(递归)
    • 101. 对称二叉树
    • 104. 二叉树的最大深度
    • 107. 二叉树的层序遍历 ||
    • 108. 将有序数组转换为二叉搜索树(递归)
    • 110. 平衡二叉树
    • 111. 二叉树的最小深度
    • 112. 路径总和
  • 动态规划
    • 53. 最大子序和
    • 70. 爬楼梯
    • 121. 买卖股票的最佳时机 |
    • 122. 买卖股票的最佳时机 ||
  • 位运算
    • 67. 二进制求和
    • 137. 只出现一次的数字(1) middle
    • 187. 重复的DNA序列 middle
    • 191. 位1的个数
    • 260. 只出现一次的数字(2) middle
    • 318. 最大单词长度乘积 middle
    • 421. 数组中两个数的最大异或值 middle
  • 数学、矩阵
    • 50. pow(x,n) middle
    • 70. 爬楼梯
    • 118. 杨辉三角 |
    • 119. 杨辉三角 ||
    • 1232. 缀点成线
  • 图论
    • 遍历:深度优先遍历、广度优先遍历、并查集
      • 399. 除法求值 middle
      • 547. 省份数量
      • 684. 冗余连接 middle
      • 947. 移除最多的同行或同列石头
      • 1202. 交换字符串的元素 middle

双指针

双指针:

  1. 快慢指针
  2. 左右指针(二分查找是左右指针的一种)

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

27. 移除元素

# 双指针法
# 方法一:直接快慢指针,对于快指针搜索不为val的元素,
# 依次前移填补满指针的位置,[4,1,2,3,5],当val为4时,移动次数较多
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        temp = 0 
        length = len(nums)
        for i in range(length):
            if nums[i]!=val:
                nums[temp] = nums[i]
                temp = temp + 1
        return temp

# 方法二:当需删除元素较少时当遇到val时,与队尾元素交换,
# 并减少元素数组长度,可以减少移动次数,并符合“元素顺序可以改变的要求”
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        temp = 0 
        length = len(nums)
        while(temp<length):
            if nums[temp]==val:
                nums[temp] = nums[length-1]
                length = length-1
            else:
                temp = temp + 1
        return temp

28. 实现 strStr()

主要方法有:

  1. 子串匹配:线性时间复杂度
  2. 双指针:线性时间复杂度
  3. 查找算法:BM、KMP、Sunday

对于方法1、方法2都有多余的操作,KMP则KMP 算法永不回退 txt 的指针 i,不走回头路(不会重复扫描 haystack),而是借助 dp 数组中储存的信息把 needle 移到正确的位置继续匹配,时间复杂度只需 O(N),用空间换时间。KMP原理上类似于有穷状态转换机,主要操作包括,根据needle建立有穷状态转换机(用状态0-1-2-3-4表示needle中0、1、2、3、4对应的字符),根据有穷状态机在haystack中搜索。
Leetcode简单题总结_第1张图片
Leetcode简单题总结_第2张图片

# 双指针 从初始位置i开始,匹配haystack与needle,指针前移
# 如果匹配失败,haystack指针回到i+1,needle指针回到0
# 考虑needle为空字符串,返回0
class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        if needle=='':
            return 0
        n_index = 0  # needle指针 
        i = 0  # haystack指针
        h_length = len(haystack)
        n_length = len(needle)
        while(i < h_length):
            if haystack[i]==needle[n_index]:
                n_index = n_index + 1
                i = i + 1
                if n_index == n_length:
                    return i-n_index
            else:
                i = i - n_index + 1  # 回溯的位置很关键
                n_index = 0
        return -1

# mississippi
# issip
# i = 5  n_index = 4  i = 2

38. 外观数列

底子依旧是双指针问题,只需要写出在每个循环中求解问题的子函数,就可以通过循环调用子函数来解决问题。在子函数中使用双指针来取连续相同元素的数目。

双指针遍历有一个问题,当判断条件为相邻元素差异的时候,对序列x,需要比较x[i]与x[i+1],首先x+1存在溢出风险,需要限定x+1 暂时的解法是在序列后加一个必然产生差异的元素,在遍历的过程中,正好抛弃掉最后一个额外元素,实现最终结果。(待学习)

取消for循环,用end遍历,依次加1,循环条件为while end

总结:

  1. 当双指针遍历时,尽量避免使用for,而直接使用while,跟踪right指针来结束循环。
  2. 如果外循环为while,那么内循环大概率也需使用while循环,循环条件也要加上边界避免在内循环直接超出边界,同时尽量类比start而不是前一个元素或者后一个元素(此时会漏掉边界元素)。
class Solution:
    def countAndSay(self, n: int) -> str:
        index = '1'
        for i in range(n-1):
            index = self.turn(index)
        return index

    def turn(self, index_):
        index_ = index_+'#'
        start = 0
        ans = ''
        length = len(index_)
        for i in range(length-1):
            if index_[i]!=index_[i+1]:
                end = i
                ans = ans+(str(end-start+1)+index_[i])
                start = i + 1
        return ans

# 新子函数
def turn(self, index_):
        start = 0
        end = 0
        ans = ''
        length = len(index_)
        while end < length:
            while end<length and index_[start]==index_[end]:
                end = end+1
            ans = ans+(str(end-start)+index_[start])
            start = end
        return ans

58. 最后一个单词的长度

同38. 外观数列

# 求最后一个单词的长度,直接逆序双指针
# 先循环过滤掉' ',确定单词的终止位置
# 再循环单元字符,确定单词的开始位置,直接返回
# 如果正序双指针,需要移除字符串末尾的' '
class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        n = len(s)
        start = n-1
        end = n-1
        while end>=0:
            while end>=0 and s[end]==' ':
                end = end-1
            start = end
            while end>=0 and s[end]!=' ':
                end = end-1
            return start-end

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        n = len(s)
        start = n-1
        end = n-1

        while end>=0 and s[end]==' ':
            end = end-1
        start = end
        while end>=0 and s[end]!=' ':
            end = end-1
        return start-end

89. 合并两个有序数组

利用双指针从前到后进行归并排序。
题目中给出了有效数字的概念,牢记指针边界与有效数字位数的关系,比较简单的一道排序问题。

!:可以使用双指针从后向前,避免了开辟额外空间存储数组。因为已知nums1的长度为m+n,直接逆序存储元素,可以直接插入。

# 从前向后的双指针
# 直接双指针的归并排序,当一个遍历结束,剩下的全部加到队列末尾
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.
        """
        ans = []
        i,j = 0,0
        while(i<m and j<n):
            if (nums1[i]<=nums2[j]):
                ans.append(nums1[i])
                i = i+1
            else:
                ans.append(nums2[j])
                j = j+1
        if j<n:
            ans = ans + nums2[j:n]
        elif i<m:
            ans = ans + nums1[i:m]
        nums1[:] = ans

# 从后向前的双指针,精彩!
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.
        """
        ans = []
        i,j = m-1, n-1
        p = m + n - 1
        while i>=0 and j>=0:
            if nums1[i]<=nums2[j]:
                nums1[p] = nums2[j]
                j = j-1
            else:
                nums1[p] = nums1[i]
                i = i-1
            p = p-1
        if j>=0:
            nums1[:j+1] = nums2[:j+1]

125. 验证回文串

只要考察对于语言字符串相关API的应用与理解:

  1. capitalize()
    将字符串的第一个字符转换为大写

  2. center(width, fillchar)
    返回一个指定的宽度 width 居中的字符串,fillchar 为填充的字符,默认为空格。

  3. count(str, beg= 0,end=len(string))
    返回 str 在 string 里面出现的次数,如果 beg 或者 end 指定则返回指定范围内 str 出现的次数

  4. encode(encoding=‘UTF-8’,errors=‘strict’)
    以 encoding 指定的编码格式编码字符串,如果出错默认报一个ValueError 的异常,除非 errors 指定的是’ignore’或者’replace’

  5. endswith(suffix, beg=0, end=len(string))
    检查字符串是否以 obj 结束,如果beg 或者 end 指定则检查指定的范围内是否以 obj 结束,如果是,返回 True,否则返回 False.

  6. find(str, beg=0, end=len(string))
    检测 str 是否包含在字符串中,如果指定范围 beg 和 end ,则检查是否包含在指定范围内,如果包含返回开始的索引值,否则返回-1

  7. index(str, beg=0, end=len(string))
    跟find()方法一样,只不过如果str不在字符串中会报一个异常。

  8. isalnum()
    如果字符串至少有一个字符并且所有字符都是字母或数字则返 回 True,否则返回 False

  9. isalpha()
    如果字符串至少有一个字符并且所有字符都是字母或中文字则返回 True, 否则返回 False

  10. isdigit()
    如果字符串只包含数字则返回 True 否则返回 False…

  11. islower()
    如果字符串中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是小写,则返回 True,否则返回 False

  12. isnumeric()
    如果字符串中只包含数字字符,则返回 True,否则返回 False

  13. isspace()
    如果字符串中只包含空白,则返回 True,否则返回 False.

  14. isupper()
    如果字符串中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是大写,则返回 True,否则返回 False

  15. join(seq)
    以指定字符串作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的字符串

  16. len(string)
    返回字符串长度

  17. ljust(width[, fillchar])
    返回一个原字符串左对齐,并使用 fillchar 填充至长度 width 的新字符串,fillchar 默认为空格。

  18. lower()
    转换字符串中所有大写字符为小写.

  19. lstrip()
    截掉字符串左边的空格或指定字符。

  20. maketrans()
    创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。

  21. max(str)
    返回字符串 str 中最大的字母。

  22. min(str)
    返回字符串 str 中最小的字母。

  23. replace(old, new [, max])
    将字符串中的 old 替换成 new,如果 max 指定,则替换不超过 max 次。

  24. rfind(str, beg=0,end=len(string))
    类似于 find()函数,不过是从右边开始查找.

  25. rindex( str, beg=0, end=len(string))
    类似于 index(),不过是从右边开始.

  26. rjust(width,[, fillchar])
    返回一个原字符串右对齐,并使用fillchar(默认空格)填充至长度 width 的新字符串

  27. rstrip()
    删除字符串字符串末尾的空格.

  28. split(str="", num=string.count(str))
    以 str 为分隔符截取字符串,如果 num 有指定值,则仅截取 num+1 个子字符串

  29. splitlines([keepends])
    按照行(’\r’, ‘\r\n’, \n’)分隔,返回一个包含各行作为元素的列表,如果参数 keepends 为 False,不包含换行符,如果为 True,则保留换行符。

  30. startswith(substr, beg=0,end=len(string))
    检查字符串是否是以指定子字符串 substr 开头,是则返回 True,否则返回 False。如果beg 和 end 指定值,则在指定范围内检查。

  31. strip([chars])
    在字符串上执行 lstrip()和 rstrip()

  32. swapcase()
    将字符串中大写转换为小写,小写转换为大写

  33. translate(table, deletechars="")
    根据 str 给出的表(包含 256 个字符)转换 string 的字符, 要过滤掉的字符放到 deletechars 参数中

  34. upper()
    转换字符串中的小写字母为大写

  35. zfill (width)
    返回长度为 width 的字符串,原字符串右对齐,前面填充0

  36. isdecimal()
    检查字符串是否只包含十进制字符,如果是返回 true,否则返回 false。

方法:

  1. 筛选+判断对称 (翻转、双指针)
  2. 原字符串上使用双指针
# 方法1,筛选+翻转
class Solution:
    def isPalindrome(self, s: str) -> bool:
        n = [i.lower() for i in s if i.isalnum()]
        return n==n[::-1]
# 方法2,双指针
class Solution:
    def isPalindrome(self, s: str) -> bool:
        start = 0
        end = len(s)-1

        while start<end:
            while start<end and not s[start].isalnum():
                start += 1
            while start<end and not s[end].isalnum():
                end -= 1

            if start<end: 
                if s[start].lower()!=s[end].lower():
                    return False
                start += 1
                end -= 1
        return True

141. 环形链表

228. 汇总区间

解法:

  1. 简单一次遍历(while)。遍历的过程中需要考虑到边界的问题,很重要。
  2. 双指针(for),逐次更新start指针和end指针。
# 必须要遍历到i=length-1处,此时i+1超出了数组范围,
# 必须要处理好边界问题
class Solution:
    def summaryRanges(self, nums: List[int]) -> List[str]:
        length = len(nums)
        i = 0
        summary = []
        while i < length:
            start = nums[i]
            while i<length-1 and nums[i+1]==nums[i]+1:
                i = i+1
            end = nums[i]
            i = i + 1
            if start==end:
                summary.append(str(start))
            else:
                summary.append(str(start)+'->'+str(end))
        return summary

class Solution:
    def summaryRanges(self, nums: List[int]) -> List[str]:
        length = len(nums)
        i = 0
        summary = []
        while i < length:
            start = nums[i]
            i = i+1
            while i<length and nums[i]==nums[i-1]+1:
                i = i+1
            end = nums[i-1]
            if start==end:
                summary.append(str(start))
            else:
                summary.append(str(start)+'->'+str(end))
        return summary

双指针:

class Solution:
    def summaryRanges(self, nums: List[int]) -> List[str]:
        length = len(nums)
        summary = []
        start = 0 # 双指针依次更新双指针
        for i in range(0,length):
            if i+1>=length or nums[i]+1!=nums[i+1]:
                end = i
                if start==end:
                    summary.append(str(nums[start]))
                else:
                    summary.append(str(nums[start])+'->'+str(nums[end])) 
                start = i+1
        return summary

查找及插入问题

二分查找及其变种

Leetcode简单题总结_第3张图片
二分查找的核心思想是「减而治之」,即「不断缩小问题规模」。
二分查找的两种思路:

  1. 思路 1:在循环体内部查找元素,搜索一个元素时,左闭右闭,直接看if是否相同,相同时返回;while结束返回-1.
    while(left <= right) 这种写法表示在循环体内部直接查找元素;
    退出循环的时候 left 和 right 不重合,区间 [left, right] 是空区间。
  2. 思路 2:在循环体内部排除元素(重点),搜索左右边界,此时搜索区间左闭右开,if相等时不返回,用mid确定新边界;
    while(left < right) 这种写法表示在循环体内部排除元素;
    退出循环的时候 left 和 right 重合,区间 [left, right] 只剩下成 11 个元素,这个元素 有可能 就是我们要找的元素。

探究几个最常用的二分查找场景:寻找一个数、寻找左侧边界、寻找右侧边界。而且,我们就是要深入细节,比如不等号是否应该带等号,mid 是否应该加一等等。分析这些细节的差异以及出现这些差异的原因,保证你能灵活准确地写出正确的二分查找算法。
链接: Leetcode.
链接: Leetcode.

  1. 模板
# 模板  ...表示需要注意的地方
int binarySearch(int[] nums, int target) {
     
    int left = 0, right = ...;  # 表示了区间的开闭

    while(...) {
     
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
     
            ...
        } else if (nums[mid] < target) {
     
            left = ...
        } else if (nums[mid] > target) {
     
            right = ...
        }
    }
    return ...;
}
  1. 寻找一个数的二分搜索(基本的二分搜索)
int binarySearch(int[] nums, int target) {
     
    int left = 0; 
    int right = nums.length - 1; // 注意,左闭右闭

    while(left <= right) {
     
        int mid = left + (right - left) / 2;
        if(nums[mid] == target)
            return mid; 
        else if (nums[mid] < target)
            left = mid + 1; // 注意
        else if (nums[mid] > target)
            right = mid - 1; // 注意
    }
    return -1;
}
  1. 寻找左边界的二分搜索
    Leetcode简单题总结_第4张图片
// 寻找左边界的关键其实是寻找当前列表中多少数小于targe
// 所以使用左闭右开区间,当if条件达成,不返回,不断向左收缩
// 在新区间里找target,可以确定左边界
// 左闭右开,所以right = length,右边界取不到,
// 所以直接right=mid,而无需right = mid-1
int left_bound(int[] nums, int target) {
     
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意
    
    while (left < right) {
      // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
     
            right = mid;  
            //此时左边界一定在mid左侧或者mid,所以right=mid
        } else if (nums[mid] < target) {
     
            left = mid + 1;
        } else if (nums[mid] > target) {
     
            right = mid; // 注意
        }
    }
    // target 比所有数都大
	if (left == nums.length) return -1;
	// 类似之前算法的处理方式
	return nums[left] == target ? left : -1;
}

// 修改的左闭右闭代码
int left_bound(int[] nums, int target) {
     
    int left = 0, right = nums.length - 1;
    // 搜索区间为 [left, right]
    while (left <= right) {
     
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
     
            // 搜索区间变为 [mid+1, right]
            left = mid + 1;
        } else if (nums[mid] > target) {
     
            // 搜索区间变为 [left, mid-1]
            right = mid - 1;
        } else if (nums[mid] == target) {
     
            // 收缩右侧边界
            right = mid - 1;
        }
    }
    // 检查出界情况
    if (left >= nums.length || nums[left] != target)
        return -1;
    return left;
}
  1. 寻找右边界的二分搜索
int right_bound(int[] nums, int target) {
     
    if (nums.length == 0) return -1;
    int left = 0, right = nums.length;
    
    while (left < right) {
     
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
     
            left = mid + 1; // 注意
        } else if (nums[mid] < target) {
     
            left = mid + 1;
        } else if (nums[mid] > target) {
     
            right = mid;
        }
    }
    return left - 1; // 注意
}

35. 搜索插入位置

方法:

  1. 直接遍历
  2. 二分查找(在一个有序列表中查找,二分查找以O(log n)时间复杂度完成搜索,但是在这个任务中,二分查找法需要对条件进行修改,即我们需要:在一个有序数组中找第一个大于等于 target 的下标)
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        low = 0
        high = len(nums)-1
        while low<=high:
            mid = (low+high)//2
            if nums[mid]==target:
                return mid
            elif nums[mid]<target:
                low = mid + 1
            elif nums[mid]>target:
                high = mid-1

        if target>nums[mid]: # 在mid后插入,插入位置为mid+1
            return mid+1
        else:   # 在mid出插入
            return mid
          
# 优化后代码
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        low = 0
        high = len(nums)-1
        ans = 0
        while low<=high:
            mid = (low+high)//2
            if nums[mid]>=target:
                ans = mid
                high = mid-1
            else:  # 严格小于 target 的元素一定不是解
                low = mid + 1
        return ans

69. x的平方根

方法:

  1. 「袖珍计算器算法」是一种用指数函数 exp 和对数函数 ln 代替平方根函数的方法。我们通过有限的可以使用的数学函数,得到我们想要计算的结果。
    在这里插入图片描述

  2. 二分查找。在一个有范围的区间里搜索一个整数。

  3. 牛顿迭代法
    Leetcode简单题总结_第5张图片

# 方法一
class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0:
            return 0
        ans = int(math.exp(0.5 * math.log(x)))
        return ans + 1 if (ans + 1) ** 2 <= x else ans
# 方法二
# 整个问题可以转化为,平方小于等于x的最大整数
# 如果选择右边界,决定了迭代次数,最大可以选择x-1;对于x>4,可以选择x//2
class Solution:
    def mySqrt(self, x: int) -> int:
        import math
        left = 0
        right = math.ceil(x/2)
        ans = 0
        while left<=right:
            mid = (left+right)//2
            if mid*mid <= x:
                ans = mid
                left = mid+1
            elif mid*mid > x:
                right = mid - 1
        return ans
# 0 1 2 3 4
# mid 2  left = mid+1 = 3 --> mid 3 right = 2

# 方法三 牛顿迭代法
# 函数f(x) = ans^2 - x,求f(x)=0时的ans,即求出使f(x)逼近0的最优解
# 导数f'(x) = 2*ans
class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0:
            return 0
        
        C, x0 = float(x), float(x)
        while True:
            xi = 0.5 * (x0 + C / x0)
            if abs(x0 - xi) < 1e-7:
                break
            x0 = xi
        
        return int(x0)

704. 二分查找

在数组中查找符合条件的元素的下标。
Leetcode简单题总结_第6张图片

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        low = 0
        high = len(nums)-1
        while(low<=high):  
        # 查找的时候需要使用=,否则会漏掉最终一个边界元素,
        # 即mid=low=high对应的元素,返回(low,high)为空
            # mid = (low+high)//2 
            # mid向下或者向上取整常规状态下不影响结果
            mid = low + (high - low) // 2  
            # 避免left和right很大的时候溢出,python不存在这个问题
            if nums[mid]==target:
                return mid
            if nums[mid]>target:
                high = mid - 1
            if nums[mid]<target:
                low = mid + 1
        return -1
#如果非要使用low

852. 山脉数组的峰顶索引

方法:

  1. 线性扫描,当arr[i]>arr[i+1]时,i即为峰顶
  2. 二分查找,这个题目中二分查找的关键在于左移右移的条件选择,当arr[mid]arr[arr+1],证明在下山,移动右指针。细节的把控上需要仔细思考和调控。
# 方法一
// A code block
var foo = 'bar';

# 方法二
class Solution:
    def peakIndexInMountainArray(self, arr: List[int]) -> int:
        length = len(arr)
        left = 0
        right = length-1
        ans = 0
        while left<=right:
            mid = (left+right)//2
            if arr[mid]<arr[mid+1]:
                left = mid + 1
            elif arr[mid]>arr[mid+1]:
                right = mid-1
                ans = mid
        return ans

队列、栈

155. 最小栈

考察栈的基本概念,熟练掌握list(python)、数组(c/c++)的相关操作,可以轻松解决该简单题。问题的难度在于如何在常数时间内检索到最小元素的栈。自然地,我们如果能够在每次push新元素进入时,保存当前栈状态下对应的最小元素;那么当我们查询最小元素时,可以直接返回最小元素值。

建立辅助栈minStack来存储最小元素,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。当push新元素x进入Stack时,min(minStack[-1],x)为当前栈状态下的最小元素,push进minStack中。

class MinStack:
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.Stack = []
        self.minStack = [float(inf)]

    def push(self, x: int) -> None:
        self.Stack.append(x)
        self.minStack.append(min(self.minStack[-1], x))

    def pop(self) -> None:
        self.Stack.pop()
        self.minStack.pop()

    def top(self) -> int:
        return self.Stack[-1]

    def getMin(self) -> int:
        return self.minStack[-1]

同样的思路,我们可以不使用两个栈来进行操作,而只使用一个栈来保存。当有更小的值来的时候,我们只需要把之前的最小值入栈,当前更小的值再入栈即可。当这个最小值要出栈的时候,下一个值便是之前的最小值了。

入栈 2 ,同时将之前的 min 值 3 入栈,再把 2 入栈,同时更新 min = 2
| 2 |   min = 2
| 3 |  
| 5 |     
|_3_|    
stack  

入栈 6 
| 6 |  min = 2
| 2 |   
| 3 |  
| 5 |     
|_3_|    
stack  

出栈 6     
| 2 |   min = 2
| 3 |  
| 5 |     
|_3_|    
stack  

出栈 2     
| 2 |   min = 2
| 3 |  
| 5 |     
|_3_|    
stack  

出栈 2     
|   |  min = 3   
| 5 |   
|_3_|    
stack  
class MinStack:
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.Stack = []
        self.min = float(inf)

    def push(self, x: int) -> None:
        if x<=self.min:  # 加入这个元素更新了最小值,则将之前的最小值保存
            self.Stack.append(self.min)
            self.min = x
        self.Stack.append(x)

    def pop(self) -> None:
        a = self.Stack.pop()
        if a == self.min:
            self.min = self.Stack.pop()
            
    def top(self) -> int:
        return self.Stack[-1]

    def getMin(self) -> int:
        return self.min

这种方法会使用额外的空间来存储,怎么样能不使用额外空间?
我们使用一个变量存储min,在每次存入元素x的时候,每次存入 x-min

  • 当存入值为正时,则x大于min,不更新min,当从栈中pop元素时,输出 x-min+min;
  • 当存入值为负时,则更新min=x,在pop过程中每次pop出负数,则证明修改过min,将min改为min-存入值。
入栈 3,存入 3 - 3 = 0
|   |   min = 3
|   |     
|_0_|    
stack   

入栈 5,存入 5 - 3 = 2
|   |   min = 3
| 2 |     
|_0_|    
stack  

入栈 2,因为出现了更小的数,所以我们会存入一个负数,这里很关键
也就是存入  2 - 3 = -1, 并且更新 min = 2 
对于之前的 min 值 3, 我们只需要用更新后的 min - 栈顶元素 -1 就可以得到    
| -1|   min = 2
| 5 |     
|_3_|    
stack  

入栈 6,存入  6 - 2 = 4
| 4 |   min = 2
| -1| 
| 5 |     
|_3_|    
stack  

出栈,返回的值就是栈顶元素 4 加上 min,就是 6
|   |   min = 2
| -1| 
| 5 |     
|_3_|    
stack  

出栈,此时栈顶元素是负数,说明之前对 min 值进行了更新。
入栈元素 - min = 栈顶元素,入栈元素其实就是当前的 min 值 2
所以更新前的 min 就等于入栈元素 2 - 栈顶元素(-1) = 3
|   | min = 3
| 2 |     
|_0_|    
stack     
class MinStack:
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.Stack = []
        self.min = float(inf)  # 仅使用一个变量表示min

    def push(self, x: int) -> None:
        if len(self.Stack)==0: # 初始化栈时,push元素时栈顶为0
            self.Stack.append(0)
            self.min = x
        else:
            self.Stack.append(x-self.min)
            if x<=self.min:  
                self.min = x

    def pop(self) -> None:
        a = self.Stack.pop()  # 如果栈顶为负,则更新min
        if a < 0:
            self.min = self.min - a

    def top(self) -> int:
    	# 当栈顶大于0时,则
        if self.Stack[-1]>=0:
            return self.Stack[-1]+self.min
        else:    
        # 当前push值小于min时,将该值赋给min,栈顶为负数,用于恢复min
        # 当栈顶为负数时,则栈顶对应元素为min,直接返回min
            return self.min

    def getMin(self) -> int:
        return self.min

数组

66. 加一

考虑对于基础数学加法原则和数组知识的考察,比较简单。

# 写法一,提前为数组补上首位0,可直接在原数组上修改,简化了首位赋值的困难
class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        digits = [0]+digits
        rex = 1
        n = len(digits)
        for i in range(n-1,-1,-1):
            num = digits[i] + rex
            if num>9:
                digits[i] = num%10
                rex = 1
            else:
                digits[i] = num
                break
        if digits[0]==0:
            return digits[1:]
        else:
            return digits

# 写法二,
class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        digits = digits
        rex = 1
        n = len(digits)
        for i in range(n-1,-1,-1):
            num = digits[i] + rex
            if num>9:
                digits[i] = num%10
                rex = 1
            else:
                digits[i] = num
                return digits
        
        return [1]+digits

67. 二进制求和

  1. 末尾对齐,逐位相加,遇2进1。可以先反转这个代表二进制数字的字符串,然后低下标对应低位,高下标对应高位。当然你也可以直接把 aa 和 bb 中短的那一个补 0 直到和长的那个一样长,然后从高位向低位遍历。
  2. 如果循环结束,进位为1,则前补’1’
class Solution:
    def addBinary(self, a: str, b: str) -> str:
        ret = 0  # 记录进位
        na = len(a)
        nb = len(b)
        c =''
        if na>nb:
            b = '0'*(na-nb)+b
            mn = na
        elif na<nb:
            a = '0'*(nb-na)+a
            mn = nb
        else:
            mn = na
        for i in range(mn-1,-1,-1):
            num = int(a[i])+int(b[i]) + ret
            if num>1:
                num_ = num%2
                ret = num//2
                c = str(num_)+c
            else:
                ret = 0
                c = str(num)+c
        if ret==1:
            return '1'+c
        else:
            return c

189. 旋转数组 middle

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        length = len(nums)
        k = k % length # 如果k大于length,需要取余,以提取出最后的元素
        #方法零 开新数组,将元素放在对应位置上
        # new = [0]*length
        # for i in range(length):
            # new[(i+k)%length] = nums[i]
        # nums[:] = new

        # 方法一 因为环形循环,保留会被覆盖的k个元素,这也开辟了新空间,直接循环移动
        # if k==0:
        #     pass
        # else:
        #     tmp = nums[-k:]

        #     for j in range(length-k-1, -1, -1):
        #         nums[j+k] = nums[j]
        #     nums[:k] = tmp

        
        # 方法二  循环问题,可以直接在后面开一个相同的数组,直接通过数学计算确定开始位置和结束位置,但是这样开了额外空间。
        #不写切片相当于nums修改的地址重新指向右边的临时地址,写切片相当于按着切片下标修改值,前者在线上判定里无法AC,线上判定只判定原地地址的情况,不写切片的nums只在函数内有效。
        # new_nums = nums+nums
        # start = length-k
        # end = 2*length - k
        # nums[:] = new_nums[start:end:1]  # [:]代表取元素再赋值,而不是直接变更nums数组

        # nums[: ] = (nums[i] for i in range(-(k % len(nums)), len(nums) - k % len(nums)))

        # 方法三 翻转法,依旧未开辟额外空间,python list自带deverse()函数
        # 我们可以采用翻转的方式,比如12345经过翻转就变成了54321,这样已经做到了把前面的数字放到后面去,但是还没有完全达到我们的要求,比如,我们只需要把12放在后面去,目标数组就是34512,与54321对比发现我们就只需要在把分界线前后数组再进行翻转一次就可得到目标数组了。所以此题只需要采取三次翻转的方式就可以得到目标数组,首先翻转分界线前后数组,再整体翻转一次即可。
        # def reverse(nums, start, end):
        #     while start<=end:
        #         temp = nums[start]
        #         nums[start] = nums[end]
        #         nums[end] = temp
        #         start = start+1
        #         end = end-1
        
        # reverse(nums,0,length-1)
        # reverse(nums,0, k-1)
        # reverse(nums,k,length-1)


        # 方法四 循环替换
        import math
        n, k = len(nums), k % len(nums)
        g = math.gcd(k, n)
        for i in range(g):
            s, x = i, nums[i]
            for j in range(n // g):
                nums[(s + k) % n], x = x, nums[(s + k) % n]
                s = (s + k) % n

链表

83. 删除排序链表中的重复元素(hash)

# 用hash存储元素,重点考察hash的应用以及链表的基本操作
# 当该结点元素重复时,需要将该结点前结点.next = curr.next,故需要保存前一个结点
class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        hash_list = set()
        curr = head
        while curr:
            val = curr.val
            if val not in hash_list:
                hash_list.add(val)
                pre = curr
                curr = curr.next
            else:
                pre.next = curr.next
                curr = pre.next
        return head

141. 环形链表

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
# 检测链表是否闭环,使用快慢指针
class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        if head==None or head.next==None:
            return False
        
        fast = head
        slow = head

        while fast.next!=None and fast.next.next!=None:
            fast = fast.next.next
            slow = slow.next

            if fast==slow:
                return True
        
        return False

160. 相交链表

方法:

  1. 暴力法:对链表A中的每一个结点a,遍历整个链表 B 并检查链表 B 中是否存在结点和 a相同。
  2. 哈希表法:遍历链表 A 并将每个结点的地址/引用存储在哈希表中。然后检查链表 B 中的每一个结点 b​ 是否在哈希表中。若在,则 b 为相交结点。
  3. 双指针法:创建两个指针 pApA 和 pBpB,分别初始化为链表 A 和 B 的头结点。然后让它们向后逐结点遍历。
    当 pA到达链表的尾部时,将它重定位到链表 B 的头结点; 类似的,当 pB到达链表的尾部时,将它重定位到链表 A 的头结点。
  • 假设pA和pB走完了全程:
  1. 由于指针pA走了个A+B,指针pB走了个B+A,因此两者走过的长度是相等的
  2. A和B结尾部分是相等的,因此A+B和B+A结尾部分也是相等的
  3. 因此当pA与pB相遇时,该节点就是相交节点
  • 假设A和B没有交点
  1. A和B均是以null结尾,因此A+B和B+A也是以null结尾
  2. 因此当pA和pB恰好走完全程后,他们同时到达null,此时他们是相等的
    依然满足循环终止条件
# 忽略了可以通过链接解决长度差问题,保证A、B指针同时到达相交结点,关键思路在于路径的构思
# A、B两链表到达链尾之后,再走到彼此的路,可以实现相同的路径长度
# 需要考虑的特殊情况,
# 设相交后长度为C,每个指针走过路程均为 A+B-C,如果存在相交结点则同时到达
# 相同长度不想交,在第一轮时同时到达None,退出
# 不同长度不想交,在第二轮时走过的长度均为A+B,同时到达None,退出,即使A或者B为None,走过路径依旧相同
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        A = headA
        B = headB
        while headA!=headB:
            headA = B if headA==None else headA.next

            headB = A if headB==None else headB.next
        
        return headA

100. 相同的树(递归)

# 递归调用,遍历左右子树,完整递归终止条件是,p\q均为空(即遍历到叶子节点的左右子树),则正常终止此次递归
# 提前终止递归,p.val/q.val不同,q/p不同时为
class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        if p==None and q==None: 
            return True
        if p!=None and q==None: 
            return False
        if p==None and q!=None: 
            return False
        if p.val!=q.val: 
            return False

        return self.isSameTree(p.left, q.left) & self.isSameTree(p.right, q.right)

101. 对称二叉树

// A code block
var foo = 'bar';

104. 二叉树的最大深度

// A code block
var foo = 'bar';

107. 二叉树的层序遍历 ||

// A code block
var foo = 'bar';

108. 将有序数组转换为二叉搜索树(递归)

直观地看,我们可以选择中间数字作为二叉搜索树的根节点,这样分给左右子树的数字个数相同或只相差 11,可以使得树保持平衡。如果数组长度是奇数,则根节点的选择是唯一的,如果数组长度是偶数,则可以选择中间位置左边的数字作为根节点或者选择中间位置右边的数字作为根节点,选择不同的数字作为根节点则创建的平衡二叉搜索树也是不同的。

确定平衡二叉搜索树的根节点之后,其余的数字分别位于平衡二叉搜索树的左子树和右子树中,左子树和右子树分别也是平衡二叉搜索树,因此可以通过递归的方式创建平衡二叉搜索树。

递归的基准情形是平衡二叉搜索树不包含任何数字,此时平衡二叉搜索树为空。
树的遍历的终止条件,通常判定当前node为空,即为叶子节点的左右子树;而不是在叶子节点终止。
方法:

  1. 直接递归调用自身,每次传入新的数组序列
  2. 定义递归子函数,执行递归,只需要传入建树元素在数组序列中的位置
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
# 方法一
class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:

        if not nums:
            return None
        left = 0
        right = len(nums)-1

        mid = left + (right-left)//2

        node = TreeNode(nums[mid])
        node.left = self.sortedArrayToBST(nums[:mid])
        node.right = self.sortedArrayToBST(nums[mid+1:])
        
        return node
    
# 方法二
class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
        def helper(left, right):
            if left > right:
                return None

            # 总是选择中间位置左边的数字作为根节点
            mid = (left + right) // 2

            root = TreeNode(nums[mid])
            root.left = helper(left, mid - 1)
            root.right = helper(mid + 1, right)
            return root

        return helper(0, len(nums) - 1)

110. 平衡二叉树

// A code block
var foo = 'bar';

111. 二叉树的最小深度

// A code block
var foo = 'bar';

112. 路径总和

查找到叶子节点的路径和,可以使用DFS或者BFS。

在使用DFS中,需要记录到当前位置的和sum,再遍历左右子树,遍历左子树之后,去遍历右子树的时候很自然地希望当前位置的和依旧为sum。
在实际代码中方法:

  1. 回溯法,当从dfs退出时,sum减去当前结点的val
  2. 形参法,不定义变量,每次传入sum+val,避免了sum的修改

使用广度优先搜索BFS的方式,记录从根节点到当前节点的路径和,以防止重复计算。这样我们使用两个队列,分别存储将要遍历的节点,以及根节点到这些节点的路径和即可。
Leetcode简单题总结_第7张图片

DFS:

# 询问是否有从「根节点」到某个「叶子节点」经过的路径上的节点之和等于目标和。
#核心思想是对树进行一次遍历,在遍历时记录从根节点到当前节点的路径和,以防止重复计算。
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if root ==None:
            return False

        def dfs(node, sum_):
            if node.left==None and node.right==None:
                if sum==sum_+node.val:
                    return True
                else:
                    return False
            result_1 = False
            result_2 = False
            if node.left:
                result_1 = dfs(node.left, sum_+node.val)
            if node.right:
                result_2 = dfs(node.right, sum_+node.val)
            return result_1 or result_2
                # 使用or的一个好处就是当前面判定为True,则直接返回,不进行or后的计算
        
        return dfs(root, 0)

BFS:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if not root:
            return False
            
        val_list = [root.val]
        node_list = [root]

        while node_list:
            node = node_list.pop(0)
            value = val_list.pop(0)
            if node.left==None and node.right==None:
                if value == sum:
                    return True
            if node.left:
                node_list.append(node.left)
                val_list.append(value+node.left.val)
            if node.right:
                node_list.append(node.right)
                val_list.append(value+node.right.val)

        return False

动态规划

53. 最大子序和

方法:

  1. 暴力求解,直接遍历,O(n^2)时间复杂度
    暴力解法的思路,第一层for 就是设置起始位置,第二层for循环遍历数组寻找最大值
  2. 贪心,与暴力求解中依次回到初始元素,贪心每次作出当前的最优选择。为了贪心地追求局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。局部最优的情况下,并记录最大的“连续和”,可以推出全局最优。从代码角度上来讲:遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和。这相当于是暴力解法中的不断调整最大子序和区间的起始位置。
    首先永远从非负整数开始,在当前元素加到sum的时候,保留最大值;当sum小于0时,在之后不可能出现最大子序列和,将sum置0.
  3. 动态规划,若前一个元素大于0,则加在当前元素上。
    Leetcode简单题总结_第8张图片
    问题演化为求f(0)–f(n-1)的最大值,根据动态规划转移方程,如果要求f(n-1),首先要依次求f(0) f(1) f(2)……,而f(0)可以很轻易的被求出来,f(0) = nums[0],直接通过迭代求解,并在过程中保留最大值。
    而整个过程类似于贪心,当f(x)>0时,显然f(x)+a更大,继续向后推导;当f(x)<0时,等同于sum<0时重置sum=0,此时f(x+1) = 0+a。
  4. 分治

代码:

# 暴力求解,python不能AC,C++语言可以AC
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        length = len(nums)
        max_ = nums[0]
        for i in range(length):
            sum_ = 0
            for j in range(i, length):
                sum_ = sum_ + nums[j]
                if sum_>max_:
                    max_ = sum_
        return max_

# 贪心
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        import math
        max_ = -float('inf')
        sum_ = 0
        length = len(nums)
        for i in range(length):
            sum_ = sum_+nums[i]
            max_ = max(max_, sum_)
            if sum_<0:
                sum_ = 0
        return max_                   
# 动态规划
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        f = nums[0]
        n = len(nums)
        max_ = nums[0]
        for i in range(1,n):
            f = max(f+nums[i], nums[i])
            max_ = max(f, max_)
        return max_                   

70. 爬楼梯

方法:

  1. 典型的动态规划问题,使用dp数组或者滚动数组
  2. 根据数学规律总结通项公式
  3. 递归;记忆化递归:可以保存中间结果,减少重复计算
# 动态规划,设f(x)为爬x阶到达楼顶的不同方法次数
# 状态方程!
# f(n) = f(n-1) + f(n-2)
# f(0) = 1
# f(1) = 1
# f(2) = f(1)+f(0) =2
# f(3) = f(2)+f(1) =3
# f(4) = f(3)+f(2) =5
class Solution:
    def climbStairs(self, n: int) -> int:
        f_1 = 1
        f_2 = 1
        for i in range(1,n):
            f = f_1 + f_2  # 滚动数组,斐波那契数列
            f_2 = f_1
            f_1 = f
        return f_1

121. 买卖股票的最佳时机 |

55. 最大子序和相同类似
方法:

  1. 暴力求解,遍历数组,选择买入日期,在之后的序列中选择卖出日期,保留最大收益,O(n^2)。
# 暴力法无法AC,样例测试超市
// A code block
var foo = 'bar';
  1. 贪心,双指针:使用start和end标记当前买入和卖出日期,当prices[end]-prices[start]小于0时,即出现了更低的买入价格,我们从新的买入价格之后查找卖
# 贪心+双指针:一旦出现负数收益,即出现了更小的买入价格,更新买入价格
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        ans = 0
        start = 0
        end = 1
        n = len(prices)
        while end<n:
            re = prices[end]-prices[start]
            if re<=0:
                start = end
                end = end+1
            else:
                ans = max(ans, re)
                end = end+1
        return ans

# 贪心+一次遍历
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        ans = 0
        minprice = prices[0]
        n = len(prices)
        for i in range(1,n):
            if minprice>=prices[i]:
                minprice = prices[i]
            else:
                ans = max(ans, prices[i]-minprice)
        return ans
  1. 动态规划

122. 买卖股票的最佳时机 ||

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        start,end,ans = 0,1,0
        n = len(prices)
        while end<len(prices):
            while end<n and prices[end-1]<=prices[end]:
                end = end+1
            ans += prices[end-1]-prices[start]
            start = end
            end = start+1
        return ans

位运算

67. 二进制求和

Leetcode简单题总结_第9张图片

运算过程:
a = 0000 1111, b = 0000 0010
a ^ b      = 0000 1101         --> 新的a
(a&b) << 1 = 0000 0100         --> 新的b

a ^ b      = 0000 1001         --> 新的a
(a&b) << 1 = 0000 1000         --> 新的b

a ^ b      = 0000 0001         --> 新的a
(a&b) << 1 = 0001 0000         --> 新的b

a ^ b      = 0001 0001         --> 答案是:10001
(a&b) << 1 = 0000 0000         --> b已经为:0000 0000
class Solution:
    def addBinary(self, a, b) -> str:
        x, y = int(a, 2), int(b, 2)
        while y:
            answer = x ^ y
            carry = (x & y) << 1
            x, y = answer, carry
        return bin(x)[2:]

137. 只出现一次的数字(1) middle

187. 重复的DNA序列 middle

191. 位1的个数

260. 只出现一次的数字(2) middle

318. 最大单词长度乘积 middle

421. 数组中两个数的最大异或值 middle

数学、矩阵

50. pow(x,n) middle

暴力解法直接n个x相乘,时间复杂度为O(n)
快速幂算法:O(log n)时间复杂度的分治算法

class Solution:
    def myPow(self, x: float, n: int) -> float:
        if (x==0):
            return 0
        if (n==0):
            return 1
        if n<0:
            return 1/self.myPowHelper(x, -n)
        else:
            return self.myPowHelper(x, n)
    

    def myPowHelper(self, x, n):
        if n==1:
            return x
        
        if(n%2==0):
            res = self.myPowHelper(x,n//2)
            return res*res
        else:
            res = self.myPowHelper(x,n//2)
            return res*res*x

70. 爬楼梯

118. 杨辉三角 |

Leetcode简单题总结_第10张图片

# 给定一层的列表,建立子函数,求解下一层的杨辉三角
# 标定第一层和第二层的特殊值,检查numRows,直接赋值
class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        ans = []
        if numRows>=1:
            ans.append([1])
        if numRows>=2:
            ans.append([1,1])

        def g(n):
            nu = []
            for i in range(0,len(n)-1):
                nu.append(n[i]+n[i+1])
            return [1] + nu + [1]
        
        n = [1,1]
        for i in range(2,numRows):
            n = g(n)
            ans.append(n)
        return ans

# 优化,不循环调用子函数,而是建立层数循环,去ans中查值
# 可以充分利用杨辉三角的对称性减少计算
class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        ans = []
        for i in range(0, numRows):
            n = [None for _ in range(i+1)]
            for j in range(i+1):
                #  1 4 6 4 1
                # j = 1  j=3  i = 4
                if j==0 or j==i:
                    n[j] = 1
                else:
                    n[j] = ans[i-1][j-1] + ans[i-1][j]
        
            ans.append(n)
        return ans

119. 杨辉三角 ||

方法:

  1. 数学二项式解题
    Leetcode简单题总结_第11张图片
  2. 根据上一行的列表,生成当前行列表
# 方法一
class Solution:
    def getRow(self, rowIndex: int) -> List[int]:
        res = [1]
        for i in range(1, rowIndex + 1):
            res.append(int(res[i - 1] * (rowIndex - i + 1) / i))
        return res

# 方法二
class Solution:
    def getRow(self, rowIndex: int) -> List[int]:

        def getk(n):
            ans = []
            for i in range(1,len(n)):
                ans.append(n[i]+n[i-1])
            return [1]+ans+[1]
        
        if rowIndex==0:
            return [1]
        n = []
        for i in range(rowIndex):
            print(n)
            n = getk(n)
        return n

1232. 缀点成线

直线的表示方式有两种:

  1. 一般式:Ax+By+C=0(AB≠0)。为方便后续计算,将所有点向 (-P0x, -P0y)方向平移,即将P0移到原点,则根据两点确定一条直线,P0和P1构成了一条穿过原点的直线,只需要检查其他点是否在该直线上。
  2. 两点式,如果直线过定点(x1,y1) (x2,y2),则对于直线上的点(x,y)满足(y-y1)/(x-x1)=(y-y2)/(x-x2),为了避免(x-x1)、(x-x2)为零,将除法转换为乘法,选择P0、P1为定点。
# 方法一
class Solution:
    def checkStraightLine(self, coordinates: List[List[int]]) -> bool:
        n = len(coordinates)
        for i in range(1,n):
            coordinates[i][0] = coordinates[i][0] - coordinates[0][0]
            coordinates[i][1] = coordinates[i][1] - coordinates[0][1]
        # Ax+BY=0  ---->y = kx
        A = coordinates[1][1]
        B = -coordinates[1][0]

        for i in range(2,n):
            if A*coordinates[i][0] + B*coordinates[i][1]!=0:
                return False        
        return True
# 方法二
class Solution:
    def checkStraightLine(self, coordinates: List[List[int]]) -> bool:
        for i in range(2, len(coordinates)):
            if (coordinates[i][1]-coordinates[0][1])*(coordinates[1][0]-coordinates[0][0]) != (coordinates[i][0]-coordinates[0][0])*(coordinates[1][1]- coordinates[0][1]):
                return False
        
        return True

图论

遍历:深度优先遍历、广度优先遍历、并查集

399. 除法求值 middle

from collections import defaultdict

class Solution:
    def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
        #创建结果list
        final_result = []
        #先创建图
        self.graph = defaultdict(dict)
        for (x,y),val in zip(equations,values):
            self.graph[x][y] = val
            self.graph[y][x] = 1/val
        # DFS
        # for i in queries:
        #     start, end = i[0], i[1]
        #     visited = set()
        #     final_result.append(self.dfs(start, end, visited))
        # BFS 广度优先的难度就在于如何计算start与每个位置的值,从任一起点,遍历所有该起点能达到的位置,并计算权重更新有向图
        print(self.graph)
        for i in self.graph:
            visited = set()
            queue = [i]
            weight = [1]
            while queue:
                node = queue.pop() # list.pop()返回list的第一个元素
                w = weight.pop()  # 从i到当前pop结点的权重  
                visited.add(node)
                length = len(self.graph[node])
                for j in self.graph[node]:
                    if j not in visited:
                        visited.add(j)
                        queue.append(j)
                        self.graph[i][j] = w*self.graph[node][j]
                        weight.append(self.graph[i][j])
        for i in queries:
            start, end = i[0], i[1]
            if start in self.graph:
                if start == end:
                    final_result.append(1)
                elif end in self.graph[start]:
                    final_result.append(self.graph[start][end])
                else:
                    final_result.append(-1)
            else:
                final_result.append(-1)
        return final_result


    def dfs(self, start, end, visited):
        if start not in self.graph:  # 如果图中不存在以start为起点,则直接返回-1
            return -1
        if start == end:    return 1  # 如果终点和起点在一个位置,则直接返回1
        visited.add(start)
        # 否则按DFS策略,遍历与start相邻的每个结点,因为如果a->b,那么存在有向边b->a,则无法求解,所以使用visited保存每个被访问过的结点,避免重复访问。
        for i in self.graph[start]:
            if i == end:
                return self.graph[start][i]
            elif i not in visited:
                visited.add(i)
                v = self.dfs(i, end, visited)
                if v != -1:
                    return self.graph[start][i] * v
        return -1

547. 省份数量

class Solution:
    def findCircleNum(self, isConnected: List[List[int]]) -> int:
        city = len(isConnected)
        total = city
        parent = {
     }
        for i in range(city):
            if i not in parent:
                parent[i] = None
    
        def find(index):
            while parent[index] != None:
                index = parent[index]
            return index
        
        def union(index1, index2):
            m = find(index1)
            n = find(index2)
            if m != n:
                parent[m] = n

        for i in range(city):
            for j in range(i+1, city):
                if isConnected[i][j]:
                    union(i,j)
                    total = total - 1
        # 可以查看根节点为None的city数目,标识了省份;也可以记录初始连通数目,每次执行合并,数目减一
        num = sum([parent[i]==None for i in range(city)]) 

        return total

684. 冗余连接 middle

方法:

  1. 并查集可以解决连通域问题,当union两个元素时,发现已经属于同一个根节点,则对应两元素的边为冗余连接。
  2. DFS或者BFS,首先建图,当遍历的点被访问过,则连接两元素的边属于冗余连接。

并查集在建立parents时,通常选择两种,一种是均为None,最终parents为None的结点表示根节点;另一种是[0,1,2,3,4]与下标和结点对应,满足index = parents[index]的节点为根节点。

在本题中结点为1,2,3,…,N,在读入时人为地为结点减一,变为0,1,2,…,N,与parents中[0,1,2,…]对齐,在输出时将结点序号修正。

# 并查集 DFS BFS都可以,并查集可以处理连通分量问题,更简单
# DFS BFS需要根据给定edges建图
from collections import defaultdict
class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        # 建图
        # graph = defaultdict(set)
        # for i in edges:
        #     (m,n) = i
        #     graph[m].add(n)
        #     graph[n].add(m)

        ans = [] # 存储二维数组中最后出现的边
        length = set()
        for i in edges:
            (m,n) = i
            length.add(m)
            length.add(n)
        parents = [i for i in range(len(length))]

        def find(index):
            while index != parents[index]:
                index = parents[index]
            return index

        def union(index1, index2):
            mm = find(index1)
            nn = find(index2)
            if mm!=nn:
                parents[mm] = nn
            else:
                ans[:] = [index1+1, index2+1]  # 每次合并重复,即出现重复冗余连接

        for i in edges:
            union(i[0]-1, i[1]-1)

        return ans

上一种方法执行时间较长并且在序号对齐过程中,容易出错。使用None,则会避免序号对齐的麻烦,只需要将parents长度置为length+1,对[1:length]进行操作。

from collections import defaultdict
class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        ans = [] # 存储二维数组中最后出现的边
        length = set()
        for i in edges:
            (m,n) = i
            length.add(m)
            length.add(n)
        parents = [None for _ in range(len(length)+1)]

        def find(index):
            while None != parents[index]:
                index = parents[index]
            return index

        def union(index1, index2):
            mm = find(index1)
            nn = find(index2)
            if mm!=nn:
                parents[mm] = nn
            else:
                ans[:] = [index1, index2]  # 每次合并重复,即出现重复冗余连接

        for i in edges:
            union(i[0], i[1])

        return ans

947. 移除最多的同行或同列石头

题目含义是:如果想移除一个石头,那么它所在的行或者列必须有其他石头存在。我们能移除的最多石头数是多少?也就是说,只有一个石头在连通分量中,才能被移除。对于连通分量而言,最理想的状态是只剩一块石头。对于任何容量为n一个连通分量,可以移除的石头数都为n-1。

一定可以把一个连通图里的所有顶点根据这个规则删到只剩下一个顶点;故最多可以移除的石头的个数 = 所有石头的个数 - 连通分量的个数。

# 并查集
from collections import defaultdict
class Solution:
    def removeStones(self, stones: List[List[int]]) -> int:
        dict_x = defaultdict(set)
        dict_y = defaultdict(set)
        for i in range(len(stones)):
            x,y = stones[i]
            dict_x[x].add(i)
            dict_y[y].add(i)

        def find(index):
            while parents[index]!=None:
                index = parents[index]
            return index
        
        def union(index1, index2):
            m = find(index1)
            n = find(index2)
            if m!=n:
                parents[m] = n

        n = len(stones)
        parents = [None for _ in range(n)]


        for i in range(n):
            x,y = stones[i]
            for j in dict_x[x]:
                union(i,j)
            for k in dict_y[y]:
                union(i,k)            

        h = 0
        for i in parents:
            if i==None:
                h = h+1
        return n-h
       
# 深度优先搜索
class Solution:
    def removeStones(self, stones: List[List[int]]) -> int:
        dict_X = collections.defaultdict(list)
        dict_Y = collections.defaultdict(list)
        for x, y in stones:
            dict_X[x].append((x, y))
            dict_Y[y].append((x, y))
        visited = set()

        def dfs(node):
            if node in visited:
                return
            visited.add(node)
            x, y = node
            for i in dict_X[x]:
                dfs(i)
            for i in dict_Y[y]:
                dfs(i)
        
        ans = 0
        for i in stones:
            i = tuple(i)
            if i not in visited:
                ans += 1
                dfs(i)
        
        return len(stones) - ans

1202. 交换字符串的元素 middle

# 任意多次交换,建图,通过DFS或者BFS遍历图,建立连通分量
class Solution:
    def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str:
        from collections import defaultdict
        length = len(s)
        ans = [0]*length
        graph = defaultdict(set)
        for (i,j) in pairs:
            graph[i].add(j)
            graph[j].add(i)

        def dfs(node, visited, ret):
            for i in graph[node]:
                if i not in visited:
                    visited.add(i)
                    ret.add(i)
                    dfs(i, visited, ret)

        visited = set()
        for i in range(len(s)):
            ret = set()
            if i not in visited:
                visited.add(i)
                ret.add(i)
                dfs(i, visited, ret)

                ret_index = sorted(ret)
                str_index = sorted([s[j] for j in ret_index])
                for h,l in zip(ret_index, str_index):
                    ans[h] = l
        return ''.join(ans)

你可能感兴趣的:(Leetcode)