总结算法中可以前后处理的方法实例
世间有好坏,算法逻辑也有前后‘因果’,我们可以从数组中看出有第一项和最后一项。
以LQ26删除有序数组中的重复项举例
可以把不相等的数字往前移动,这样最多会改变一个数据,如果没有重复数据存在的时候,那就是最前面的(索引0)数据。
若有相同,那就不一样了;
数组篇
数据前后移动处理
数据往前移动处理
处理关键是慢指针的处理,他是确定相同数据的指标;有相同的数据他就停下来
n = len(nums) # 有序数组才可使用快慢双指针排序
fast = slow = 1
while fast < n:
# 为什么不等于的数据往前移动
if nums[fast] != nums[fast-1]:
nums[slow] = nums[fast]
slow +=1 # 前后不同把他替换成功的慢指针加1
fast+=1 # 快指针不停的走;这样会把原来的数据替换没,下方书上那个应该不改变原来元素
return slow # (28 ms),这题到底返回啥,数字还是列表
数据往后移动处理
n = len(set(nums))
i = 1
while i < n: # 这是先求出n,在来对列表进行切片赋值
if nums[i] == nums[i-1]: # 等于的数据往后移动
temp = nums[i] # 果然报错
nums[i: len(nums) -1] = nums[i+1:] # list index out of range
nums[-1] = temp
continue
else:
i += 1
return n # (5988 ms)用了len(set())所以很慢,但数组中的数据没改变,还是输出了不重复的数据
LQ[217] 存在重复元素
## 第三种方法使用sort排序后
nums.sort()
# 正的
# for i in range(len(nums)-1):
# 倒的
for i in range(len(nums)-1,0,-1):
# if nums[i] == nums[i+1]: # 正的
if nums[i] == nums[i-1]:
return True
return False # (100 ms) (96 ms)
哈希算法
判断比较数组中元素的个数,通用(使用哈希表)
# nums是数组
a = {}
for num in nums:
# 这里的条件是不在字典中加1,就是有点逆序的想法
if num not in a: # 这里加一个a.keys()时间复杂度就超时了
a[num] = 1 # 创建哈希表元素
else:
# 这里是关键,通过get获取指定元素值来加1
a[num] = a.get(num) + 1
这是a的键就是元素的值,a的值就是元素的个数,直接从a中的键读取元素,也可以使用a.keys(),a.values()提取出所有的键,所有的值。
提取出来的是个列表,再在提取出来的基础上进行操作,就一目了然
另外一种哈希方式
采用字典的keys和continue来完成,在某种程度上和上面的if,else运行的语句相反
d = {}
for i in s:
if i in d.keys(): # 第二次出现时,要捕获并结束这次循环
d[i] += 1
continue
d[i]=1
该方法可以求两数组中相同的元素并提取,然后不同的元素保留(并没有去重的话相同元素有多个)
## 两数组长短不一求交集
# 交集肯定在短的中
if len(nums1) > len(nums2):
nums1,nums2 = nums2,nums1
# 反正就是nums1这个名字集合最小
a = []
for i in set(nums2):
for j in set(nums1):
if i == j:
a.append(i)
nums1.remove(j)
return a # (76 ms)
将列表中的元素全部转化为字符串
使用生成器即可;digits是个数字的列表
a = ("%s"%i for i in digits)
数字位上的操作
LQ66 加一
数字分个位,十位,百位依次递增;需要考虑的特殊情况都为9
# 正宗倒序,对每个元素进行处理
digits.reverse() # 列表元素倒序,修改自身
flag = True # 设定一个为9的标志
for i in range(len(digits)):
if flag: # 只有都为9的似乎后才需要flag等于true
# 这是处理999都是9的情况
if digits[i] == 9:
digits[i] = 0
else:
digits[i] += 1
flag = False # 只要对最后一位元素加1,其他各尽一位
if digits[-1] == 0:
digits.append(1)
# 处理完加法后,在倒序回去
digits.reverse()
return digits # (24 ms)
双指针法经常作用的点
上面的数据 数据往前后移动处理也使用了双指针
指针指向的位置:pos
指针的移动特点:fea
LQ[283] 移动零
pos:0,0
fea:left元素遇到0,整体后移,right一直后移,遇到非0;left,right均后移(right在这里就是个条件统计所有元素的条件)
left = 0
right = 0 # 以索引位置来做比较
while right < len(nums) -1: # len(nums)做了个统计很耗时
if nums[left] == 0 :
nums[left:-1] = nums[left+1:]
nums[-1] = 0
right +=1
else: # 就算为left不为0,right也要走完
left +=1
right +=1
return nums # (532 ms)
突然的想法
是不是有些用统计长度的内置函数不使用,换成其他方式解决算法题木。
得到索引
LQ[1] 两数之和
替换变量值的思想
d = {}
for i in range(len(nums)): # (12 ms)
b = target - nums[i]
if nums[i] in d: # 处理相同元素的,直接用in d,不要加.keys(),.values(),字典太仁慈了
return [d[nums[i]],i]
else:
# 反正在不在都得往字典中加入,键值分别为被减数和索引;通过减数与被减数得到索引
d[b] = i # 值/别人的索引
字符串篇
统计与乘2除2相关的操作
为什么要乘2,为什么要除2
LQ344
# 第3种方法,除2来界定for循环边界
length = len(s)
if length < 2:
return
for i in range(length//2):
# 这其实比较形象,显得出要循环迭代的次数;有点废脑子
# 以中间为分界线,对称呼唤
s[i],s[length-1-i]=s[length-i-1],s[i] # (64 ms)
验证回文串
LQ[125] 验证回文串,这里有些回文串包含除字母外的其他字符。
最好使用while循环,right > left,一个递增,一个递减,就算是中间相同也没关系会退出
并不能使用,这里的字符有些是并不是字符,那么次数就不管用,所以这是错误的
# 可以不用while,这里的次数可以算出来
for i in range(len(s)//2): # 但还是尽量少用len(s)//2,就算是对称的
# 最好使用right > left,一个递增,一个递减,就算是中间相同也没关系会推出
if not s[left].isalnum():
left +=1
continue
if not s[right].isalnum():
right -=1
continue
if s[left]==s[right]:
left +=1
right -=1
else:
return False
return True # (492 ms) 我的解法都好慢啊
上面的是错误的,要用除2判断次数,必须要对字符串进行一定的处理
if len(s) < 2:
return True # 小于2只有1个字符串就为回文串
sList = []
s = s.lower() # 全部处理为小写
for word in s:
if word.isalnum():
sList.append(word) # 创建新空间提取字母
n = len(sList) // 2
# 这里要处理下用倒序,取出n为奇数偶数个干扰
if sList[:n] == sList[::-1][:n]:
return True
else:
return False # 就是太浪费空间 # (32 ms)
while解法(双指针),可以不用预先处理字符串,
s = s.lower()
left = 0
right = len(s)-1
while right - left > 0:
# 从左边开始不是一个有效字符,左加1
if not s[left].isalnum():
left +=1
continue
if not s[right].isalnum():
right -=1
continue
# 判断是否为字符串,不是字符串时候执行
if s[left]==s[right]:
left +=1
right -=1
print('左边索引为',left)
print('右边索引为',right)
# 两者索引靠近,就会等于0
else:
return False
递归与while循环
基本上使用while循环的函数都可以用递归来做
递归的结束条件就是while循环的结束条件,递归地进行下去的条件就是while循环中的自增自减,递归中对数据的操作和while循环中一样
两者都有相同的结束条件和进行的条件,对数据的操作也相同
LQ344
while循环
def test(s):
l= 0
r = len(s)-1
while True:
s[l],s[r] = s[r],s[l] # while语句中进行的操作
# while得以进行的条件
l +=1
r -=1
if l >= r: # 结束条件
return s # (40 ms)
递归
class jj:
def test(self,s)
return self.digui(s,0,len(s)-1) # (40 ms)
def digui(self,s,left,right):
# 结束条件
if left>=right: # 当前面所有超过后面索引返回
return s
# 反正都是一次替换2个,不过这个方法不一样
# 递归进行的条件
self.digui(s,left+1,right-1)# 把双指针的加剪边成了递归拆解
# 递归时进行的操作;基本上任何可以使用while进行的函数都可以使用递归
s[left],s[right] = s[right],s[left] # 这是赋值
链表
链表的构成可以参考实现链表的完整代码
反转链表
一般链表的题目只会给个head,链表的特性是用next指针连接起来,而next指针又为下一个元素
这里主要注意对链表反转之前的操作,要进行判断是否为空链表,是否为单链表,对双指针的right和left进行处理,right为前进的链表头,把他的下一个指向指为自身(这是反转的关键),left的next开始为None
# 两个指针的用法
if head == None:
return None
left = right = head
if right.next == None:
return head
else:
# 这是对平常的处理,赋值
# right的下一个指向指向right
# lext的下一个指向指向None
right = right.next
left.next = None
while right != None:
# 玩的就是个变量的赋值
head = right # 变量h被赋值r,不断地把r变为头节点
right = right.next # r要继续导出下一个节点,把变量赋值给r
head.next = left # h的next指向到l
left = head # h的值又给到l,不断把头节点变为左节点
return head # (12 ms)
创建26个小写字母为键,值为0的字典
words = [chr(i) for i in range(97,123)]
values = [0] *26
words_dict = dict(zip(words,values))