week2
day1
1.两数相加(⭐️⭐️)
1.1 题目
有两个非空链表,分别表示两个非负整数(num>=0)。表内数字元素逆序存储,并且每个节点只能存储一位数字。
要求将两数相加,并以相同形式返回一个表示和的链表。
提示:可假设除数字 0 之外,这两个数都不会以 0 开头。
1.2 算法思路
链表是逆序存储的,故按顺序依次将两个节点相加并将结果放置于新链表对应节点即可。
按位相加时,有三种情况:
(1)两个链表待加数位上都有数字
(2)一个有,一个没有
(3)两个都没有,但有上个数位相加的不为0的进位
三种情况,都可执行 carry += (l1.val if l1 else 0)+(l2.val if l2 else 0)
然后后移指针变量carry,l1,l2的位置
1.3 流程图
1.4 代码实现
时间复杂度:O(max(m,n))
空间复杂度:O(n)
# 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 = cur = ListNode()
carry = 0
while l1 or l2 or carry:
carry += (l1.val if l1 else 0)+(l2.val if l2 else 0)
cur.next =ListNode(carry%10)
cur = cur.next
carry //=10
if l1: l1 = l1.next
if l2: l2 = l2.next
return dummy.next
2.无重复字符的最长子串(⭐️⭐️)
2.1 题目
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
2.2 算法思路
方法一:找出所有的不含有重复字符的字串,再选出最大长度返回
eg:abcabcaa
两层循环寻找非重复子串。
1.自左向右遍历字符串 指针为i.
2.自i向后遍历字符串,指针为j.
设指针 i 指向字串的首字符,指针 j 指向字串的尾字符。
并设置一个列表str1用于存储检索到的子串以及一个变量max_count,用于后面循环退出时的字串长度计算和最长字串长度的保存。
在每次循环结束,将检索到的非重复字串的长度与已保存的最长子串长度进行比较,将较大值填入max_count.
(a)bcabcaa -> abc 3
a(b)cabcaa -> bca 3
ab©abcaa -> cab 3
abc(a)bcaa -> abc 3
abca(b)caa -> bca 3
abcab©aa -> ca 2
abcabc(a)a -> a 1
abcabca(a) -> a 1
时间复杂度:O(n^2)
方法二:找出最长的字符串,输出长度
2.3 流程图
2.4 代码实现
方法一:
时间复杂度:O(n^2)
空间复杂度:O(n)
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
str1 = []
max_length = 0
count = 0
for i in range(len(s)):
for j in range(i,len(s)):
if s[j] not in str1:
str1.append(s[j])
count+=1
else:
break
if max_length < count:
max_length = count
str1.clear()
count = 0
return max_length
方法二:
day2
1. 寻找两个正序数组的中位数(⭐️⭐️⭐️)
1.1 算法思路
条件:
已知输入为两个正序数组,设为nums1,nums2.大小分别为m,n.
要求 编写 一个函数能返回 两个数组合并后的中位数
时间复杂度:O(log(m+n))
分析:
大致分为两步:
- 1.Merge(nums1,nums2)
- 该步骤也可分为
- 边排序边合并
- 时间复杂度 : O(m+n)
- 先合并再排序
- 时间复杂度 = max(合并算法的时间复杂度+排序算法的时间复杂度)
- 边排序边合并
- 该步骤也可分为
- 2.return (nums[len/2]+nums[len/2-1])/2 if len%2 else nums[len/2-1]
1.2流程图
1.3 代码实现
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m = len(nums1) #数组1的大小
n = len(nums2) #数组2的大小
num = [] #用于存储合并后的数组
# 1.在存在空数组的情况下的寻找中位数
if not nums2 :
if len(nums1)%2==0:
return (nums1[int(len(nums1)/2)] + nums1[int(len(nums1)/2)-1])/2
else :
return nums1[int(len(nums1)/2)]
if not nums1 :
if len(nums2)%2==0:
return (nums2[int(len(nums2)/2)] + nums2[int(len(nums2)/2)-1])/2
else :
return nums2[int(len(nums2)/2)]
# 2.在不存在空数组的情况下的寻找中位数
i = j = 0 #分别用于遍历数组1,2的索引
while len(num)!=(m+n):
# 2.1 当某个数组已被遍历完时的情况
if i==m :
while (j!=n):
num.append(nums2[j])
j+=1
if j==n:
while (i!=m):
num.append(nums1[i])
i+=1
# 2.2 两个数组都未被遍历完的情况
else:
if(nums1[i]<=nums2[j]):
num.append(nums1[i])
i+=1
else:
num.append(nums2[j])
j+=1
if len(num)%2==0:
return (num[int(len(num)/2)] + num[int(len(num)/2)-1])/2
else :
return num[int(len(num)/2)]
2. 最长回文子串(⭐️⭐️)
2.1 暴力法
算法思路:
(1)枚举出字符串中所有的子串
(2)从所有子串中筛选出回文串
(3)返回最长的回文串
流程图:
代码实现:
#判断是否回文串
def huiwen(s:str)->int:
if len(s) == 1:
return 1
if len(s)%2==0:
i = 0
j = -1
while(i+1-j <= len(s)):
if(s[i]!=s[j]):
return 0
i+=1
j-=1
return 1
else:
i = 0
j = -1
while(i+1-j<=len(s)-1):
if(s[i]!=s[j]):
return 0
i+=1
j-=1
return 1
class Solution:
def longestPalindrome(self, s: str) -> str:
# 1. 暴力法
# 1.1 找出所有字串并存入A1中
A1 = []
for i in range(len(s)):
t = s[i]
A1.append(t)
for j in range(i+1,len(s)):
t += s[j]
A1.append(t)
# 1.2 从所有子串中找出回文串
max_len = 0
max_s = ""
for a in A1:
if huiwen(a)==1:
if len(a)>max_len:
max_len = len(a)
max_s = a
return max_s
时间复杂度 | 空间复杂度 |
---|---|
O(n^3) | O(n) |
2.2 中心扩展法
算法思路:
(1)从左到右遍历整个数组,每次扫描到一个元素时,进行步骤(2)
(2)设两个指针i,j分别指向该元素。
(3)判断i的左部节点s[i-1]与j的右方相邻节点s[j+1]是否存在,若不存在或存在但不相等,len = j-i+1.否则分别向左右移动
class Solution:
def expandAroundCenter(self, s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return left + 1, right - 1
def longestPalindrome(self, s: str) -> str:
start, end = 0, 0
for i in range(len(s)):
left1, right1 = self.expandAroundCenter(s, i, i)#检测奇数回文串
left2, right2 = self.expandAroundCenter(s, i, i + 1)#检测偶数回文串
if right1 - left1 > end - start:
start, end = left1, right1
if right2 - left2 > end - start:
start, end = left2, right2
return s[start: end + 1]
时间复杂度 | 空间复杂度 |
---|---|
O(n^2) | O(1) |
2.3 动态规划法
动态规划法属于分治法思路的一种方法
核心在于将一个问题划分为多个子问题
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)# 字符串长度
if n < 2:
return s
max_len = 1
begin = 0 #左指针
# dp[i][j] 表示 s[i..j] 是否是回文串
dp = [[False] * n for _ in range(n)]
for i in range(n):
dp[i][i] = True
# 递推开始
# 先枚举子串长度
for L in range(2, n + 1):
# 枚举左边界,左边界的上限设置可以宽松一些
for i in range(n):
# 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
j = L + i - 1
# 如果右边界越界,就可以退出当前循环
if j >= n:
break
if s[i] != s[j]:
dp[i][j] = False
else:
if j - i < 3:
dp[i][j] = True
else:
dp[i][j] = dp[i + 1][j - 1]
# 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if dp[i][j] and j - i + 1 > max_len:
max_len = j - i + 1
begin = i
return s[begin:begin + max_len]
day3
1. N 字形变换(⭐️⭐️)
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
1.1 算法思路:
1.2 流程图:
1.3 代码:
class Solution:
def convert(self, s: str, numRows: int) -> str:
n = len(s)
if n<=numRows or numRows==1: return s
res = ["" for _ in range(numRows)]
i = 0
flag = -1
for c in s:
print(i)
res[i]+=c
if i==numRows-1 or i ==0 : flag = -flag
i+=flag
ans = ''
for r in res:
ans+=r
return ans
2.整数反转(⭐️⭐️)
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−2^31, 2^31 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
2.1 算法思路:
2.2 流程图:
2.3 代码:
class Solution:
def reverse(self, x: int) -> int:
INT_MIN, INT_MAX = -2**31, 2**31 - 1
rev = 0
while x != 0:
# INT_MIN 也是一个负数,不能写成 rev < INT_MIN // 10
if rev < INT_MIN // 10 + 1 or rev > INT_MAX // 10:
return 0
digit = x % 10
# Python3 的取模运算在 x 为负数时也会返回 [0, 9) 以内的结果,因此这里需要进行特殊判断
if x < 0 and digit > 0:
digit -= 10
# 同理,Python3 的整数除法在 x 为负数时会向下(更小的负数)取整,因此不能写成 x //= 10
x = (x - digit) // 10
rev = rev * 10 + digit
return rev
day4
1.字符串转换整数 (atoi)(⭐️⭐️)
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
-
读入字符串并丢弃无用的前导空格
-
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
-
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
-
将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
-
如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 2**31 − 1 。
-
返回整数作为最终结果。
-
注意
本题中的空白字符只包括空格字符 ’ ’ 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
1.1 算法思路
1.2 流程图
1.3 代码实现
class Solution:
def myAtoi(self, s: str) -> int:
# string.lstrip()去前置空格
s, ans, flag = s.lstrip(), 0, 1
# 检测是否为空
if s == "":
return 0
#检测符号位,只有首个有效,并切片
if s[0] == '+':
s = s[1:]
elif s[0] == '-':
flag, s = -1, s[1:]
# 一个符合题目要求的字符串此时应该是数字部分
for c in s:
if c.isdigit():
ans = ans * 10 + int(c)
else:
break
ans *= flag
return min(max(ans, - 1 << 31), (1 << 31) - 1)
2. 盛最多水的容器(⭐️⭐️)
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
2.1 算法思路
方法一:暴力解法(枚举)
n个垂线,两两组合,共有n(n-1)/2种非重复结果
两垂线同x轴构成的容器容积大小为:min(height[left],height[right])*(right-left)
时间复杂度高
方法二:借用双指针只遍历一遍(优化)
- 设立两个指针变量left , right
- 并初始化使他们分别指向数组的首尾left = 0 right = len(height)-1
- 计算容积min(height[left],height[right])*(right-left)
- 比较最大容积和此时的容积,取最大值更新容积
- 两个指针中所指向的高度较低的那个移动,(left右移,right左移)
- 若left!=right,则重复进行步骤3,4,5
- 返回最大容积
2.2 流程图
2.3 代码实现
class Solution:
def maxArea(self, height: List[int]) -> int:
# 1.排列组合问题
#n个垂线,两两组合,共有n(n-1)/2种非重复结果
## 两垂线同x轴构成的容器容积大小为:min(height[i1],height[i2])*((i1-i2) if i1>i2 else i2-i1)
# 时间复杂度高
# n = len(height)
# max_V = 0
# for i in range(n):
# for j in range(i+1,n):
# v = min(height[i],height[j])*((i-j) if i>j else j-i)
# if v>max_V:
# max_V = v
#2.优化
n = len(height)
left = 0
right = n-1
max_V = 0
while left!=right:
v = min(height[left],height[right])*(right-left)
if v>max_V:
max_V = v
if height[left] >= height[right]:
right-=1
else:
left+=1
return max_V
day5
1.罗马数字转整数(⭐️)
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
1.1 算法思路
看题可以发现存在IX这类组合字符,对这类字符的处理我们可以利用规则“通常情况下,罗马数字中小的数字在大的数字的右边。IX这类都属于特殊规则”
1.2 流程图
1.3 代码实现
class Solution:
def romanToInt(self, s: str) -> int:
dic2 = {'M':1000,'D':500,'C':100,'L':50,'X':10,'V':5,'I':1}
n = len(s)
ans = 0
for i,c in enumerate(s):
if i<n-1 and dic2[s[i+1]]>dic2[s[i]]:
ans-=dic2[s[i]]
continue
ans+=dic2[s[i]]
return ans
2.最长公共前缀(⭐️)
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
2.1 算法思路
数组的遍历
2.2 流程图
2.3 代码实现
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
c = ''
strs.sort(key = lambda i :len(i))
print(strs)
for i in range(len(strs[0])):
c+=strs[0][i]
#print(c)
for s in strs:
if s[i]!=c[-1]:
c = c[0:-1]
return c
return c
3.三数之和(⭐️⭐️)
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。
请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
3.1 算法思路
- 对列表排序
- 定义三个指针first,second,third
- 枚举a,逐个遍历即可
- 枚举b,从a后一位开始枚举
- 枚举c,从数组尾部向前移动,移动至接近a+b+c>0的临界
3.2 流程图
3.3 代码实现
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()#使待处理数组有序(升序)
ans = []
#first 指向三元组首个元素
# 枚举a
for first in range(len(nums)):
# first每次进入三元组不重复
if first>0 and nums[first]==nums[first-1]:
continue
#c+b = target
target = -1*nums[first]
#c从数组最右侧
third = len(nums)-1
#枚举b
for second in range(first+1,len(nums)):
# 保证同上次枚举的不重复
if nums[second]==nums[second-1] and second > first+1:
continue
# 枚举c
while second<third and nums[second]+nums[third]>target:
third-=1
#判断其退出循环的方式
#两者值想同,但是a+b>-c.此时a,b定,c活动,但c再左移,就算是出现了符合a+b+c的三元组,也是重复的
if second==third:
break
if nums[second]+nums[third]==target:
ans.append([nums[first],nums[second],nums[third]])
return ans
4. 最接近的三数之和(⭐️⭐️)
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解
4.1算法思路
以数组A[1,2,3,4,5,6,7,8]为例,target = 7
我们需要从中选出与target 最近的值,因为涉及距离比较问题。
我们使用num1,num2,num3 的和sum与target的差的绝对值来表示三数与target的接近程度。
涉及到三数的非重复枚举,可参考上题的三数之和的思路,先排序,再枚举。
在枚举过程中需考虑几个情况:
- (1)三数之和 = target:return target
- (2)三数之和 > target: c左移
- (3)三数之和 < target: b右移
4.2 代码
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
n = len(nums)
best = 10**7
# 根据差值的绝对值来更新答案
def update(cur):
nonlocal best
if abs(cur - target) < abs(best - target):
best = cur
# 枚举 a
for i in range(n):
# 保证和上一次枚举的元素不相等
if i > 0 and nums[i] == nums[i - 1]:
continue
# 使用双指针枚举 b 和 c
j, k = i + 1, n - 1
while j < k:
s = nums[i] + nums[j] + nums[k]
# 如果和为 target 直接返回答案
if s == target:
return target
update(s)
if s > target:
# 如果和大于 target,移动 c 对应的指针
k0 = k - 1
# 移动到下一个不相等的元素
while j < k0 and nums[k0] == nums[k]:
k0 -= 1
k = k0
else:
# 如果和小于 target,移动 b 对应的指针
j0 = j + 1
# 移动到下一个不相等的元素
while j0 < k and nums[j0] == nums[j]:
j0 += 1
j = j0
return best
day6
1.电话号码的字符组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
算法思路
回溯法
流程图
代码
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return list()
phoneMap = {
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz",
}
def backtrack(index: int):
if index == len(digits):
combinations.append("".join(combination))
else:
digit = digits[index]
for letter in phoneMap[digit]:
combination.append(letter)
backtrack(index + 1)
combination.pop()
combination = list()
combinations = list()
backtrack(0)
return combinations
2. 四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案
代码
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
n = len(nums)
res = []
if n<4 :
return res
#枚举a
for a in range(n-3):
if a>0 and nums[a]==nums[a-1]:
continue
#枚举b
for b in range(a+1,n-2):
if b>a+1 and nums[b]==nums[b-1]:
continue
# c,d 初始化
c = b+1
d = n-1
while c<d :
# 更新c,d
if nums[a]+nums[b]+nums[c]+nums[d]>target:
d-=1
elif nums[a]+nums[b]+nums[c]+nums[d]<target:
c+=1
else:
res.append([nums[a],nums[b],nums[c],nums[d]])
while c<d and nums[c]==nums[c+1]:
c+=1
c+=1
while c<d and nums[d]==nums[d-1]:
d-=1
d-=1
return res
3.删除链表的倒数第 N 个结点(⭐️⭐️)
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
3.1 算法思路
设立两个指针first,second
first初始化为head的头节点
同步后移两个指针,直至指针j移动至最后一个节点时,指针i指向该链表倒数第N个节点
删除该节点然后返回链表头节点
3.2 代码
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0, head)
first = head
second = dummy
for i in range(n):
first = first.next
while first:
first = first.next
second = second.next
second.next = second.next.next
return dummy.next
4.有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
4.1 算法思路
声明一个辅助字典(hash表)dic = {
‘]’:‘[’,
‘}’:‘{’,
‘)’:‘(’
}
以及一个辅助栈stack = []
1.考虑到输入只有这三种括号类型的字符
故可以知道,当字符串长度为奇数时,括号不可能匹配上
2.我们使用上面的辅助栈来匹配括号
在扫描字符串时遇到字符ch不在dic的key集合,即不是右括号的情况时,将其压入stack中。
若遇见右括号则需判断该右括号的对应左括号是否在stack的倒数第一个位置中,若在则弹出,否则返回 False
4.2
4.3 代码实现
class Solution:
def isValid(self, s: str) -> bool:
if len(s) % 2 == 1:
return False
pairs = {
")": "(",
"]": "[",
"}": "{",
}
stack = list()
for ch in s:
if ch in pairs:
if not stack or stack[-1] != pairs[ch]:
return False
stack.pop()
else:
stack.append(ch)
return not stack
day7
1.两数之和
1.1算法思路
1.2
1.3 代码实现
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 排序
res = []
for i,num in enumerate(nums):
res.append((num,i))
res = sorted(res,key = lambda x:(x[0]))
#
i = 0
j = len(nums)-1
while i<j:
if res[i][0]+res[j][0] == target:
return [res[i][1],res[j][1]]
elif res[i][0]+res[j][0] < target:
i+=1
else:
j-=1
return []