双指针:
# 双指针法
# 方法一:直接快慢指针,对于快指针搜索不为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
主要方法有:
对于方法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中搜索。
# 双指针 从初始位置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
底子依旧是双指针问题,只需要写出在每个循环中求解问题的子函数,就可以通过循环调用子函数来解决问题。在子函数中使用双指针来取连续相同元素的数目。
双指针遍历有一个问题,当判断条件为相邻元素差异的时候,对序列x,需要比较x[i]与x[i+1],首先x+1存在溢出风险,需要限定x+1
取消for循环,用end遍历,依次加1,循环条件为while end 总结: 同38. 外观数列 利用双指针从前到后进行归并排序。 !:可以使用双指针从后向前,避免了开辟额外空间存储数组。因为已知nums1的长度为m+n,直接逆序存储元素,可以直接插入。 只要考察对于语言字符串相关API的应用与理解: capitalize() center(width, fillchar) count(str, beg= 0,end=len(string)) encode(encoding=‘UTF-8’,errors=‘strict’) endswith(suffix, beg=0, end=len(string)) find(str, beg=0, end=len(string)) index(str, beg=0, end=len(string)) isalnum() isalpha() isdigit() islower() isnumeric() isspace() isupper() join(seq) len(string) ljust(width[, fillchar]) lower() lstrip() maketrans() max(str) min(str) replace(old, new [, max]) rfind(str, beg=0,end=len(string)) rindex( str, beg=0, end=len(string)) rjust(width,[, fillchar]) rstrip() split(str="", num=string.count(str)) splitlines([keepends]) startswith(substr, beg=0,end=len(string)) strip([chars]) swapcase() translate(table, deletechars="") upper() zfill (width) isdecimal() 方法: 解法: 双指针: 探究几个最常用的二分查找场景:寻找一个数、寻找左侧边界、寻找右侧边界。而且,我们就是要深入细节,比如不等号是否应该带等号,mid 是否应该加一等等。分析这些细节的差异以及出现这些差异的原因,保证你能灵活准确地写出正确的二分查找算法。 方法: 方法: 方法: 考察栈的基本概念,熟练掌握list(python)、数组(c/c++)的相关操作,可以轻松解决该简单题。问题的难度在于如何在常数时间内检索到最小元素的栈。自然地,我们如果能够在每次push新元素进入时,保存当前栈状态下对应的最小元素;那么当我们查询最小元素时,可以直接返回最小元素值。 建立辅助栈minStack来存储最小元素,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。当push新元素x进入Stack时,min(minStack[-1],x)为当前栈状态下的最小元素,push进minStack中。 同样的思路,我们可以不使用两个栈来进行操作,而只使用一个栈来保存。当有更小的值来的时候,我们只需要把之前的最小值入栈,当前更小的值再入栈即可。当这个最小值要出栈的时候,下一个值便是之前的最小值了。 这种方法会使用额外的空间来存储,怎么样能不使用额外空间? 考虑对于基础数学加法原则和数组知识的考察,比较简单。 方法: 直观地看,我们可以选择中间数字作为二叉搜索树的根节点,这样分给左右子树的数字个数相同或只相差 11,可以使得树保持平衡。如果数组长度是奇数,则根节点的选择是唯一的,如果数组长度是偶数,则可以选择中间位置左边的数字作为根节点或者选择中间位置右边的数字作为根节点,选择不同的数字作为根节点则创建的平衡二叉搜索树也是不同的。 确定平衡二叉搜索树的根节点之后,其余的数字分别位于平衡二叉搜索树的左子树和右子树中,左子树和右子树分别也是平衡二叉搜索树,因此可以通过递归的方式创建平衡二叉搜索树。 递归的基准情形是平衡二叉搜索树不包含任何数字,此时平衡二叉搜索树为空。 查找到叶子节点的路径和,可以使用DFS或者BFS。 在使用DFS中,需要记录到当前位置的和sum,再遍历左右子树,遍历左子树之后,去遍历右子树的时候很自然地希望当前位置的和依旧为sum。 使用广度优先搜索BFS的方式,记录从根节点到当前节点的路径和,以防止重复计算。这样我们使用两个队列,分别存储将要遍历的节点,以及根节点到这些节点的路径和即可。 DFS: BFS: 方法: 代码: 方法: 与55. 最大子序和相同类似 暴力解法直接n个x相乘,时间复杂度为O(n) 方法: 直线的表示方式有两种: 方法: 并查集在建立parents时,通常选择两种,一种是均为None,最终parents为None的结点表示根节点;另一种是[0,1,2,3,4]与下标和结点对应,满足index = parents[index]的节点为根节点。 在本题中结点为1,2,3,…,N,在读入时人为地为结点减一,变为0,1,2,…,N,与parents中[0,1,2,…]对齐,在输出时将结点序号修正。 上一种方法执行时间较长并且在序号对齐过程中,容易出错。使用None,则会避免序号对齐的麻烦,只需要将parents长度置为length+1,对[1:length]进行操作。 题目含义是:如果想移除一个石头,那么它所在的行或者列必须有其他石头存在。我们能移除的最多石头数是多少?也就是说,只有一个石头在连通分量中,才能被移除。对于连通分量而言,最理想的状态是只剩一块石头。对于任何容量为n一个连通分量,可以移除的石头数都为n-1。 一定可以把一个连通图里的所有顶点根据这个规则删到只剩下一个顶点;故最多可以移除的石头的个数 = 所有石头的个数 - 连通分量的个数。
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. 最后一个单词的长度
# 求最后一个单词的长度,直接逆序双指针
# 先循环过滤掉' ',确定单词的终止位置
# 再循环单元字符,确定单词的开始位置,直接返回
# 如果正序双指针,需要移除字符串末尾的' '
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. 合并两个有序数组
题目中给出了有效数字的概念,牢记指针边界与有效数字位数的关系,比较简单的一道排序问题。# 从前向后的双指针
# 直接双指针的归并排序,当一个遍历结束,剩下的全部加到队列末尾
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. 验证回文串
将字符串的第一个字符转换为大写
返回一个指定的宽度 width 居中的字符串,fillchar 为填充的字符,默认为空格。
返回 str 在 string 里面出现的次数,如果 beg 或者 end 指定则返回指定范围内 str 出现的次数
以 encoding 指定的编码格式编码字符串,如果出错默认报一个ValueError 的异常,除非 errors 指定的是’ignore’或者’replace’
检查字符串是否以 obj 结束,如果beg 或者 end 指定则检查指定的范围内是否以 obj 结束,如果是,返回 True,否则返回 False.
检测 str 是否包含在字符串中,如果指定范围 beg 和 end ,则检查是否包含在指定范围内,如果包含返回开始的索引值,否则返回-1
跟find()方法一样,只不过如果str不在字符串中会报一个异常。
如果字符串至少有一个字符并且所有字符都是字母或数字则返 回 True,否则返回 False
如果字符串至少有一个字符并且所有字符都是字母或中文字则返回 True, 否则返回 False
如果字符串只包含数字则返回 True 否则返回 False…
如果字符串中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是小写,则返回 True,否则返回 False
如果字符串中只包含数字字符,则返回 True,否则返回 False
如果字符串中只包含空白,则返回 True,否则返回 False.
如果字符串中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是大写,则返回 True,否则返回 False
以指定字符串作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的字符串
返回字符串长度
返回一个原字符串左对齐,并使用 fillchar 填充至长度 width 的新字符串,fillchar 默认为空格。
转换字符串中所有大写字符为小写.
截掉字符串左边的空格或指定字符。
创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。
返回字符串 str 中最大的字母。
返回字符串 str 中最小的字母。
将字符串中的 old 替换成 new,如果 max 指定,则替换不超过 max 次。
类似于 find()函数,不过是从右边开始查找.
类似于 index(),不过是从右边开始.
返回一个原字符串右对齐,并使用fillchar(默认空格)填充至长度 width 的新字符串
删除字符串字符串末尾的空格.
以 str 为分隔符截取字符串,如果 num 有指定值,则仅截取 num+1 个子字符串
按照行(’\r’, ‘\r\n’, \n’)分隔,返回一个包含各行作为元素的列表,如果参数 keepends 为 False,不包含换行符,如果为 True,则保留换行符。
检查字符串是否是以指定子字符串 substr 开头,是则返回 True,否则返回 False。如果beg 和 end 指定值,则在指定范围内检查。
在字符串上执行 lstrip()和 rstrip()
将字符串中大写转换为小写,小写转换为大写
根据 str 给出的表(包含 256 个字符)转换 string 的字符, 要过滤掉的字符放到 deletechars 参数中
转换字符串中的小写字母为大写
返回长度为 width 的字符串,原字符串右对齐,前面填充0
检查字符串是否只包含十进制字符,如果是返回 true,否则返回 false。
# 方法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. 汇总区间
# 必须要遍历到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
查找及插入问题
二分查找及其变种
二分查找的核心思想是「减而治之」,即「不断缩小问题规模」。
二分查找的两种思路:
while(left <= right) 这种写法表示在循环体内部直接查找元素;
退出循环的时候 left 和 right 不重合,区间 [left, right] 是空区间。
while(left < right) 这种写法表示在循环体内部排除元素;
退出循环的时候 left 和 right 重合,区间 [left, right] 只剩下成 11 个元素,这个元素 有可能 就是我们要找的元素。
链接: Leetcode.
链接: Leetcode.
# 模板 ...表示需要注意的地方
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 ...;
}
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;
}
// 寻找左边界的关键其实是寻找当前列表中多少数小于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;
}
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. 搜索插入位置
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的平方根
# 方法一
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. 二分查找
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. 山脉数组的峰顶索引
# 方法一
// 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. 最小栈
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。
入栈 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. 二进制求和
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. 相交链表
当 pA到达链表的尾部时,将它重定位到链表 B 的头结点; 类似的,当 pB到达链表的尾部时,将它重定位到链表 A 的头结点。
依然满足循环终止条件# 忽略了可以通过链接解决长度差问题,保证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. 将有序数组转换为二叉搜索树(递归)
树的遍历的终止条件,通常判定当前node为空,即为叶子节点的左右子树;而不是在叶子节点终止。
方法:
# 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. 路径总和
在实际代码中方法:
# 询问是否有从「根节点」到某个「叶子节点」经过的路径上的节点之和等于目标和。
#核心思想是对树进行一次遍历,在遍历时记录从根节点到当前节点的路径和,以防止重复计算。
# 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)
# 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. 最大子序和
暴力解法的思路,第一层for 就是设置起始位置,第二层for循环遍历数组寻找最大值
首先永远从非负整数开始,在当前元素加到sum的时候,保留最大值;当sum小于0时,在之后不可能出现最大子序列和,将sum置0.
问题演化为求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。# 暴力求解,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. 爬楼梯
# 动态规划,设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. 买卖股票的最佳时机 |
方法:
# 暴力法无法AC,样例测试超市
// A code block
var foo = 'bar';
# 贪心+双指针:一旦出现负数收益,即出现了更小的买入价格,更新买入价格
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
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. 二进制求和
运算过程:
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
快速幂算法: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. 杨辉三角 |
# 给定一层的列表,建立子函数,求解下一层的杨辉三角
# 标定第一层和第二层的特殊值,检查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. 杨辉三角 ||
# 方法一
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. 缀点成线
# 方法一
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
# 并查集 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
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. 移除最多的同行或同列石头
# 并查集
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)