文章目录
- 前言
- 两数之和
-
- [1️⃣ 暴力for循环](#1️⃣ 暴力for循环)
- [2️⃣ 解法2](#2️⃣ 解法2)
- [3️⃣ 构建哈希表](#3️⃣ 构建哈希表)
- 两数相加
-
- [1️⃣ 链表 → \xrightarrow{} 列表 → \xrightarrow{} 链表](#1️⃣ 链表 → \xrightarrow{} 列表 → \xrightarrow{} 链表)
- 2️⃣使用链表的特性来解决
- 无重复字符的最长子串
-
- 1️⃣暴力解法
- [2️⃣ 微调第一种方法](#2️⃣ 微调第一种方法)
- 3️⃣滑动窗口+哈希表
- 最长回文子串
- 整数反转
- 总结
前言
算法小白初入leetcode。本文主要记录个人在leetcode上使用python解题的思路和过程,如果有更好、更巧妙的的解题方法,欢迎大家在评论区给出代码或思路。🚀
两数之和
- 题目描述
1️⃣ 暴力for循环
python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
length = len(nums)
for x in range(length):
for y in range(x+1,length):
if (nums[x] + nums[y]) == target:
return [x,y]
2️⃣ 解法2
- 假设我们将列表中所有元素都减去目标值的一半 得到新列表,这样的话,如果我们得到两个及以上的 0 0 0,那么 0 0 0所在的索引位置就是答案;否则,两个值一定是一正一负,所以将列表再分成只包含正数和只包含负数,然后从长度更短的那个列表开始进行遍历,得到相应的索引位置即可。
python
class Solution:
def twoSum(self , nums: List[int], target: int) -> List[int]:
num1 = list(map(lambda x: x - target/2, nums))
num2 = list(filter(lambda x: x < 0, num1))
num3 = list(filter(lambda x: x >= 0, num1))
if num3.count(0) >= 2:
return [num1.index(0) , len(num1[:num1.index(0)+1]) + num1[(num1.index(0)+1):].index(0)]
else:
for i in (num2 if len(num2) < len(num3) else num3):
if -i in (num3 if len(num2) < len(num3) else num2):
return [num1.index(i),num1.index(-i)]
break
3️⃣ 构建哈希表
python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
a = {}
for i , num in enumerate(nums):
if (target - num) in a:
return [a[target - num] , i]
a[nums[i]] = i
return []
两数相加
1️⃣ 链表 → \xrightarrow{} 列表 → \xrightarrow{} 链表
- 对于初入leetcode的小白来说,不懂链表,只懂列表🤷♂️,所以当做列表来处理并按照示例的步骤解题即可。
python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
def to_list(head):
# 初始化一个空列表
result = []
# 从头节点开始遍历链表
current = head
while current:
# 将当前节点的值添加到列表中
result.append(current.val)
# 移动到下一个节点
current = current.next
return result
def list_to_linked_list(nums):
# 初始化一个空的头节点
dummy_head = ListNode()
# 初始化当前节点为头节点
current = dummy_head
# 遍历列表中的每个元素
for num in nums:
# 创建一个新的节点,并将当前节点的下一个节点指向新节点
current.next = ListNode(num)
# 将当前节点指向新节点
current = current.next
# 返回头节点的下一个节点(第一个真正的节点)
return dummy_head.next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
# 将两个列表中的元素转换为字符串,并拼接成一个字符串,最后转换成整数
l1_number = int(''.join(map(str, to_list(l1)[::-1])))
l2_number = int(''.join(map(str, to_list(l2)[::-1])))
sum = l1_number + l2_number
result = list(map(int, list(str(sum))))[::-1]
return list_to_linked_list(result)
2️⃣使用链表的特性来解决
- 对于我这种算法小法来说,首先需要了解一些
链表
的相关知识:Python 中的链表 或者 链表
根据题目中已给的ListNode
就可以知道,一个链表的创建过程如下:(后面将链表中所有元素打印出来)
- 思路:对两个链表从头结点开始同步遍历,用一个变量
val
记录两个节点的和,另外一个变量carry
记录进位的值,然后不断指向下一个节点,直到满足终止条件。按照该思路,两个链表的长度共有两种情形:- 两个一样长:遍历的节点完全是同步的,此时终止条件是两个链表的指针都指向空并且进位上的值等于0。
- 一长一短:该情况下由于短的链表先遍历结束,所以需要将短的链表的节点"补上0",和长的链表对齐。
python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
res_list = [] # 用于存储结果的列表
res = None # 用于存储结果的链表
val = 0 # 用于存储当前位的和
carry = 0 # 用于存储进位的数值
current1 = l1
current2 = l2
while (current1 or current2 or carry): # 当两个链表的指针都为空 并且 进位等于0时,结束循环;
## 如果短的链表遍历完了,将其指针指向0
if not current1:
current1 = ListNode(0)
if not current2:
current2 = ListNode(0)
sum = current1.val + current2.val + carry # 计算当前位的和
val = sum % 10
carry = sum // 10
res_list.append(val)
current1 = current1.next
current2 = current2.next
for i in range(len(res_list)):
if i == 0:
res = ListNode(res_list[-1 - i])
else:
res = ListNode(res_list[-1 - i], res)
return res
速度反而变慢了🤦♂️
- 由于上面代码还是存在列表和链表之间的转换,导致效率并不高。既然都已经考虑用链表的特性来解决了,所以对上面代码再优化一下,直接使用链表存储结果。
python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
# 初始化结果链表的头节点和当前节点
dummy_head = ListNode()
current = dummy_head
val = 0 # 用于存储当前位的和
carry = 0 # 用于存储进位的数值
current1 = l1
current2 = l2
while (current1 or current2 or carry): # 当两个链表的指针都为空 并且 进位等于0时,结束循环;
# 如果短的链表遍历完了,将其指针指向0
if not current1:
current1 = ListNode(0)
if not current2:
current2 = ListNode(0)
sum = current1.val + current2.val + carry # 计算当前位的和
val = sum % 10
carry = sum // 10
current.next = ListNode(val) # 创建新的节点,并将其作为当前节点的下一个节点
current = current.next # 将当前节点指向新节点
current1 = current1.next
current2 = current2.next
return dummy_head.next # 返回结果链表的头节点的下一个节点(第一个真正的节点)
无重复字符的最长子串
1️⃣暴力解法
- 从最大的子串长度开始遍历所有子串,依次判断是否满足条件,一旦满足条件退出循环,输出最长子串长度。
python
def condition(s: str) -> bool: # 判断子串是否重复,True为无重复
return len(set(s)) == len(s)
def sub_of_s(s: str, n: int) -> str: # 返回一个字符串指定长度的所有子串
return [s[i:i+n] for i in range(len(s) - n + 1)]
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
n = len(s)
result = 0
while n > 0:
for i in sub_of_s(s, n):
if condition(i):
result = n
break
n -= 1
if result != 0:
break
return result
- 但是在面对一些长度很长的字符串时出现超时了🤦♂️
2️⃣ 微调第一种方法
- 知道第一张方法效率不高,但是没想到直接会超时了,看看是否能优化一下。上面是从最大的子串长度 开始遍历的,这里直接设置的是字符串的长度,但是仔细一想会发现,既然是不重复 的子串长度,那么最长的长度的上界只能是所有字符类别的数目,而不是整个字符串的长度。
python
def condition(s: str) -> bool: # 判断子串是否重复,True为无重复
return len(set(s)) == len(s)
def sub_of_s(s: str, n: int) -> str: # 返回一个字符串指定长度的所有子串
return [s[i:i+n] for i in range(len(s) - n + 1)]
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
n = len(set(s)) # 最长不重复子串长度的上界
result = 0
while n > 0:
for i in sub_of_s(s, n):
if condition(i):
result = n
break
n -= 1
if result != 0:
break
return result
3️⃣滑动窗口+哈希表
- 解法参考:滑动窗口+哈希表,图文并茂,可以说非常详细,容易理解了。但是少了一个条件判断,那就是:当长度值取到最大(也就是所有字符的类别数目)时,就可以直接退出循环不同遍历了,因为只需要确定长度即可。加上之后,时间确实更快了一点。
python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
dic = {}
res = 0
i = -1
for j in range(len(s)):
if s[j] in dic:
i = max(dic[s[j]], i) # 更新左指针 i
dic[s[j]] = j # 哈希表记录
res = max(res, j - i) # 更新结果
return res
python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
max_length = len(set(s)) # 最长的长度
dic = {}
res = 0
i = -1
for j in range(len(s)):
if s[j] in dic:
i = max(dic[s[j]], i) # 更新左指针 i
dic[s[j]] = j # 哈希表记录
res = max(res, j - i) # 更新结果
if res == max_length:
break
return res
最长回文子串
1️⃣暴力解法
- 同前面一个问题中的解法一,更换一下判断条件即可,没什么可说的
python
def condition(s:str) -> bool: #判断子串是否是回文子串
return s == s[::-1]
def sub_of_s(s: str, n: int) -> str: # 返回一个字符串指定长度的所有子串
return [s[i:i+n] for i in range(len(s) - n + 1)]
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
result = None
while n > 0:
for i in sub_of_s(s, n):
if condition(i):
result = i
break
n -= 1
if result != None:
break
return result
- 对于上面算法,注意到在遍历字符串指定长度的所有子串时会非常耗时,我们可以简单做个筛选:当开头字符和结尾字符不一致时,可以直接pass掉。
python
def condition(s:str) -> bool: #判断子串是否是回文子串
return s == s[::-1]
def sub_of_s(s: str, n: int) -> str: # 进行初步的筛选,并返回一个字符串指定长度的所有可能的回文子串
res = []
for i in range(len(s) - n + 1):
if s[i] == s[i+n-1]:
res.append(s[i:i+n])
return res
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
result = None
while n > 0:
for i in sub_of_s(s, n):
if condition(i):
result = i
break
n -= 1
if result != None:
break
return result
2️⃣解法2
- 因为求解的是最长的回文子串,所以上面是从可能的最大长度的子串开始往下遍历,一旦符合回文串的条件,即可退出循环,输出结果。
- 还可以按照这个思路,不过遍历的不是整个子串,而是对称轴的位置 ,因为回文子串一定是对称的。并且对于任意一个字符串,包含的所有回文子串当中,其长度越长,对称轴肯定是越靠近整个字符串的中心位置的,当整个字符串刚好是一个回文串时,对称轴刚好是中心位置。
- 按照以上思路,可以将对称轴从中心位置开始,按照左右来回横跳的方式一直遍历到首尾字符,只要中间过程中出现了回文串(判断对称轴左右侧字符是否满足条件),输出即可,这样应该就可以尽最大概率先找到最长的回文串;
- 但是上面过程存在一个问题,比如在处理
abbe
时(为区分,假设第一个是b1
,第二个是b2
),那么依次判断ab1
和b2e
,a
和b2
,b1
和e
,a
和b2
,b2
和e
发现都不满足条件直接输出空了,但是实际上是存在bb
这个回文串的;因此,对于每个对称轴,判断时还需要不断砍去首尾字符,避免遗漏掉这种情况。过程例子如下: - 代码:
python
import math
class Solution:
def longestPalindrome(self, s: str) -> str:
res = None
res1 = s[0]
length = len(s)
i = (length - 1)/2 # 对称轴初始索引位置
n = 1
T = -1 # 用于对称轴左右跳转
while i >= 0:
length_half = math.ceil(min(i , math.ceil(length - 1 - i))) # 此时能够取到的最长回文子串的 1/2 长度
s_1 = s[math.ceil(i) - length_half : math.ceil(i)] # 子串的一半
s_2 = s[int(i+1) : int(i+1)+length_half] # 子串的另一半
if s_1 == s_2[::-1]: # 如果去掉这两行代码,最终速度会慢10倍!
break
for j in range(1,length_half):
s_1 = s[math.ceil(i) - length_half + j : math.ceil(i)] # 子串的一半
s_2 = s[int(i+1) : int(i+1)+length_half - j] # 子串的另一半
if s[math.ceil(i)- 1] != s[int(i+1)]:
break
if s_1 == s_2[::-1]:
if '5' == str(i)[-1]:
res1 = s_1 + s_2 if len(s_1 + s_2) > len(res1) else res1
else:
res1 = s_1 + s[int(i)] +s_2 if len(s_1 + s[int(i)] +s_2) > len(res1) else res1
break
i += n*0.5*T
n += 1
T = -T
## 判断当前对称轴所在位置,如果是某个字符上,那么要加上这个字符;否则不用
if '5' == str(i)[-1]:
res = s_1 + s_2 if len(s_1 + s_2) > len(res1) else res1
else:
res = s_1 + s[int(i)] +s_2 if len(s_1 + s[int(i)] +s_2) > len(res1) else res1
return res
又快了一些些🐱🏍
整数反转
1️⃣常规解法
python
class Solution:
def reverse(self, x: int) -> int:
sign = -1 if x < 0 else 1
reversed_str = str(abs(x))[::-1]
reversed_int = int(reversed_str) * sign
if reversed_int < -2**31 or reversed_int >2**31-1:
return 0
else :
return reversed_int
- 思路很简单,从字符串的角度考虑即可。但是,如果仅仅将上面的判断条件换一种方式,最终执行用时居然差异会这么大!不清楚这里的原因是什么,有没有人可以解答一下🍳
python
class Solution:
def reverse(self, x: int) -> int:
sign = -1 if x < 0 else 1
reversed_str = str(abs(x))[::-1]
reversed_int = int(reversed_str) * sign
if -2**31 <= reversed_int <= 2**31 - 1:
return reversed_int
else :
return 0
总结
算法小白初入leetcode,期待给出更精妙的算法🚀🚀🚀