挑战30天从入门到精通,每日打卡,长期有效,持续关注!!!以下除题目外,答案与解析均原创,转载请标明出处,感兴趣的同学欢迎评论区打卡~,
关注公众号【可与】,关注更多新鲜内容
【挑战30天掌握】算法与数据结构!!!
算法与数据结构打卡【day1】
3.19
二分查找
704. 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/binary-search
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)-1
while left<=right:
mid = (left+right)//2
if target == nums[mid]:
return mid
elif target > nums[mid]:
left = mid+1
else:
right = mid-1
return -1
278. 第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/first-bad-version
# The isBadVersion API is already defined for you.
# def isBadVersion(version: int) -> bool:
class Solution:
def firstBadVersion(self, n: int) -> int:
left = 1
right = n # right总是指向错误版本
# 确定相邻两个为False和True即可,由于若有一个true,他后都为true,
# 让区间[left,right] = [false,true]从而将第一个true包含其中,缩紧边界,
#让 left 为false或第一个true同时,让right为true,而后left==right时指向第一个true
while left< right:
mid = (left+right)//2
if isBadVersion(mid): # mid为true
right = mid #让right为true
else: # mid 为false, 此时mid+1可能为false或第一个true
left = mid+1 # left 为false或第一个true
# 此时 left==right 为第一个true
return left
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/search-insert-position
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)-1
if target> nums[right]:
return len(nums)
if target<nums[0]:
return 0
# 二分法总是将target包在[nums[left],nums[right]]之中,当发现nums里没有the exact "target",此时left与right颠倒,取left就可以
while left<=right:
mid = (left+right)//2
if target==nums[mid]:
return mid
elif target > nums[mid]:
left = mid+1
else :
right = mid-1
return left
哈希表
217. 存在重复元素
给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
链接:https://leetcode.cn/problems/contains-duplicate/
- 方法1
利用python的集合
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
if len(nums) == len(set(nums)):
return False
else:
return True
- 方法2
创建字典
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
dic = {}
for num in nums:
if dic.get(num)==None:
dic[num]=1
else:
dic[num]=dic[num]+1
if dic[num] > 1:
return True
return False
动态规划
53. 最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/maximum-subarray
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 以nums[i]结尾的最大和记为dp[i]
# dp[i] = max(dp[i-1]+nums[i],nums[i])
# 最大和为所有dp[i]中的最大值
l = len(nums)
dp = [min(nums)]*l
dp[0]=nums[0] #初始情况(边界)
#只说输出最大连续子数组之和,并没有让输出数组本身
for i in range(1,l):
dp[i] = max(dp[i-1]+nums[i],nums[i])
return max(dp)
算法与数据结构打卡【day2】
3.20
双指针
知识点讲解:
# 两个指针
# 1. 方向相反
# 求两数之和
# e.g. 给定升序排列数组 nums=[1,2,3,4,12],求两数之和为7的下标,左指针left=0, 右指针right=4
# nums[left]+nums[right]>7, right=right-1 ,
# nums[left]+nums[right]<7, left = left +1 ,一直下去
def oppositeDirection(nums,target):
left = 0
right = len(nums)-1
while left<right:
if nums[left]+nums[right]==target:
return left,right
elif nums[left]+nums[right]> target:
right = right-1
else:
left = left +1
return "Not found"
print(oppositeDirection([1,2,3,4,12],7))
# 2. 方向相同
# 合并两个有序数组
# [2,5,8,10],[1,2,4,6,11]
# 定义两个指针i,j分别指向两个数组元素,i,j从大到小比较数组,小的放在结果里,移动指针至两个指针指向末尾
# code:
def sameDirection(lis1,lis2):
l1 = len(lis1)
l2 = len(lis2)
i,j = 0,0
l = []
while i<=l1-1 and j<=l2-1: #不满足两个条件,说明将l1或l2至少一个搬空
if lis1[i]<=lis2[j]:
l.append(lis1[i])
i = i+1
else:
l.append(lis2[j])
j = j+1
if i<=l1-1: #说明l1没有搬空
l.extend(lis1[i:])
if j<=l2-1:
l.extend(lis2[j:])
return l
# print(sameDirection([2,5,8,10],[1,2,4,6,11]))
# 3. 一快一慢(快慢指针)
# 判断单链表是否存在“环”
# 设定指针p1,p2,其中p2的速度是p1的两倍,若存在环,p1会被再次追上
#4. 指针同向,但起点不同
# 返回倒数第n个节点值
# 设定p2初始指向第n个节点,p1初始指向第1个节点,同时跑时,当p2指向最后一个节点时,p1指向倒数第n个节点
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array
- 方法一,利用python内置方法
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
l = sorted([num**2 for num in nums])
return l
- 方法二:双指针
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
left = 0
right = len(nums)-1
l = []
while left<=right:
left_n,right_n = nums[left]**2,nums[right]**2
# 此时左边和右边都为两侧最大值向中间递减,将最大值依次放入后倒转列表
if left_n >= right_n:
l.append(left_n)
left = left+1
else:
l.append(right_n)
right = right-1
return list(reversed(l))
189. 轮转数组
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
链接:https://leetcode.cn/problems/rotate-array
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
- 方法一:数组切片
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
nums[:] = [*nums[-(k%len(nums)):],*nums[:-(k%len(nums))]]
# 数组切片时间复杂度O(n),
#等于其他语言中的新建一个数组,将原数组中的元素放到新数组中对应的位置上,然后再将新数组的元素复制到原数组中,时间复杂度为 O(n)
# 注意这里不能直接nums = ,这样为指向了新的不属于in-place操作,
# 这里 k 可能大于数组长度,当k等于len(nums)的倍数时,和原始数组相同
- 方法二:反转数组
反转三次数组得到
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# ---->-->
k = k%len(nums)
nums1 = nums[::-1] # <--<----
nums1_left =nums1[:k][::-1] # -->
nums1_right = nums1[k:][::-1] # ---->
nums[:] = nums1_left+nums1_right # -->---->
# 注意这里不能直接nums = ,这样为指向了新的不属于in-place操作,
# 这里 k 可能大于数组长度,当k等于len(nums)的倍数时,和原始数组相同
# 注意这里如果用reversed,需要在用list(reversed),
# 因为使用后类型为<list_reverseiterator object at 0x0000018F61C13880>
88. 合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
链接:https://leetcode.cn/problems/merge-sorted-array
方法1,使用额外数组复制
#使用双指针,注意深复制
#时间复杂度O(m+n)
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
num_1 =nums1[:] #复制一份nums1用来排序,原来的nums1用来装结果,这里必须deepcopy,否则nums1变动,会影响num_1
# 使用同向双指针,从小到大排
i = 0
j = 0
k = 0
while i<=m-1 and j<=n-1: #两个数组都没有排完,若有一个数组排列完毕,index经过+1后会大于len-1
if num_1[i]<=nums2[j]:
nums1[k]=num_1[i]
i = i+1
k = k+1
else:
nums1[k]=nums2[j]
j = j+1
k = k+1
if i<=m-1:
while k<=m+n-1:
nums1[k]=num_1[i]
i = i+1
k = k+1
if j<=n-1:
while k<=m+n-1:
nums1[k]=nums2[j]
j = j+1
k = k+1
方法2,从大到小放入nums1末端
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
#从后向前遍历数组
i = m-1
j = n-1
k = m+n-1
if m==0:
nums1[:]=nums2
while i>=0 and j>=0: #任意一个排满了就退出
if nums1[i]>=nums2[j]:
nums1[k]=nums1[i]
k = k-1
i = i-1
else:
nums1[k]=nums2[j]
k = k-1
j = j-1
# if i>=0: #本身剩下的就不用移动了
# while k>=0:
# nums1[k]=num1[i]
# i = i-1
# k = k-1
if j>=0:
while k>=0:
nums1[k]=nums2[j]
j = j-1
k = k-1
反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
链接:https://leetcode.cn/leetbook/read/top-interview-questions-easy/xnhbqj/
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
# 双指针
# 对称交换
for i in range(len(s)//2):
s[i],s[-i-1]=s[-i-1],s[i] # 这里其实相当于用temp额外空间O(1)进行交换
哈希表
1. 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
链接:https://leetcode.cn/problems/two-sum
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 哈希映射
mapDic = {}
#按顺序将mapDic 中存入nums的值和其index,{[num]:index}
#从左往右计算每个num的互补值,如果mapDic中有其互补值,则已经找到,没有则将num及其index存入mapDic
# 时间复杂度为O(n)
for i in range(len(nums)):
cur = target-nums[i]
if mapDic.get(cur) !=None: #mapDic中存在当前nums[i]的互补值
return [mapDic[cur],i]
else:
mapDic[nums[i]]=i #将nums[i]本身存入mapDic
字符串中的第一个唯一字符
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
示例 1:
输入: s = “leetcode”
输出: 0
链接:https://leetcode.cn/leetbook/read/top-interview-questions-easy/xn5z8r/
class Solution:
def firstUniqChar(self, s: str) -> int:
Dic = {}
for i in range(len(s)):
if Dic.get(s[i])==None:
Dic[s[i]]=1
else:
Dic[s[i]]=Dic[s[i]]+1
for i in range(len(s)):
if Dic.get(s[i])==1:
return i
return -1
算法与数据结构打卡【day3】
3.21
双指针
283. 移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
题目链接:https://leetcode.cn/problems/move-zeroes
方法1
遍历两遍
第一遍记录非零个数n
第二遍,i记录经过的非零个数-1,j向后移动,当nums[j]!=0时,放入nums[i],
最后将nums后的n个位置赋值0
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = 0
for i in range(len(nums)):
if nums[i]==0:
n = n+1
i,j=0,0
while j<len(nums)-1:
if nums[i]!=0:
i = i+1
j = j+1
else: #此时i指向第一个0
while j<=len(nums)-2: #当j不是最后一个元素,继续向后移动
j=j+1
if nums[j]!=0: #当当前位置不为零,将值放前面
nums[i] = nums[j]
i=i+1
if n!=0: #没有0,不需要剪切
nums[-n:]=[0]*n
方法2
遍历一遍
指针i,记录第一个0的位置,j向后移动,当nums[j]!=0时,与nums[i]交换,
i++后j继续向后移动,直至j==len(nums)
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
i,j=0,0
while j<len(nums)-1: #j到达最后一个,停止操作
if nums[i]!=0:
i = i+1
j = j+1
else: #此时i指向第一个0
while j<=len(nums)-2: #当j不是最后一个元素,继续向后移动
j=j+1
if nums[j]!=0: #当当前位置不为零,加入交换操作
nums[i],nums[j]=nums[j],nums[i]
i=i+1
方法3,python内置函数
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
return nums.sort(key=lambda x:x==0) #优先级高的放在最后
350. 两个数组的交集 II
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
链接:https://leetcode.cn/problems/intersection-of-two-arrays-ii
方法1,排序+双指针
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1[:]=sorted(nums1)
nums2[:]=sorted(nums2)
ls = []
i,j=0,0
while i<=len(nums1)-1 and j<=len(nums2)-1: #若有一个测试完毕就退出
if nums1[i]==nums2[j]: #当前元素相同
ls.append(nums1[i])
i=i+1
j=j+1
elif nums1[i]<nums2[j]:
i=i+1
else:
j=j+1
return ls
方法2,哈希表
创建一个哈希表H存储nums1元素出现次数,
在nums2中若H[num]存在,H[num]-1,且在结果中append这个num
默认len(nums1)<=len(nums2),否则在首行调用自身"func(nums2,nums1)"
# 官方版本
class Solution:
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 = []
for num in nums2:
if m.get(num, 0) > 0:
intersection.append(num)
m[num] -= 1
if m[num] == 0:
m.pop(num)
return intersection
动态规划
121. 买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices)==1:
return 0
earn = 0
min_price = prices[0]
for i in range(1,len(prices)):
min_price = min(min_price,prices[i]) #寻找之前的最小的price
earn=max(prices[i]-min_price,earn)
# dp[i]=max(dp[i−1],prices[i]−minprice)
return earn
链表
删除链表中的节点
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:指定链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9
提示:
链表中节点的数目范围是 [2, 1000]
-1000 <= Node.val <= 1000
链表中每个节点的值都是 唯一 的
需要删除的节点 node 是 链表中的节点 ,且 不是末尾节点
链接:https://leetcode.cn/leetbook/read/top-interview-questions-easy/xnarn7/
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
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 #将下一节点的指针赋给当前节点的指针(当前节点的指针被跳过)
算法与数据结构打卡【day4】
3.22
双指针
557. 反转字符串中的单词 III
给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
示例 1:
输入:s = "Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"
链接:https://leetcode.cn/problems/reverse-words-in-a-string-iii
方法1
class Solution:
def reverseWords(self, s: str) -> str:
i,j=0,0
result = ""
while j<len(s): #j出界时退出
if s[i]==" ":
i=i+1 #i,j跳过空格
j=j+1
result=result+" "
else: #i指向第一个非空元素
while j<=len(s)-1 and s[j]!=" ": #跳出时j指向下一个空格或者j指向末尾
j=j+1
result=result+s[i:j][::-1]
i=j #此时一起指向空格
return result
方法2 内置方法
class Solution:
def reverseWords(self, s: str) -> str:
ls = s.split(" ")
ls[:] = list(map(lambda x:x[::-1],ls))
return " ".join(ls)
矩阵
566. 重塑矩阵
在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。
给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
链接:https://leetcode.cn/problems/reshape-the-matrix
方法1,元素赋值
class Solution:
def matrixReshape(self, mat: List[List[int]], r: int, c: int) -> List[List[int]]:
R = len(mat)
C = len(mat[0])
ls = []
if r*c!=R*C:
return mat
inner_ls =[0]*c
for i in range(r):
for j in range(c):
inner_ls[j] = mat[(j + i * c) // C][(j + i * c) % C]
ls.append(inner_ls[:]) #这里需要用[:],否则当变量inner_ls改变时,原来赋值过的位置会一起改变
return ls
方法二:切片赋值
class Solution:
def matrixReshape(self, nums: List[List[int]], r: int, c: int) -> List[List[int]]:
if len(nums)*len(nums[0])!=r*c:
return nums
l=[]
new=[]
for i in range(len(nums)):
l+=nums[i]
for i in range(0,len(l),c):
new.append(l[i:i+c])
return new
数学
118. 杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
方法1,传统公式
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
ls=[[1],[1,1]]
inner_ls=[1,1]
if numRows==1:
return [[1]]
if numRows==2:
return ls
for i in range(2,numRows):
cur=[1]
for j in range(1,i):
cur.append(inner_ls[j-1]+inner_ls[j])
cur.append(1)
inner_ls[:]=cur[:] #储存生成的值,供下次使用
ls.append(inner_ls[:])
return ls
方法二:发现 错位之和为下一行答案:
如:
1 2 1 0
0 1 2 1
…
1 3 3 1
从而有:
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
ls = [[1]]
res = [1]
while len(ls)<=numRows-1: ## 前面已经有一个初始情况,当numsRows==1时,直接返回ls
res = [a+b for a,b in zip(res+[0],[0]+res)]
ls.append(res)
return ls
去掉一行
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
ls = [[1]]
while len(ls)<=numRows-1: ## 前面已经有一个初始情况,当numsRows==1时,直接返回ls
res = [a+b for a,b in zip(ls[-1]+[0],[0]+ls[-1])]
ls.append(res)
return ls
链表 & 双指针
删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
https://leetcode.cn/leetbook/read/top-interview-questions-easy/xn2925/
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy = ListNode(next = head) #实际上在head前面加了一个节点,从而防止head本身只有一个节点的删头问题,因为head的头部相当于dummy的中间(正数第一个节点)
fast = dummy
slow = dummy
for i in range(n): #与最后一个的间隔为n,此时slow指向的是倒数第n+1个
fast = fast.next
while fast.next!=None: #当fast没到尾部,退出时,fast为尾
fast = fast.next
slow = slow.next
slow.next = slow.next.next #将倒数第n+1个节点的指针赋成倒数第n个节点的指针就可以了
return dummy.next #去掉在head处加的头部
算法与数据结构打卡【day5】
3.23
链表
876. 链表的中间结点
给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例
输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。
方法1,遍历两遍
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
n = 0
p = head
while p: #计算节点个数 , 注意head本身也算
n=n+1
p = p.next
mid_n = n//2
Q = head #已经算作第一个节点了
for i in range(mid_n): #加上前面的走的那一步,就是n//2+1
Q = Q.next
return Q
方法2,使用数组
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
ls = [head]
while ls[-1].next: #下一个节点不是None就放入,放入后下次检验的是刚刚放入的节点的下一节点
ls.append(ls[-1].next)
return(ls[len(ls)//2]) #注意这里实际上是len//2+1个,index为len//2
方法3,快慢指针,快指针是慢指针的2倍速
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast = head
slow = head
while fast and fast.next: #保证
slow = slow.next
fast = fast.next.next
return slow
滑动窗口
3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
方法1,双指针,滑动窗口
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
left,ans = 0,0
while left<len(s):
cur = 1 #初始化当前长度
if left+1<len(s):
right = left+1
s_set = {s[left]} #初始化当前集合(无需标号时,用集合比列表更快)
while right<len(s) and s[right] not in s_set:
s_set.add(s[right])
cur = cur+1
right = right+1
ans = max(ans,cur)
left = left +1
return ans
方法2,使用哈希表、滑动变长区间
key为字符,value存储字符出现的最后一次位置,
保证[i,j]中没有重复字符串
class Solution:
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
st = {}
i, ans = 0, 0
for j in range(len(s)):
if s[j] in st:
i = max(st[s[j]]+1, i) #设为新的区间左端(舍弃经过的重复字符,保证区间左端到右端位置里没有重复字符窜)
st[s[j]] = j #记录当前字符串出现的位置index
ans = max(ans, j - i+1) #区间长度
return ans
矩阵
36. 有效的数独
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
注意:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 ‘.’ 表示。
输入:board =
[["5","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
输出:true
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
"""
:type board: List[List[str]]
:rtype: bool
"""
for i in range(3):
s_set = {}
for j in range(3):
nums = [board[p][q] for p in range(i*3,i*3+3) for q in range(j*3,j*3+3) if board[p][q]!="."]
if len(nums)!=len(set(nums)):
return False
for i in range(9):
nums_row = board[i]
nums_row = list(filter(lambda x:x!=".",nums_row))
if len(nums_row)!=len(set(nums_row)):
return False
nums_col = [board[j][i] for j in range(9) if board[j][i]!="."]
if len(nums_col)!=len(set(nums_col)):
return False
return True
73. 矩阵置零
方法1,时间复杂度O(mn),空间复杂度O(mn)
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
set_0 = []
for i in range(m):
for j in range(n):
if matrix[i][j]==0:
set_0.append((i,j))
for i,j in set_0:
matrix[i] = [0]*n
for k in range(m):
matrix[k][j]=0
方法2,时间复杂度O(mn),空间复杂度O(m+n)
因为行列都要清零,所以直接储存要清零的行和列就行
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
rows=set()
cols=set()
for i in range(m):
for j in range(n):
if matrix[i][j]==0:
rows.add(i)
cols.add(j)
for row in rows:
matrix[row] = [0]*n
for col in cols:
for k in range(m):
matrix[k][col]=0
方法3,时间复杂度O(mn),空间复杂度O(1)
利用矩阵第一行和第一列存储行列是否为零的信息
利用额外的两个标量存储第一行和第一列的信息
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
find_row = any( matrix[0][j]==0 for j in range(n))
find_col = any( matrix[i][0]==0 for i in range(m))
for i in range(1,m): #注意要从index==1开始
for j in range(1,n):
if matrix[i][j]==0:
matrix[i][0] = matrix[0][j] = 0 #如果当前元素为零,则这一行和列都为零,在第一行和第一列做标记
for i in range(1,m):
for j in range(1,n):
if matrix[i][0]==0 or matrix[0][j]==0: #根据标记置零
matrix[i][j]=0
if find_row :
matrix[0] = [0]*n
if find_col:
for k in range(m):
matrix[k][0]=0
算法与数据结构打卡【day6】
3.24
字符串/ 哈希映射 /哈希表
387. 字符串中的第一个唯一字符
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
方法1,哈希映射,二次遍历
class Solution:
def firstUniqChar(self, s: str) -> int:
dic = {}
for i in range(len(s)):
if dic.get(s[i] )==None:
dic[s[i]]=i #存储index
else:
dic[s[i]]=-1
ans = len(s)
flag = False
for k,v in dic.items():
if v!=-1:
flag = True
ans = min(ans,v)
if flag:
return ans
else:
return -1
方法2,字母表
唯一出现的元素左起index和右起index相同
class Solution:
def firstUniqChar(self, s: str) -> int:
min_index = len(s)
for i in 'abcdefghijklmnopqrxtuvwxyz':
left = s.find(i)
if(i in s and s.rfind(i)==left):
min_index = min(min_index, left)
return min_index if(min_index<len(s)) else -1
383. 赎金信
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/ransom-note
方法1,字符统计
哈希表,二次遍历
第一遍记录表值,第二遍减去表中值
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
m_has={}
for m in magazine:
if m_has.get(m)==None:
m_has[m]=1
else:
m_has[m]=m_has[m]+1
for r in ransomNote:
if m_has.get(r)==None or m_has[r]==0:
return False
else:
m_has[r]=m_has[r]-1
return True
简化以上代码
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
if len(ransomNote)>len(magazine):
return False
else:
return not collections.Counter(ransomNote)-collections.Counter(magazine) #返回字典差集,如果有元素,说明ransomNote里面有magazine中没有的元素,所以not true == false, 反之没有元素说明magazine包含ransomNote, not 空 == true
#注意 not {}或not () 或 not 1 都是true
这里Counter是collections里的一个计数器,是字典的子类
用法:
import collections
print(collections.Counter("abcdsbb"))
print(collections.Counter("abcdsbb")-collections.Counter("abcds"))
print(collections.Counter("abcds")-collections.Counter("abcds"))
输出:
Counter({'b': 3, 'a': 1, 'c': 1, 'd': 1, 's': 1})
Counter({'b': 2})
Counter()
242. 有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
return collections.Counter(s)==collections.Counter(t)
567. 字符串的排列
给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
换句话说,s1 的排列之一是 s2 的 子串 。
示例 1:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
方法1,哈希映射(字典)
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
if len(s1)>len(s2):
return False
count_s1 = collections.Counter(s1)
n = len(s1)
for i in range(len(s2)):
if count_s1.get(s2[i])!=None:
if count_s1 == collections.Counter(s2[i:(i+n)]):
return True
return False
优化以上算法
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
if len(s1)>len(s2):
return False
count_s1 = collections.Counter(s1)
n = len(s1)
count_s2 = collections.Counter(s2[:n])
if count_s1 == count_s2:
return True
for i in range(n,len(s2)): #以长度为n的窗口滑动, i代表区间右端点
count_s2[s2[i-n]] = count_s2[s2[i-n]]-1
if count_s2.get(s2[i])==None:
count_s2[s2[i]] = 1
else:
count_s2[s2[i]] = count_s2[s2[i]]+1
if count_s1 == count_s2:
return True
return False
方法2,哈希表
26个字母,index表示第几个字母,value表示个数
原理同上
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
n, m = len(s1), len(s2)
a = ord("a") # 97
if n > m:
return False
charCount1 = [0]*26
charCount2 = [0]*26
for i in range(n):
charCount1[ord(s1[i])-a] += 1
charCount2[ord(s2[i])-a] += 1
if charCount1 == charCount2:
return True
for j in range(n,m):
charCount2[ord(s2[j])-a] += 1
charCount2[ord(s2[j-n])-a] -= 1
if charCount1 == charCount2:
return True
return False