二分法
704.二分法查找(找到目标值,不在返回-1)
看到while(left<right)不表示搜索区间为[左闭右开],这一点没有根据,
只看到while(left<right)里的<,不能说明右边界不能取到,
在区间里有偶数个元素的时候,mid = (left + right) / 2只能取到位于左边的中间数
,要想取到位于右边的中间数,就要在括号里加1.之所以要取右边的中间数是因为,
偶数的时候,右边的中间数能把搜索区间缩小。
重点:
写成while(left < right) ,退出循环的时候有 left == right 成立,好处是:
不用判断应该返回 left 还是 right。
class Solution:
def search(self, nums: List[int], target: int) -> int:
l = 0
r = len(nums)-1
while l < r:
mid = (l + r + 1)//2
if nums[mid] == target:
return l
elif target < nums[mid]:
r = mid -1
else:
l = mid + 1
return -1
这里会显示有错,while 没有取=,没有通过
278.第一个错误的版本
class Solution:
def firstBadVersion(self, n):
l, r = 1, n-1
#这里应该是 l <= r 才能使l, r逼近同一个值
while l <= r:
mid = (l + r + 1) // 2
if isBadVersion(mid):
#如果说该mid是错误版本,那么应该向前查找
r = mid - 1
else:
#如果说该mid是正确版本,那么应该向后查找
l = mid + 1
#最后会逼近到第一个错误版本号l
return l
35.搜索插入的位置(找到目标值,并返回索引,不在里面就插入)
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
l = 0
r = len(nums) - 1
while l <= r:
mid = (l + r + 1)//2
if nums[mid] == target:
return mid
elif nums[mid] < target:
l = mid + 1
else:
r = mid - 1
return l
和上上一题很像,上上一题不在的时候返回-1,这里选到最后没有找到,就在最后选到的地方插入,由于左右都是一样的,因为重合了,所以返回哪个都一样
双指针
977.有序数组的平方,给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
由于这些数字是有序的,
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
n = len(nums)
ans = [0] * n #构建同样长度的0数组,存放数据
# 从两端开始遍历, 找到平方最大元素放到数组末尾,始终将两端的乘方相比较,放到新数组的最后一个
i, j, pos = 0, n - 1, n - 1
while i <= j:
if nums[i] * nums[i] > nums[j] * nums[j]:
ans[pos] = nums[i] * nums[i]
i += 1
else:
ans[pos] = nums[j] * nums[j]
j -= 1
pos -= 1
return ans
##还有一个最简单的方法是用python的内置函数直接sort
189.轮转数组
轮转数组也有最简单的python方法,当轮转的长度为数组长度的整数倍时相当于没有动,这里用到切片操作
简写就是
n = len(nums)
k = k % n
nums[:] = nums[n - k:] + nums[:n - k] #nums[:]表示里面所有的元素
283.移动零;将0全部移动到末尾,同时保持非0元素的位置。不能拷贝额外的数组,必须在原数组上操作,并且减少操作次数。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# l = 0 #这里需要快慢指针 不是左右指针
# r = len(nums) - 1
# while l < = r:
# if nums[l] == 0:
n = len(nums)
left = right = 0 # 初始化左指针和右指针均指向列表的开头
while right < n: #右指针不为0就将左右互换,有指针为0就向前移动一个
if nums[right] != 0:
nums[left],nums[right] = nums[right],nums[left] #当没有遇见0的时候换了等于没换
left += 1
right += 1 #这里如果右边为0就向前移动一个
167.两数之和,输入有效的数组
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
l = 0
r = len(numbers) - 1
while l <= r:
if numbers[l] + numbers[r] == target:
return [l+1,r+1]
elif numbers[l] + numbers[r] < target:
l += 1
else:
r -= 1
#这个太简单。又是偷懒模式,最后要求返回指定的索引,明天开始,不能只做简单提了
344.反转字符串
easy:
left,right = 0,len(s)-1
while left < right:
s[left],s[right] = s[right],s[left]
left += 1
right -= 1
交换然后都向中间挪
557.反转字符串中的单词3,注意是字符串的单词,python内置函数真实yyds,再次作弊
class Solution(object):
def reverseWords(self, s):
return " ".join(word[::-1] for word in s.split(" "))
#先空格分割,然后遍历分割之后的列表,对列表里面的每个元素(字符串)逆操作,之后join合并
876.链表的中间节点,
给定一个头结点为 head
的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点
开始均指向链表头节点,然后每次快节点走两步,慢指针走一步,直至快指针指向 null,此时慢节点刚好来到链表的下中节点。
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
slow = fast = head
while fast and fast.next: # 前者是偶数长度终止条件,后者是奇数长度终止条件
slow = slow.next # 移动一步
fast = fast.next.next # 移动两步
return slow
19.删除链表的倒数第n个节点
在原有的链表前面加上一个哑节点,这里设置两个指针,
哑结点的好处在于,因为这里我们是要删除一个结点,所以我们可以定位到被删除结点的前置结点,然后将前置结点的后续指针指向被删除结点的后续结点,则可完成删除。
我们设置两个指针,两个指针初始状态都指向哑结点,指针fast 先走n步,然后指针fast和指针slow同步往前继续遍历链表,直至fast的后续结点为空,此时指针slow到达被删除结点的前置结点
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
if not head:
return head
slownode = ListNode(None)#创建一个初始值为none的空节点
slownode.next = head
fastnode = slownode
for i in range(n):
fastnode = fastnode.next
while(fastnode.next!=None):
slownode = slownode.next
fastnode = fastnode.next
if slownode.next == head:#如果要剔除的就是头节点
head = head.next
else:
slownode.next = slownode.next.next
return head
滑动窗口
3.无重复字符的最长字串,返回长度,看着esay,可惜有些测试用例没有通过
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
# ans = ""#定义两个空字符串
# temp = ""
# for i in s:#不在里面的就添加,
# if i not in temp:
# temp += i
# else:#可能添加了会有冗余,这时开始收缩
# temp = temp[temp.index(i)+1:] # 用到py的内置函数,字符串.index()会返回一个索引位置。
# #不止字符串,列表也用到index
# temp += i#在前面去掉之后在后面加上
# if len(temp)>len(ans): # 去比较窗口里面和最终结果的关系,进行替换
# ans = temp
# return len(ans)
# if len(s)==0:
# return 0
567.字符串的排列
广度优先和深度优先
733.图像渲染
695.岛屿的最大面积
617.合并二叉树
116.填充每个节点的下一个
542.01矩阵
994.腐烂的橘子
递归/回溯
21.合并两个有序链表
206.反转链表
77.组合
784.字母大小写全排列
class Solution:
def letterCasePermutation(self, S: str) -> List[str]:
if not S:
return ['']
self.res=[]
self.dfs(S,0,'')
return self.res
def dfs(self,S,i,temp):
if i==len(S):
self.res.append(temp)
return
if S[i].isdigit():
self.dfs(S,i+1,temp+S[i])
else:
self.dfs(S,i+1,temp+S[i].lower())
self.dfs(S,i+1,temp+S[i].upper())
46.全排列
回溯法的三个基本要素:
路径:已经做出的选择;
选择列表:当前可以做出的选择;
结束条件:结束一次回溯算法的条件,即遍历到决策树的叶节点
# 回溯算法,复杂度较高,因为回溯算法就是暴力穷举,遍历整颗决策树是不可避免的
结果 = []
def backtrack(路径, 选择列表):
if 满足结束条件:
结果.append(路径)
return
for 选择 in 选择列表: # 核心代码段
做出选择
递归执行backtrack
撤销选择
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
# 回溯算法,复杂度较高,因为回溯算法就是暴力穷举,遍历整颗决策树是不可避免的
res = []
def backtrack(path=[], selects=nums):
if not selects:
res.append(path[:])
return
for i in range(len(selects)):
path.append(selects[i])
backtrack(path, selects[:i] + selects[i+1:])#在选择列表里除去 这次被选择的selected[i] ,然后递归
path.pop()
backtrack()
return res
backtrack会被调用N!次,O(N!):这部分的复杂度是回溯算法不可避免的,最开始for循环N次,随递归的进行,每次递归循环减1次,N*(N-1)(N-2)...*1 = N!);
除了backtrack的调用,还包括列表的切片操作,O(N)
空间复杂度:O(N),取决于结果列表和递归栈深度。
动态规划
70.爬楼梯
198.打家劫舍
120.三角形最小路径和
位运算:
190.颠倒二位进制
136.只出现一次的数字