数据结构学习计划_入门_第一天_数组
217.存在重复元素
https://leetcode.cn/problems/contains-duplicate/
数组 哈希表 排序
给你一个整数数组 nums 。如果任一值在数组中出现至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
方法一:dict()
思路:快递检索数据,用哈希表。python可以用字典来实现哈希表。
循环遍历数组,并将数组元素存储在字典中,判断是否存在相同元素,存在则返回True;若循环结束,不存在相同元素,则返回False;
from typing import List
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
record = {} # records = dict() {1:0,3:1,4:2}
for idx, val in enumerate(nums):
if val not in record: # 指的是键值,相当于records.keys()
record[val] = idx
else:
return True
return False
if __name__ == '__main__':
nums = [1,3,4,3,2,4,2]
so = Solution()
print(so.containsDuplicate(nums))
用判断值的方法,为什么会超出时间限制?
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
record = {}
for idx, val in enumerate(nums):
if val not in record.values():
record[idx] = val
else:
return True
return False
方法二:set()
set() 函数创建一个无序不重复元素集
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
record = set()
for num in nums:
if num in record:
return True
else:
record.add(num)
return False
方法三:暴力排序
class Solution:
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
53.最大子数组和
https://leetcode.cn/problems/maximum-subarray/
数组 分治 动态规划
给你一个整数数组nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。
方法一:动态规划
- 初始化: d p [ 0 ] = n u m s [ 0 ] dp[0] = nums[0] dp[0]=nums[0]
- 取当前元素之前的和的最大值: p r e S u m = m a x ( p r e S u m + n u m s [ i ] , n u m s [ i ] ) preSum = max(preSum+nums[i], nums[i]) preSum=max(preSum+nums[i],nums[i])
- 状态转移方程:
d p [ i ] = m a x ( d p [ i − 1 ] , p r e S u m ) dp[i] = max(dp[i - 1], preSum) dp[i]=max(dp[i−1],preSum)
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 动态规划思想
# 初始化
dp = [0] * len(nums)
dp[0] = nums[0] # 考虑len(nums)为1时,值为nums[0]
preSum = nums[0]
# 递推公式
for i in range(1, len(nums)):
preSum = max(preSum + nums[i], nums[i])
dp[i] = max(dp[i - 1], preSum)
return dp[-1] # 等价于dp[len(nums)-1]
方法二:动态规划_优化
创建一个数组用来存储子数组的和,取最大值。
dp[i-1]是到前一项为止的最大子序和,如果dp[i-1]大于0,则下一个子数组和是dp[i]和dp[i-1]相加,否则就是dp[i]本身。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = nums
for i in range(1, len(nums)):
dp[i] = dp[i] + max(dp[i-1], 0)
return max(dp)
数据结构学习计划_入门_第二天_数组
1.两数之和
https://leetcode.cn/problems/two-sum/
数组 哈希表
给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出和为目标值target
的那两个
整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
方法一:字典实现哈希表
返回下标
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = {}
for idx, val in enumerate(nums):
if target - val not in record:
record[val] = idx
else:
return [record[target - val], idx]
返回值
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = {}
for idx, val in enumerate(nums):
if target - val not in record:
record[val] = idx
else:
return [target - val, val]
复杂度分析:
时间复杂度:
O
(
N
)
O(N)
O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以
O
(
1
)
O(1)
O(1) 地寻找 target - x。
空间复杂度:
O
(
N
)
O(N)
O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。
方法二:暴力
class Solution(object):
def twoSum(self, nums, target):
n = len(nums)
for i in range(n):
for j in range(i+1,n):
if nums[i]+nums[j]==target:
return i,j
复杂度分析:
时间复杂度: O ( N 2 ) O(N^2) O(N2),其中 NN 是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。
空间复杂度: O ( 1 ) O(1) O(1)。
88.合并两个有序数组
https://leetcode.cn/problems/merge-sorted-array/
数组 双指针 排序
给你两个按
非递减顺序
排列的整数数组nums1
和nums2
,另有两个整数m
和n
,分别表示nums1
和nums2
中的元素数目。
请你合并
nums2 到 nums1 中,使合并后的数组同样按非递减顺序
排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组nums1
中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
题目分析:不同于合并有序链表,题目中说明要直接在nums1上修改,因此不能开辟一个新的空数组不断append那种经典的做法。
方法一:直接合并后排序
最直观的方法是先将数组 nums 2 \textit{nums}_2 nums2放进数组 nums 1 \textit{nums}_1 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.
"""
nums1[m:] = nums2
nums1.sort()
复杂度分析
时间复杂度:
O
(
(
m
+
n
)
log
(
m
+
n
)
)
O((m+n)\log(m+n))
O((m+n)log(m+n))。
排序序列长度为
m
+
n
m+n
m+n,套用快速排序的时间复杂度即可,平均情况为
O
(
(
m
+
n
)
log
(
m
+
n
)
)
O((m+n)\log(m+n))
O((m+n)log(m+n))。
空间复杂度:
O
(
log
(
m
+
n
)
)
O(\log(m+n))
O(log(m+n))。
排序序列长度为
m
+
n
m+n
m+n,套用快速排序的空间复杂度即可,平均情况为
O
(
log
(
m
+
n
)
)
O(\log(m+n))
O(log(m+n))。
方法二:双指针
思路:利用数组
nums
1
\textit{nums}_1
nums1与
nums
2
\textit{nums}_2
nums2已经被排序的性质。
为了利用这一性质,我们可以使用双指针方法。这一方法将两个数组看作队列,每次从两个数组头部取出比较小的数字放到结果中。为两个数组分别设置一个指针
p
1
p_1
p1与
p
2
p_2
p2来作为队列的头部指针。
tips:除了对比大小,还要考虑到p1=m和p2=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.
"""
# 双指针
sorted = []
p1 , p2 = 0, 0
while p1 < m or p2 < n:
if p1 == m:
sorted.append(nums2[p2])
p2 += 1
elif p2 == n:
sorted.append(nums1[p1])
p1 += 1
elif nums1[p1] < nums2[p2]:
sorted.append(nums1[p1])
p1 += 1
else:
sorted.append(nums2[p2])
p2 += 1
nums1[:] = sorted
数据结构学习计划_入门_第三天_数组
350.两个数组的交集 II
https://leetcode.cn/problems/intersection-of-two-arrays-ii/
数组 哈希表 双指针 二分查找
给你两个整数数组
nums1
和nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
我的思路:首先考虑哈希表,然后可以考虑排序和双指针
方法一:哈希表
1、用哈希表(字典)实现collections.Counter()
的功能,即返回键值为数组元素,值为数组元素个数的字典。
2、首先遍历nums1数组,即较短的数组,可以降低空间复杂度。定义一个字典count=dict()
记录nums1数组中的元素与元素个数。
3、遍历nums2数组,判断nums2数组中的元素是否在count中并且该元素个数不为0,如果是则将该元素记录到结果数组中,然后count中该元素的个数减1。
from collections import Counter
class Solution:
def intersect(self, nums1: List[int], nums: List[int]) -> List[int]:
# 降低空间复杂度,先遍历较短的数组,再遍历较长的数组得到交集
if len(nums1) > len(nums2):
return intersect(nums2, nums1)
# count = Counter(nums1)
count = {}
# 自己试着实现Counter的功能
for num in nums1:
if num in count:
count[num] += 1
else:
count[num] = 1
ans = []
for num in nums2:
if num in count and count[num]:
ans.append(num)
count[num] -= 1
return ans
方法二:利用Counter方法
import collections
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
num1 = collections.Counter(nums1)
num2 = collections.Counter(nums2)
num = num1 & num2 # &求两个字典中key的交集
return list(num.elements()) # Counter对象elements()方法
方法三:排序+双指针
如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。首先对两个数组进行排序,然后使用两个指针遍历两个数组。
初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位
,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
p1, p2 = 0, 0
ans = []
while p1 < len(nums1) and p2 < len(nums2):
if nums1[p1] < nums2[p2]:
p1 += 1
elif nums1[p1] == nums2[p2]:
ans.append(nums1[p1])
p1 += 1
p2 += 1
else:
p2 += 1
return ans
推荐方法一,占用空间较少。
拓展:349.两个数组的交集
相当于350求交集需要去重
给定两个数组 nums1 和 nums2 ,返回
它们的交集
。输出结果中的每个元素一定是唯一
的。我们可以 不考虑输出结果的顺序 。
我的思路:1、字典记录数组元素的值和个数,然后判断两个字典的键值是否有相同;2、可以用set实现。
方法一:字典实现(哈希表)
# 运行时间很长,可能复杂度比较高
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
def Counter(nums):
count = {}
for num in nums:
if num in count:
count[num] += 1
else:
count[num] = 1
return count
# 去重
count1 = Counter(nums1)
count2 = Counter(nums2)
ans = count1.keys() & count2.keys() # 得到的是一个集合set
return list(ans)
方法二:用set实现:(官方解法一:两个集合)
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 先遍历短的数组,降低复杂度
if len(nums1) > len(nums2):
return self.intersection(nums2, nums1)
set1 = set(nums1)
set2 = set(nums2)
return [x for x in set1 if x in set2]
# ans = set1 & set2
# return list(ans)
复杂度分析
- 时间复杂度: O ( m + n ) O(m+n) O(m+n),其中 m 和 n 分别是两个数组的长度。使用两个集合分别存储两个数组中的元素需要 O ( m + n ) O(m+n) O(m+n) 的时间,遍历较小的集合并判断元素是否在另一个集合中需要 O ( min ( m , n ) ) O(\min(m,n)) O(min(m,n)) 的时间,因此总时间复杂度是 O ( m + n ) O(m+n) O(m+n)。
- 空间复杂度: O ( m + n ) O(m+n) O(m+n),其中 m 和 n 分别是两个数组的长度。空间复杂度主要取决于两个集合。
方法三:排序+双指针(官方解法二)
加入答案的数组的元素一定是递增的,为了保证加入元素的唯一性,我们需要额外记录变量 p r e pre pre 表示上一次加入答案数组的元素。
如果两个数字相等,且该数字不等于 p r e pre pre ,将该数字添加到答案并更新变量 p r e pre pre ,同时将两个指针都右移一位。……
变量 p r e pre pre 可以用 a n s [ − 1 ] ans[-1] ans[−1] 来表示,添加元素时自动更新。
重点:保证唯一性:if not ans or nums[p1] != ans[-1]:
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
ans = []
p1, p2 = 0, 0
while p1 < len(nums1) and p2 < len(nums2):
if nums1[p1] < nums2[p2]:
p1 += 1
elif nums1[p1] == nums2[p2]:
# 保证加入元素的唯一性!
if not ans or nums1[p1] != ans[-1]:
ans.append(nums1[p1])
p1 += 1
p2 += 1
else:
p2 += 1
return ans
补充:求交集,差集,并集。
在python3环境下,字典的keys()或者items()可以求交集、差集和并集,values()不可以。如果相对values()方法进行集合操作,必须先将值转化为集合。set(a.values())
a = {'x' : 1, 'y' : 2, 'z' : 3}
b = {'w' : 10, 'x' : 11, 'y' : 2}
print('Common keys:', a.keys() & b.keys()) # & 求交集
print('Keys in a not in b:', a.keys() - b.keys()) # - 求差集
print('Keys in a or in b',a.keys() | b.keys()) # | 求并集
print('(key,value) pairs in common:', a.items() & b.items())
print('(key,value) pairs in a not in b:', a.items() - b.items())
print('(key,value) pairs in a or in b:', a.items() | b.items())
121. 买卖股票的最佳时机
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/
数组 动态规划
给定一个数组
prices
,它的第i
个元素prices[i]
表示一支给定股票第i
天的价格。你只能选择某一天
买入这只股票,并选择在未来的某一个不同的日子
卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
方法一:一次遍历(可以理解为动态规划的优化)
思路(官方题解方法二:一次遍历)
遍历一遍数组,计算每次到当天为止
的最小股票价格和最大利润。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 一次遍历(可以理解为动态规划的优化)
minPrice = float("inf") # 定义一个极大值
# minprice = int(1e9)
maxProfit = 0
for price in prices:
minPrice = min(price, minPrice)
maxProfit = max(price - minPrice, maxProfit)
return maxProfit
复杂度分析
时间复杂度:
O
(
n
)
\mathcal{O}(n)
O(n),遍历了一遍数组。
空间复杂度:
O
(
1
)
\mathcal{O}(1)
O(1),使用了有限的变量。
方法二:动态规划
动态规划一般分为一维、二维、多维(使用状态压缩),对应形式为 d p ( i ) dp(i) dp(i)、 d p ( i ) ( j ) dp(i)(j) dp(i)(j)、二进制 d p ( i ) ( j ) dp(i)(j) dp(i)(j)。
1. 动态规划做题步骤
- 明确 d p ( i ) dp(i) dp(i) 应该表示什么(二维情况: d p ( i ) ( j ) dp(i)(j) dp(i)(j));
- 根据 d p ( i ) dp(i) dp(i) 和 d p ( i − 1 ) dp(i-1) dp(i−1) 的关系得出状态转移方程;
- 确定初始条件,如 d p ( 0 ) dp(0) dp(0)。
2. 本题思路
其实方法一的思路不是凭空想象的,而是由动态规划的思想演变而来。这里介绍一维动态规划思想。
d
p
[
i
]
dp[i]
dp[i] 表示前
i
i
i 天的最大利润,因为我们始终要使利润最大化,则:
d
p
[
i
]
=
m
a
x
(
d
p
[
i
−
1
]
,
p
r
i
c
e
s
[
i
]
−
m
i
n
p
r
i
c
e
)
dp[i] = max(dp[i-1], prices[i]-minprice)
dp[i]=max(dp[i−1],prices[i]−minprice)
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 一维动态规划思想
n = len(prices)
if n == 0: return 0 # 边界条件
# 1、初始化dp[0]
dp = [0] * n
dp[0] = 0
minPrice = prices[0]
# 2、递推公式
for i in range(1, n):
minPrice = min(minPrice, prices[i])
dp[i] = max(dp[i - 1], prices[i] - minPrice)
return dp[-1] # 等价于 return dp[n - 1]
# 返回 dp数组的最后一项;如果直接取 dp(n)是越界的
数据结构学习计划_入门_第四天_数组
27.移除元素
https://leetcode.cn/problems/remove-element/
数组 双指针
给你一个数组
num
和一个值val
,你需要原地
移除所有数值等于val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用O(1)
额外空间并原地
修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
快慢指针法:定义两个指针,快指针 fast \textit{fast} fast 指向当前将要处理的元素,慢指针 slow \textit{slow} slow 指向下一个将要赋值的位置。
-
如果快指针指向的元素不等于 val \textit{val} val,它一定是输出数组的一个元素,我们就将快指针指向的元素复制到慢指针位置,然后将快慢指针同时右移;
-
如果快指针指向的元素等于 val \textit{val} val,它不能在输出数组里,此时慢指针不动,快指针右移一位。
class Solution():
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
if n == 0: return 0
slow, fast = 0, 0
while slow < n and fast < n: # 其实只要fast < n就行了
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
复杂度分析
时间复杂度:
O
(
n
)
O(n)
O(n),其中 n 为序列的长度。我们只需要遍历该序列至多两次。
空间复杂度:
O
(
1
)
O(1)
O(1)。我们只需要常数的空间保存若干变量。
209.长度最小的数组
https://leetcode.cn/problems/minimum-size-subarray-sum/
数组 滑动窗口
给定一个含有
n
个正整数的数组和一个正整数target
。
找出该数组中满足其和≥ target
的长度最小的连续子数组
[ n u m s l , n u m s l + 1 , . . . , n u m s r − 1 , n u m s r ] [nums_l, nums_{l+1}, ..., nums_{r-1}, nums_r] [numsl,numsl+1,...,numsr−1,numsr],并返回其长度。如果不存在符合条件的子数组,返回 0 。
方法一:暴力法
需要思考,双层循环,求长度最小。
方法二:滑动窗口法(双指针)
滑动窗口法也可以理解为双指针,两个指针为滑动窗口的起始位置和终止位置,
实现滑动窗口,主要确定如下三点:
-
窗口内是什么?
-
如何移动窗口的起始位置?
-
如何移动窗口的结束位置?
-
窗口就是
满足其和 ≥ s
的长度最小的连续
子数组。 -
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
-
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于窗口的起始位置如何移动。
每一轮迭代,将 nums [ e n d ] \text{nums}[end] nums[end] 加到 sum \textit{sum} sum,如果 sum ≥ s \textit{sum} \ge s sum≥s,则更新子数组的最小长度(此时子数组的长度是 end − start + 1 \textit{end}-\textit{start}+1 end−start+1),然后将 nums [ s t a r t ] \text{nums}[start] nums[start] 从 sum \textit{sum} sum 中减去并将 start \textit{start} start 右移,直到 sum < s \textit{sum} < s sum<s,在此过程中同样更新子数组的最小长度。在每一轮迭代的最后,将 end \textit{end} end 右移。
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
# 滑动窗口法(双指针)
res = float("inf")
Sum = 0
index = 0 # 滑动窗口的起始位置
for i in range(len(nums)): # 滑动窗口的终止位置
Sum += nums[i]
while Sum >= target:
res = min(res, i - index + 1)
Sum -= nums[index]
index += 1
return 0 if res == float("inf") else res
拓展:
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和=target
的长度最小的连续子数组。
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
# 滑动窗口法(双指针)
res = float("inf")
Sum = 0
index = 0 # 滑动窗口的起始位置
for i in range(len(nums)): # 滑动窗口的终止位置
Sum += nums[i]
while Sum > target: # 大于target时向前滑动
Sum -= nums[index]
index += 1
while Sum == target: # 等于tartget时,记录子数组长度
res = min(res, i - index + 1)
Sum -= nums[index]
index += 1
return 0 if res == float("inf") else res
566.重塑矩阵
https://leetcode.cn/problems/reshape-the-matrix/
数组 矩阵 模拟
可以直接从二维数组nums 得到 r 行 c 列的重塑矩阵:
- 设 nums 本身为 m 行 n 列,如果 m n ≠ r c mn \neq rc mn=rc,那么二者包含的元素个数不相同,因此无法进行重塑;
- 否则,对于
x
∈
[
0
,
m
n
)
x \in [0, mn)
x∈[0,mn),第 x 个元素在 nums 中对应的下标为
(
x
/
n
,
x
%
n
)
(x ~/~ n, x~\%~ n)
(x / n,x % n),而在新的重塑矩阵中对应的下标为
(
x
/
c
,
x
%
c
)
(x ~/~ c, x~\%~ c)
(x / c,x % c)。我们直接进行赋值即可。
重点:对于 x ∈ [ 0 , m n ) x \in [0, mn) x∈[0,mn),第 x 个元素在 nums 中对应的下标为 ( x / n , x % n ) (x ~/~ n, x~\%~ n) (x / n,x % n),而在新的重塑矩阵中对应的下标为 ( x / c , x % c ) (x ~/~ c, x~\%~ c) (x / c,x % c)。
class Solution:
def matrixReshape(self, mat: List[List[int]], r: int, c: int) -> List[List[int]]:
m, n = len(mat), len(mat[0])
if m * n != r * c:
return mat
ans = [[0] * c for _ in range(r)]
for x in range(m * n):
ans[x // c][x % c] = mat[x // n][x % n]
return ans
118.杨辉三角
https://leetcode.cn/problems/pascals-triangle/
数组 动态规划
给定一个非负整数
numRows
,生成「杨辉三角」的前numRows
行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
每个数字等于上一行的左右两个数字之和,可用此性质写出整个杨辉三角。即第 n行的第 i 个数等于第 n-1 行的第 i-1 个数和第 i 个数之和。这也是组合数的性质之一,即 C n i = C n − 1 i + C n − 1 i − 1 \mathcal{C}_n^i=\mathcal{C}_{n-1}^i+\mathcal{C}_{n-1}^{i-1} Cni=Cn−1i+Cn−1i−1
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
res = []
for i in range(numRows):
row = []
for j in range(0, i+1): # 第i行
if j == 0 or j == i:
row.append(1) # 第i行的第一个数和最后一个数为1
else: # 第i-1行的第j-1个数和第j个数之和
row.append(res[i-1][j-1] + res[i-1][j])
res.append(row)
return res
数据结构学习计划_入门_第五天_数组
数组 哈希表 矩阵
36.有效的数独
https://leetcode.cn/problems/valid-sudoku/
数组 哈希表 矩阵
请你判断一个 9 x 9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
1、数字 1-9 在每一行只能出现一次。
2、数字 1-9 在每一列只能出现一次。
3、数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
**哈希表是怎么记录的:**遍历数独,如果出现了数字num,则创建的哈希表中在num - 1的索引位置加1,代表这个数字出现过一次。
注:定义9个3x3的三维数组,注意数组的索引范围,subBoxs[x][y][z],x可以取0到8,y可以取值0,1,2,z可以取值0,1,2。
三维数组被绕晕了
用二维数组表示3x3
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
rows = [[0] * 9 for _ in range(9)]
cols = [[0] * 9 for _ in range(9)]
subBoxs = [[0] * 9 for _ in range(9)]
for i in range(9):
for j in range(9):
if board[i][j] != '.':
num = int(board[i][j]) - 1
index = (i // 3) * 3 + j // 3
rows[i][num] += 1
cols[j][num] += 1
subBoxs[index][num] += 1
if rows[i][num] > 1 or cols[j][num] > 1 or subBoxs[index][num] > 1:
return False
return True
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
rows = [[0] * 9 for _ in range(9)]
cols = [[0] * 9 for _ in range(9)]
subBoxs = [[0] * 9 for _ in range(9)]
for i in range(9):
for j in range(9):
if board[i][j] != '.':
num = int(board[i][j]) - 1
index = (i // 3) * 3 + j // 3
if rows[i][num] or cols[j][num] or subBoxs[index][num]:
return False
rows[i][num] = cols[j][num] = subBoxs[index][num] = 1
return True
73.矩阵置零
参考官方解法
方法一:使用标记数组
思路和算法:
我们可以用两个标记数组分别记录每一行和每一列是否有零出现。
具体地,我们首先遍历该数组一次,如果某个元素为 0,那么就将该元素所在的行和列所对应标记数组的位置置为
true
\text{true}
true。最后我们再次遍历该数组,用标记数组更新原数组即可。
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m, n = len(matrix), len(matrix[0])
row, col = [False] * m, [False] * n
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
row[i] = col[j] = True
for i in range(m):
for j in range(n):
if row[i] or col[j]:
matrix[i][j] = 0
方法二:使用两个标记变量
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
m, n = len(matrix), len(matrix[0])
flag_col0 = any(matrix[i][0] == 0 for i in range(m))
flag_row0 = any(matrix[0][j] == 0 for j in range(n))
for i in range(1, m):
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 flag_col0:
for i in range(m):
matrix[i][0] = 0
if flag_row0:
for j in range(n):
matrix[0][j] = 0
方法三:使用一个标记变量
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
m, n = len(matrix), len(matrix[0])
flag_col0 = False
for i in range(m):
if matrix[i][0] == 0:
flag_col0 = True
for j in range(1, n):
if matrix[i][j] == 0:
matrix[i][0] = matrix[0][j] = 0
for i in range(m - 1, -1, -1):
for j in range(1, n):
if matrix[i][0] == 0 or matrix[0][j] == 0:
matrix[i][j] = 0
if flag_col0:
matrix[i][0] = 0。