简单算法
1. 数组
1. 双指针
删除排序数组中的重复项:双指针一快一慢,慢指针为最终输出数组
def removeDuplicates(self, nums: List[int]) -> int:
l = len(nums)
i, j = 0, 1
if l < 2:return l;
for a in range(l-1):
if nums[i] != nums[j]:
i += 1
nums[i] = nums[j]
j +=1
nums = nums[0:i+1]
return len(nums);
2. 贪心算法
问题求解时,总是做出目前最优的方案
def maxProfit(self, prices: List[int]) -> int:
income = 0
for i in range(1,len(prices)):
income += max(0, prices[i]-prices[i-1])
return income
3. 旋转数组
1. 暴力法
每次移动一个元素
def rotate(self, nums: List[int], k: int) -> None:
temp ,previous = 0, 0
for i in range(k):
previous = nums[len(nums) - 1]
for j in range(len(nums)):
temp = nums[j]
nums[j] = previous
previous = temp
return nums
时间复杂度:O(n*k) 空间复杂度O(1)
2. 反转
当我们旋转数组 k 次, *k%n* 个尾部元素会被移动到头部,剩下的元素会被向后移动
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k %= n
nums[:] = nums[::-1] #反转整个数组
nums[:k] = nums[:k][::-1] #反转前k个元素
nums[k:] = nums[k:][::-1] #反转剩下的元素
时间复杂度:O(n) 空间复杂度O(1)
3. 环状替代
数组内元素的移动可以形成一个或多个闭环,只需一次遍历即可
def rotate(self, nums: List[int], k: int) -> None:
count,index,temp = 0,0,nums[0]
done_index = [0]
while count < len(nums):
count,target = count+1, (index + k) % len(nums)
temp,nums[target] = nums[target],temp
if target not in done_index:
index = target
elif target + 1 < len(nums):
index,temp = target + 1,nums[target + 1]
done_index.append(index)
时间复杂度:O(n) 空间复杂度O(1)
4. 存在重复元素
1. 朴素线性查找
依次逐个检查列表中的元素,直到找到满足的元素
def containsDuplicate(self, nums: List[int]) -> bool:
for i in range(len(nums)):
for j in range(i):
if nums[j] == nums[i]:
return True
return False
时间复杂度 : O(n2) 空间复杂度 : O(1)
2. 排序
将数组排列后,扫描是否有连续的重复元素
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
时间复杂度 : O(n log n) 空间复杂度:O(1)
3. 哈希表
利用支持快速搜索和插入操作的动态数据结构。
def containsDuplicate(self, nums: List[int]) -> bool:
return len(nums) != len(set(nums)) #set()可以自动去重,自动排序
时间复杂度:O(n) 空间复杂度:O(n)
5. 只出现一次的数字(重复两次)
1. 位运算
数组中的全部元素的异或运算结果即为数组中只出现一次的数字 eg:6 ^4 = 4^6
def singleNumber(self, nums: List[int]) -> int:
return reduce(lambda x, y: x ^ y, nums)
时间复杂度:O(n) 空间复杂度:O(1)
2.排序
def singleNumber(nums):
if len(nums)==1: #如果数组长度为1,则直接返回即可
return nums[0]
nums.sort() #对数组进行排序,使其相同元素靠在一起
for i in range(1,len(nums),2): #循环数组,验证前后是否相同,由于原始出现两次,因此可跳跃判断
if nums[i-1] != nums[i]:
return nums[i-1]
if (i+2) == len(nums): #判断单一元素在排序后数组的最后面
return nums[-1]
3.删除元素
依次删除列表的元素
def singleNumber(nums):
while True:
d = nums[0]
nums.remove(d)
try:
nums.remove(d)
except:
return d
6. 两个数组的交集
1.哈希表
用哈希表存储每个数字出现的次数
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1)
m = collections.Counter()
for num in nums1:
m[num] += 1
intersection = list()
for num in nums2:
if (count := m.get(num, 0)) > 0:
intersection.append(num)
m[num] -= 1
if m[num] == 0:
m.pop(num)
return intersection
时间复杂度:O(m+n) 空间复杂度:O(min(m,n))
2. 双指针
先对两个数组进行排序,然后使用两个指针遍历两个数组。
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
length1, length2 = len(nums1), len(nums2)
intersection = list()
index1 = index2 = 0
while index1 < length1 and index2 < length2:
if nums1[index1] < nums2[index2]:
index1 += 1
elif nums1[index1] > nums2[index2]:
index2 += 1
else:
intersection.append(nums1[index1])
index1 += 1
index2 += 1
return intersection
时间复杂度:O(m log m+n log n) 空间复杂度:O(min(m,n))
7. 数组最后一位加一,每个元素为 0-9
1.数组合成数字
def plusOne(digits) :
nums_s = ''.join([str(x) for x in digits]) #先将其数组中的值转换为字符串,然后进行拼接
nums = str(int(nums_s) + 1)
r = [int(x) for x in nums]
return [0]*(len(digits)-len(r)) + r #数组前面可能存在多个0,防止这种情况出现
2.从数组尾部遍历
def plusOne(self, digits: List[int]) -> List[int]:
for i in range(len(digits),0,-1): #循环数组,从最后一位开始计算
if digits[i-1] == 9: #如果为9,则相加为十,需要向前进一位
digits[i-1] = 0
if i == 1 : #如果满十进位到数组第一个,则数组需要增加一位
digits = [1] + digits
else:
digits[i-1] += 1 #不等于9就终止循环结束
break
return digits
8. 移动数组中零
def moveZeroes(self, nums: List[int]) -> None:
i = 0
for a in range(len(nums)):
if nums[i] == 0:
del nums[i]
nums.append(0)
else:
i += 1
return nums
9.两数之和
哈希表
创建一个哈希表,对于每一个x,我们首先查询哈希表中是否存在target - x,然后将x插入到哈希表中
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashtable = dict()
for i, num in enumerate(nums):
if target - num in hashtable:
return [hashtable[target - num], i]
hashtable[nums[i]] = i
return []
10. 有效的数独
def isValidSudoku(self, board: List[List[str]]) -> bool:
row = [{} for _ in range(9)]
col = [{} for _ in range(9)]
grid = [[{} for _ in range(3)] for _ in range(3)]
for i in range(9):
for j in range(9):
if board[i][j] != '.':
tmp = int(board[i][j])
row[i][tmp] = row[i].get(tmp, 0) + 1 #get("位置", “不存在时返回值”)
col[j][tmp] = col[j].get(tmp, 0) + 1
grid[i//3][j//3][tmp] = grid[i//3][j//3].get(tmp, 0) + 1
if row[i].get(tmp) > 1 or col[j].get(tmp) > 1 or grid[i//3][j//3].get(tmp) > 1:
return False
return True
11.旋转图像
![image.png](https://i-blog.csdnimg.cn/blog_migrate/a56f5b17405ed7e82443e491a5a15728.png)
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix[0])
for i in range(n // 2 + n % 2):
for j in range(n // 2):
tmp = matrix[n - 1 - j][i] #左上角
matrix[n - 1 - j][i] = matrix[n - 1 - i][n - j - 1] #右上角
matrix[n - 1 - i][n - j - 1] = matrix[j][n - 1 -i] #右下角
matrix[j][n - 1 - i] = matrix[i][j] #左下角
matrix[i][j] = tmp #左上角
2.字符串
1. 反转字符串
def reverseString(self, s: List[str]) -> None:
for i in range(len(s),len(s)//2,-1):
s[i-1],s[len(s)-i] = s[len(s)-i],s[i-1]
def reverseString(self, s: List[str]) -> None:
s.reverse()
def reverseString(self, s: List[str]) -> None:
s = s[::-1]
2. 整数反转
将整数转换为字符串
def reverse(self, x: int) -> int:
s = str(x)
if s[0] == '-':
x = int('-' + s[1:][::-1])
else:
x = int(s[::-1])
if (-2147483648 <= x <= 2147483647):
return x
else:
return 0
3. 字符串中的第一个唯一字符
def firstUniqChar(self, s: str) -> int:
count = collections.Counter(s) #统计s中元素出现频率
for idx, ch in enumerate(s): #idx 为s中内容的序号,ch为s中内容
if count[ch] == 1:
return idx
return -1
4. 有效的字母异位词
1.排序法
将字符串保存到容器,进行sort排序,然后依次对比
def isAnagram(self, s: str, t: str) -> bool:
if len(s) != len (t):
return False
a = 0
s_l = list(s)
t_l = list(t)
s_l.sort()
t_l.sort()
s_0 = "".join(s_l)
t_0 = "".join(t_l)
for i in range(len(s)):
if s_0[i] == t_0[i]:
a += 1
if a == len(s):
return True
else:
return False
2.哈希表
利用collections.Counter函数
def isAnagram(self, s: str, t: str) -> bool:
return collections.Counter(s) == collections.Counter(t)
5. 验证回文串
1.筛选判断
先筛选出所有字母与数字,并将字母全部小写,然后判断
def isPalindrome(self, s: str) -> bool:
sgood = "".join(ch.lower() for ch in s if ch.isalnum()) #lower()将字母小写,ch.isalnum()判断是否为字母与数字
return sgood == sgood[::-1]
3. 在原字符串上直接判断
利用双指针
def isPalindrome(self, s: str) -> bool:
n = len(s)
left, right = 0, n - 1
while left < right:
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if left < right:
if s[left].lower() != s[right].lower():
return False
left, right = left + 1, right - 1
return True
4. 字符串转换整数 (atoi)
1.正常遍历
class Solution:
def myAtoi(self, s: str) -> int:
i=0
n=len(s)
while i<n and s[i]==' ':
i=i+1
if n==0 or i==n:
return 0
flag=1
if s[i]=='-':
flag=-1
if s[i]=='+' or s[i]=='-':
i=i+1
INT_MAX=2**31-1
INT_MIN=-2**31
ans=0
while i<n and '0'<=s[i]<='9':
ans=ans*10+int(s[i])-int('0')
i+=1
if(ans-1>INT_MAX):
break
ans=ans*flag
if ans>INT_MAX:
return INT_MAX
return INT_MIN if ans<INT_MIN else ans
2.有限状态机
' ' | +/- | number | other | |
---|---|---|---|---|
start | start | signed | in_number | end |
signed | end | end | in_number | end |
in_number | end | end | in_number | end |
end | end | end | end | end |
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 get_col(self, c):
if c.isspace():
return 0
if c == '+' or c == '-':
return 1
if c.isdigit():
return 2
return 3
def get(self, c):
self.state = self.table[self.state][self.get_col(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, str: str) -> int:
automaton = Automaton()
for c in str:
automaton.get(c)
return automaton.sign * automaton.ans
4.正则表达
def myAtoi(self, str: str) -> int:
INT_MAX = 2147483647
INT_MIN = -2147483648
str = str.lstrip() #清除左边多余的空格
num_re = re.compile(r'^[\+\-]?\d+') #设置正则规则
num = num_re.findall(str) #查找匹配的内容
num = int(*num) #由于返回的是个列表,解包并且转换成整数
return max(min(num,INT_MAX),INT_MIN) #返回值
元字符 | 匹配内容 |
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
\b | 匹配一个单词的结尾 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结尾 |
\W | 匹配非字母或数字或下划线 |
\D | 匹配非数字 |
\S | 匹配非空白符 |
a|b | 匹配字符a或字符b |
() | 匹配括号内的表达式,也表示一个组 |
[...] | 匹配字符组中的字符 |
[^...] | 匹配除了字符组中字符的所有字符 |
量词 | 用法说明 |
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
5. 实现 strStr()
1.子串逐一比较 - 线性时间复杂度
沿着字符换逐步移动滑动窗口,将窗口内的子串与 needle 字符串比较。
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
for start in range(n - L + 1):
if haystack[start: start + L] == needle:
return start
return -1
2.双指针 - 线性时间复杂度
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
if L == 0:
return 0
pn = 0
while pn < n - L + 1:
while pn < n - L + 1 and haystack[pn] != needle[0]: # 与第一个字母不匹配
pn += 1
curr_len = pL = 0
while pL < L and pn < n and haystack[pn] == needle[pL]: # 与第一个字母匹配
pn += 1
pL += 1
curr_len += 1
# if the whole needle string is found,
# return its start position
if curr_len == L:
return pn - L
# otherwise, backtrack
pn = pn - curr_len + 1 # 出现不匹配字母
return -1
3。Rabin Karp - 常数复杂度
先生成窗口内子串的哈希码,然后再跟 needle 字符串的哈希码做比较。
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
if L > n:
return -1
# base value for the rolling hash function
a = 26
# modulus value for the rolling hash function to avoid overflow
modulus = 2**31
# lambda-function to convert character to integer
h_to_int = lambda i : ord(haystack[i]) - ord('a')
needle_to_int = lambda i : ord(needle[i]) - ord('a')
# compute the hash of strings haystack[:L], needle[:L]
h = ref_h = 0
for i in range(L):
h = (h * a + h_to_int(i)) % modulus
ref_h = (ref_h * a + needle_to_int(i)) % modulus
if h == ref_h:
return 0
# const value to be used often : a**L % modulus
aL = pow(a, L, modulus)
for start in range(1, n - L + 1):
# compute rolling hash in O(1) time
h = (h * a - h_to_int(start - 1) * aL + h_to_int(start + L - 1)) % modulus
if h == ref_h:
return start
h 0 = c 0 a L − 1 + c 1 a L − 2 + . . . + c L − 1 a 2 + c L a 1 h_0=c_0a^{L-1}+c_1a^{L-2}+...+c_{L-1}a^2+c_La^1 h0=c0aL−1+c1aL−2+...+cL−1a2+cLa1
h 1 = h 0 a − c 0 a L + c L + 1 h_1=h_0a-c_0a^L+c_{L+1} h1=h0a−c0aL+cL+1
6. 外观数列
1. 双指针实现
def countAndSay(self, n: int) -> str:
pre = ''
cur = '1'
# 从第 2 项开始
for _ in range(1, n):
# 这里注意要将 cur 赋值给 pre
# 因为当前项,就是下一项的前一项。有点绕,尝试理解下
pre = cur
# 这里 cur 初始化为空,重新拼接
cur = ''
# 定义双指针 start,end
start = 0
end = 0
# 开始遍历前一项,开始描述
while end < len(pre):
# 统计重复元素的次数,出现不同元素时,停止
# 记录出现的次数,
while end < len(pre) and pre[start] == pre[end]:
end += 1
# 元素出现次数与元素进行拼接
cur += str(end-start) + pre[start]
# 这里更新 start,开始记录下一个元素
start = end
return cur
2.正则表达式(实现):提取元素
def countAndSay(self, n: int) -> str:
if n == 1:
return "1"
s = self.countAndSay(n-1)
# 字符串 (\d)\1* 可以用来匹配结果。这里用来提取连在一块的元素, 如 '111221',提取出的元素是 res = ['111', '22', '1']。
# (\d)\1*解释:
# \1 是为了引用前面的 \d,表明 \1 是与 \d 匹配到相同的数字。
# 只有 \d 添加了(),才能被引用,在正则里面称之为捕获组。
# * 表示重复匹配前面字符0次或多次。所以可以匹配的到像 1 、111、11 这样连在一起并相同的数字。
pattern = re.compile(r'(\d)\1*')
res = [_.group() for _ in pattern.finditer(s)]
return ''.join(str(len(c)) + c[0] for c in res)
3.正则表达式(实现):元素替换
def countAndSay(self, n: int) -> str:
res = "1"
pattern= re.compile(r'(\d)\1*')
for _ in range(n-1):
res = pattern.sub(lambda x: str(len(x.group())) + x.group(1), res) # 核心代码,实现元素的替换
return res
7. 最长公共前缀
1. 纵向扫描
def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs:
return ""
length, count = len(strs[0]), len(strs)
for i in range(length):
c = strs[0][i]
if any(i == len(strs[j]) or strs[j][i] != c for j in range(1, count)):
return strs[0][:i]
return strs[0]
2. 纵向扫描
def longestCommonPrefix(self, s: List[str]) -> str:
if not s:
return ""
res = s[0]
i = 1
while i < len(s):
while s[i].find(res) != 0:
res = res[0:len(res)-1]
i += 1
return res
3. python特性
def longestCommonPrefix(self, strs):
res = ""
for tmp in zip(*strs):
tmp_set = set(tmp)
if len(tmp_set) == 1:
res += tmp[0]
else:
break
return res
3. 链表
1. 删除链表中的节点
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
node.val = node.next.val
node.next = node.next.next
2. 删除链表的倒数第N个节点
1. 循环迭代
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
#step1: 获取链表长度
cur, length = head, 0
while cur:
length += 1
cur = cur.next
#step2: 找到倒数第N个节点的前面一个节点
cur = dummy
for _ in range(length - n):
cur = cur.next
#step3: 删除节点,并重新连接
cur.next = cur.next.next
return dummy.next
2. 双指针
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
slow, fast = dummy, dummy
for _ in range(n):
fast = fast.next
while fast and fast.next:
slow, fast = slow.next, fast.next
slow.next = slow.next.next
return dummy.next
3. 递归迭代
![递归](D:\Desktop\啊这\递归.gif)def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
if not head: # 判断是否为最后一个节点
self.count = 0
return head
head.next = self.removeNthFromEnd(head.next, n) # 递归调用
self.count += 1 # 回溯时进行节点计数
return head.next if self.count == n else head
3. 反转链表
1. 双指针
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur:
tmp = cur.next # 暂存
cur.next = pre # 倒序
pre = cur # 进位
cur = tmp
return pre
2. 递归解法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fuhOVnK-1608036976052)(https://i.loli.net/2020/12/15/H4U9nb5gPV7hfYS.gif)]
def reverseList(self, head: ListNode) -> ListNode:
if(head==None or head.next==None):
return head
# 这里的cur就是最后一个节点
cur = self.reverseList(head.next)
# 这里请配合动画演示理解
# 如果链表是 1->2->3->4->5,那么此时的cur就是5
# 而head是4,head的下一个是5,下下一个是空
# 所以head.next.next 就是5->4
head.next.next = head
# 防止链表循环,需要将head.next设置为空
head.next = None
# 每层递归函数都返回cur,也就是最后一个节点
return cur
4. 合并两个有序链表
1. 迭代
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
res = ListNode(-1)
cur = res
while l1 and l2:
if l1.val <= l2.val :
cur.next = l1
l1 = l1.next
else:
cur.next = l2
l2 = l2.next
cur = cur.next
cur.next = l1 if l1 is not None else l2
return res.next
2.迭代
def mergeTwoLists(self, l1, l2):
if l1 is None:
return l2
elif l2 is None:
return l1
elif l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2