LeetCode Cookbook 数组习题(7)
篇接上回,开始!
766. 托普利茨矩阵
题目链接:766. 托普利茨矩阵
题目大意:给你一个 m x n 的矩阵 matrix 。如果这个矩阵是托普利茨矩阵,返回 true ;否则,返回 false 。如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是 托普利茨矩阵 。
例如:
输入:matrix = [[1,2,3,4],[5,1,2,3],[9,5,1,2]]
输出:true
解释:在上述矩阵中, 其对角线为:
"[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]"。
各条对角线上的所有元素均相同, 因此答案是 True 。
解题思路:循环从下标1开始,此题不难,双重循环做就可以了。
class Solution:
def isToeplitzMatrix(self, matrix: List[List[int]]) -> bool:
m,n = len(matrix),len(matrix[0])
for i in range(1,m):
for j in range(1,n):
if matrix[i-1][j-1] != matrix[i][j]:
return False
return True
832. 翻转图像
题目链接:832. 翻转图像
题目大意:给定一个 n x n 的二进制矩阵 image ,先 水平 翻转图像,然后 反转 图像并返回 结果 。
- 水平翻转图片就是将图片的每一行都进行翻转,即逆序。
- 反转图片的意思是图片中的 0 全部被 1 替换, 1 全部被 0 替换。
例如:
输入:image = [[1,1,0],[1,0,1],[0,0,0]]
输出:[[1,0,0],[0,1,0],[1,1,1]]
解释:首先翻转每一行: [[0,1,1],[1,0,1],[0,0,0]];
然后反转图片: [[1,0,0],[0,1,0],[1,1,1]]
解题思路: 正常做就行,此题不难。
class Solution:
def flipAndInvertImage(self, image: List[List[int]]) -> List[List[int]]:
m,n = len(image),len(image[0])
for i in range(m):
image[i] = image[i][::-1]
for j in range(n):
image[i][j] ^= 1
return image
888. 公平的糖果交换
题目链接:888. 公平的糖果交换
题目大意:爱丽丝和鲍勃拥有不同总数量的糖果。给你两个数组 aliceSizes 和 bobSizes ,aliceSizes[i] 是爱丽丝拥有的第 i 盒糖果中的糖果数量,bobSizes[j] 是鲍勃拥有的第 j 盒糖果中的糖果数量。两人想要互相交换一盒糖果,这样在交换之后,他们就可以拥有相同总数量的糖果。一个人拥有的糖果总数量是他们每盒糖果数量的总和。返回一个整数数组 answer,其中 answer[0] 是爱丽丝必须交换的糖果盒中的糖果的数目,answer[1] 是鲍勃必须交换的糖果盒中的糖果的数目。如果存在多个答案,你可以返回其中 任何一个 。题目测试用例保证存在与输入对应的答案。
例如:
输入:aliceSizes = [1,1], bobSizes = [2,2]
输出:[1,2]
解题思路:这行 rec = set(aliceSizes)
还是很有必要的,可以省下不少时间;
- 推导主要是两个数组与两个输出之间的关系,简要推导如下:
sum(A)-x+y = sum(B)-y+x ; x = y + (sum(A)-sum(B))//2
class Solution:
def fairCandySwap(self, aliceSizes: List[int], bobSizes: List[int]) -> List[int]:
delta = (sum(aliceSizes)-sum(bobSizes))//2
rec = set(aliceSizes)
'''
sum(A)-x+y = sum(B)-y+x
x = y + (sum(A)-sum(B))//2
'''
for y in bobSizes:
x = y + delta
if x in rec:
return [x,y]
break
891. 子序列宽度之和
题目链接:891. 子序列宽度之和
题目大意:一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。给你一个整数数组 nums ,返回 nums 的所有非空 子序列 的 宽度之和 。由于答案可能非常大,请返回对 109 + 7 取余 后的结果。子序列 定义为从一个数组里删除一些(或者不删除)元素,但不改变剩下元素的顺序得到的数组。例如,[3,6,2,7] 就是数组 [0,3,1,6,2,2,7] 的一个子序列。
例如:
输入:nums = [2,1,3]
输出:6
解释:子序列为 [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3] 。
相应的宽度是 0, 0, 0, 1, 1, 2, 2 。宽度之和是 6 。
解题思路: 脑壳疼的一道题,需要找到数学规律,公式法来解决。公式就是下面这幅图片,选自GF题解
(一)精简的
class Solution:
def sumSubseqWidths(self, nums: List[int]) -> int:
MOD = 10**9 + 7
nums.sort()
ans,n,p = 0,len(nums),1
for i in range(n):
ans = (ans + (nums[i]-nums[n-1-i])*p)%MOD
p = (p<<1)%MOD
return ans
(二)展开的
mul = [1]
MOD = 10**9 + 7
for i in range(99999):
mul.append(mul[-1]*2 % MOD)
class Solution:
def sumSubseqWidths(self, nums: List[int]) -> int:
n = len(nums)
ans = 0
nums.sort()
for i in range(n):
ans += nums[i]*mul[i]
ans -= nums[i]*mul[n-1-i]
ans %= MOD
return ans
896. 单调数列
题目链接:896. 单调数列
题目大意: **如果数组是单调递增或单调递减的,那么它是 单调 的。
- 如果对于所有 i <= j,nums[i] <= nums[j],那么数组 nums 是单调递增的。
- 如果对于所有 i <= j,nums[i]> = nums[j],那么数组 nums 是单调递减的。
- 当给定的数组 nums 是单调数组时返回 true,否则返回 false。
例如:
输入:nums = [1,2,2,3]
输出:true
解题思路: (一)两次遍历;(二)递增递减计数。
(一)两次遍历
class Solution:
def isMonotonic(self, A: List[int]) -> bool:
return sorted(A) in (A, A[::-1])
(二)递增递减计数
class Solution:
def isMonotonic(self, nums: List[int]) -> bool:
n = len(nums)
inc,dec = 0,0
for i in range(1,n):
if nums[i-1] <= nums[i]:
inc += 1
if nums[i-1] >= nums[i]:
dec += 1
# print(inc,dec)
return max(inc,dec) == n-1
907. 子数组的最小值之和 **
题目链接:907. 子数组的最小值之和
题目大意:给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。
- 由于答案可能很大,因此 返回答案模 10^9 + 7 。
例如:
输入:arr = [3,1,2,4]
输出:17
解释:
子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。
最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。
解题思路:最小栈 典型题!涉及连续数组时,不是 动态规划 就是 最小栈。
class Solution:
def sumSubarrayMins(self, arr: List[int]) -> int:
MOD = 10**9+7
stack = []
ans,tmp = 0,0
for j,y in enumerate(arr):
count = 1
while stack and stack[-1][0] >= y:
x,c = stack.pop()
count += c
tmp -= x*c
stack.append((y,count))
tmp += y*count
ans += tmp
return ans % MOD
914. 卡牌分组
题目链接: 914. 卡牌分组
题目大意: 给定一副牌,每张牌上都写着一个整数。此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组:
- 每组都有 X 张牌。
- 组内所有的牌上都写着相同的整数。
- 仅当你可选的 X >= 2 时返回 true。
例如:
输入:deck = [1,2,3,4,4,3,2,1]
输出:true
解释:可行的分组是 [1,1],[2,2],[3,3],[4,4]
解题思路: 先计数 再求取所有数字的个数的公约数,大于等于2 即返回 True。这里有个 reduce() 函数可以学习一下。
class Solution:
def hasGroupsSizeX(self, deck: List[int]) -> bool:
nums = collections.Counter(deck).values()
nums = list(set(nums))
n = len(nums)
if n == 1 and nums[0] >= 2: return True
factor = nums[0]
for i in range(1,n):
factor = math.gcd(factor,nums[i])
return factor >= 2
# reduce() 省下了 写动态规划部分的代码
# nums = collections.Counter(deck).values()
# return reduce(gcd, counter) >= 2
918. 环形子数组的最大和
题目链接:918. 环形子数组的最大和
题目大意: 给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 。
- 环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。
- 子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], …, nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。
例如:
输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3
解题思路:学习 这篇题解 我花一遍就看懂的题解,你也可以!,需要转化思维啊! 如果环形数组的最大子数组,不就意味着 数组的总和减去数组的最小子数组 吗?如下图所示,这道题太巧妙了。
class Solution:
def maxSubarraySumCircular(self, nums: List[int]) -> int:
maxSum,minSum,Sum,curMax,curMin = nums[0],nums[0],0,0,0
for num in nums:
Sum += num
curMax = max(curMax+num,num)
maxSum = max(maxSum,curMax)
curMin = min(curMin+num,num)
minSum = min(minSum,curMin)
print(maxSum,minSum)
return max(maxSum,Sum-minSum) if maxSum>0 else maxSum
922. 按奇偶排序数组 II
题目链接:922. 按奇偶排序数组 II
题目大意:给定一个非负整数数组 nums, nums 中一半整数是 奇数 ,一半整数是 偶数 。对数组进行排序,以便:
- 当 nums[i] 为奇数时,i 也是 奇数 ;
- 当 nums[i] 为偶数时, i 也是 偶数 。
- 你可以返回 任何满足上述条件的数组作为答案 。
例如:
输入:nums = [4,2,5,7]
输出:[4,5,2,7]
解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。
解题思路:简单的双指针题目,不多解释了。
class Solution:
def sortArrayByParityII(self, nums: List[int]) -> List[int]:
n = len(nums)
if n == 0: return nums
i,j = 0,1
while i<n and j<n:
if nums[i] % 2 != 0:
while j<n and nums[j] % 2 != 0:
j += 2
nums[i],nums[j] = nums[j],nums[i]
# print(nums)
i += 2
return nums
923. 三数之和的多种可能 **
题目链接:923. 三数之和的多种可能
题目大意: 给定一个整数数组 arr ,以及一个整数 target 作为目标值,返回满足 i < j < k 且 arr[i] + arr[j] + arr[k] == target 的元组 i, j, k 的数量。由于结果会非常大,请返回 109 + 7 的模。
例如:
输入:arr = [1,1,2,2,3,3,4,4,5,5], target = 8
输出:20
解释:按值枚举(arr[i], arr[j], arr[k]):
(1, 2, 5) 出现 8 次;(1, 3, 4) 出现 8 次;
(2, 2, 4) 出现 2 次;(2, 3, 3) 出现 2 次。
解题思路: 双指针变形题 ,三个指针考虑三种情况:(1)三个数都不一样;(2)两个数一样;(3)三个数都一样。需要考虑一些组合 combination 的公式,不难理解。
class Solution(object):
def threeSumMulti(self, A: List[int], target: int) -> int:
a_counter = [0] * 101
for x in A:
a_counter[x] += 1
ans = 0
kMod = 10**9 + 7
for i in range(target+1):
for j in range(i, target+1):
k = target - i - j
if k < 0 or k >= 101 or k < j:
continue
if not a_counter[i] or not a_counter[j] or not a_counter[k]: continue
if i == j and j == k:
ans += (a_counter[i]-2) * (a_counter[i]-1) * a_counter[i] / 6
elif i == j and j != k:
ans += a_counter[i] * (a_counter[i]-1) / 2 * a_counter[k]
elif i != j and j == k:
ans += a_counter[i] * (a_counter[j]-1) * a_counter[j] / 2
else:
ans += a_counter[i] * a_counter[j] * a_counter[k]
return int(ans % kMod)
977. 有序数组的平方
题目链接:977. 有序数组的平方
题目大意:给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的 平方 组成的新数组,要求也按 非递减顺序 排序。
例如:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
解题思路: 常规做法,先求平方在排序,补充一下 sort 默认升序。
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
return sorted(num*num for num in nums)
978. 最长湍流子数组
题目链接:978. 最长湍流子数组
题目大意:给定一个整数数组 arr ,返回 arr 的 最大湍流子数组的长度 。如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是 湍流子数组 。更正式地来说,当 arr 的子数组 A[i], A[i+1], …, A[j] 满足仅满足下列条件时,我们称其为湍流子数组:
-
若 i <= k < j :当 k 为奇数时, A[k] > A[k+1],且当 k 为偶数时,A[k] < A[k+1];
-
或 若 i <= k < j :当 k 为偶数时,A[k] > A[k+1] ,且当 k 为奇数时, A[k] < A[k+1]。
例如:
输入:arr = [9,4,2,10,7,8,8,1,9]
输出:5
解释:arr[1] > arr[2] < arr[3] > arr[4] < arr[5]
解题思路: 典型动态规划的题目,设置 up 和 down 两个动态数组 具体看代码。
(一)动态数组
class Solution:
def maxTurbulenceSize(self, arr: List[int]) -> int:
n = len(arr)
up,down = [1]*n,[1]*n
ans = 1
for i in range(1,n):
if arr[i-1] < arr[i]:
up[i] = down[i-1]+1
down[i] = 1
elif arr[i-1] > arr[i]:
down[i] = up[i-1] + 1
up[i] = 1
else:
down[i],up[i] = 1,1
ans = max(ans,max(up[i],down[i]))
# print(up,down)
return ans
(二)内存优化
class Solution:
def maxTurbulenceSize(self, arr: List[int]) -> int:
n = len(arr)
up,down = 1,1
ans = 0
if n == 1: return 1
for i in range(1,n):
if arr[i-1] < arr[i]:
up = down + 1
down = 1
elif arr[i-1] > arr[i]:
down = up + 1
up = 1
else:
up,down = 1,1
ans = max(ans,max(up,down))
return ans
985. 查询后的偶数和
题目链接:985. 查询后的偶数和
题目大意:给出一个整数数组 A 和一个查询数组 queries。对于第 i 次查询,有 val = queries[i][0], index = queries[i][1],我们会把 val 加到 A[index] 上。然后,第 i 次查询的答案是 A 中偶数值的和。
-
(此处给定的 index = queries[i][1] 是从 0 开始的索引,每次查询都会永久修改数组 A。)返回所有查询的答案。你的答案应当以数组 answer 给出,answer[i] 为第 i 次查询的答案。
例如:
输入:A = [1,2,3,4], queries = [[1,0],[-3,1],[-4,0],[2,3]]
输出:[8,6,2,4]
解释:开始时,数组为 [1,2,3,4]。
将 1 加到 A[0] 上之后,数组为 [2,2,3,4],偶数值之和为 2 + 2 + 4 = 8。
将 -3 加到 A[1] 上之后,数组为 [2,-1,3,4],偶数值之和为 2 + 4 = 6。
将 -4 加到 A[0] 上之后,数组为 [-2,-1,3,4],偶数值之和为 -2 + 4 = 2。
将 2 加到 A[3] 上之后,数组为 [-2,-1,3,6],偶数值之和为 -2 + 6 = 4。
解题思路: 设置好原先数组的 偶数和,然后 每次数组变动 进行判断
- 主要是两个条件判断:
- 如果调整后的数字是偶数,分两种情况,一种是变换前的数是奇数,另一种则是变换前是偶数;
- 如果调整后的数字是计数,则只需要确定变换前的数是否为偶数即可。
- 具体看代码
class Solution:
def sumEvenAfterQueries(self, nums: List[int], queries: List[List[int]]) -> List[int]:
ans = []
even = 0
for num in nums:
if num % 2 == 0:
even += num
for val,idx in queries:
tmp = nums[idx] + val
if tmp % 2 == 0:
if nums[idx] % 2 != 0:
even += tmp
else:
even += val
else:
if nums[idx] % 2 == 0:
even -= nums[idx]
nums[idx] += val
ans.append(even)
return ans
999. 可以被一步捕获的棋子数
题目链接:999. 可以被一步捕获的棋子数
题目大意:在一个 8 x 8 的棋盘上,有一个白色的车(Rook),用字符 ‘R’ 表示。棋盘上还可能存在空方块,白色的象(Bishop)以及黑色的卒(pawn),分别用字符 ‘.’,‘B’ 和 ‘p’ 表示。不难看出,大写字符表示的是白棋,小写字符表示的是黑棋。
- 车按国际象棋中的规则移动。东,西,南,北四个基本方向任选其一,然后一直向选定的方向移动,直到满足下列四个条件之一:
- 棋手选择主动停下来。
- 棋子因到达棋盘的边缘而停下。
- 棋子移动到某一方格来捕获位于该方格上敌方(黑色)的卒,停在该方格内。
- 车不能进入/越过已经放有其他友方棋子(白色的象)的方格,停在友方棋子前。
- 你现在可以控制车移动一次,请你统计有多少敌方的卒处于你的捕获范围内(即,可以被一步捕获的棋子数)。
例如:
输入:
[[".",".",".",".",".",".",".","."],
[".",".",".","p",".",".",".","."],
[".",".",".","R",".",".",".","p"],
[".",".",".",".",".",".",".","."],
[".",".",".",".",".",".",".","."],
[".",".",".","p",".",".",".","."],
[".",".",".",".",".",".",".","."],
[".",".",".",".",".",".",".","."]]
输出:3
解释:在本例中,车能够捕获所有的卒。
解题思路: 子函数编写,制定好四个移动方向,计算一下就可以了,尽量减少使用for的次数,使用 while 循环可以提升一定是时间空间利用率。
class Solution:
def numRookCaptures(self, board: List[List[str]]) -> int:
def capture(x: int,y: int,dx: int,dy: int) -> int:
while 0<=x<m and 0<=y<n and board[x][y] != 'B':
if board[x][y] == 'p':
return 1
x += dx
y += dy
return 0
directions = [(-1,0),(1,0),(0,-1),(0,1)]
m,n = len(board),len(board[0])
ans = 0
for i in range(m):
for j in range(n):
if board[i][j] == 'R':
for dx,dy in directions:
ans += capture(i+dx,j+dy,dx,dy)
return ans
总结
进度确实慢下来了,不能这样,想法子再提上去,果然我想的是对的:事实上让人心烦意乱的大多数不是事情本身的难易,而是人与人相处所带来的各种烦心事或者不痛快的回忆,在解决问题的同时看书是一个解决自我困扰的非常好的解决办法,昨天晚上听了柏拉图写得《苏格拉底的演讲》这本书的简介,真的让我很是震撼啊!“是因为做这件事情是虔诚的所以神喜欢,还是因为神喜欢这件事情所以这件事情是虔诚的。”好厉害啊!辩证的思维确实让人心底受到震撼,苏格拉底被神认为是最具有智慧的人,而他认为这是对的,因为他知道:神是无所不知的,而自己的聪慧是认识到自己的一无所知,所以人生在世就需要不断地 检省自我,通过不断的自我审视,不断地学习完善自我。这让我忽然想起《三体:黑死神永生》中丁仪的学生 白艾思(白Ice),丁仪和白lce都是非常具有智慧的人,一个面对了三体的 水滴 ,另一个则是面对了歌者的 二向箔,这两个传奇人物在面对未知的科技面前,他们是虔诚的,不是狂妄自大的,尤其是白lce在面对二向箔时说到:“我说别傲慢,弱小和无知不是生存的障碍,傲慢才是,想想水滴吧!”三体世界在毁灭地球上的人类时曾经说过,他们欣赏的是罗辑、云天明、维德、章北海这类人,他们不喜欢芸芸众生,我想可能他们是对的,身处于一个这样的环境,能做到的只能是搞明白自己应该怎么做,因为主动权在自我,而不断地自我反思与勤奋是不迷失自我的有力武器。
看完《三体》在惊叹于大刘塑造的数个有趣的人物时,无不在想如何成为一个有趣的人,我想这不是看一本书、赏一部电影、听几句话就能做到的,是一个时间尺度上的积累,由量变到质变的突破,我深度思考自己缺少的就是一个长时间的深度持久的学习一个方向的知识的能力,想要找到某种自我人生价值定位及社会认知感受,必须去努力追寻一种持久的精神毅力,那便是 不断地学习,持久的学习,这里的学习不是一个泛化的空洞词汇,我可以去看更多的有趣的书,思考书中的人物、故事情节带给我的思考,可以看更多有趣的电视剧、电影、动漫、纪录片等等的媒体作品思考其中给我的直观感受,甚至一些短视频笑一笑缓解一下无聊的时间空当,但最重要的是找准自己以后努力的明确方向,进行短时间(三至五年)的努力规划,学习 机器视觉 ,这是一个很大的范围,深度学习 同样也是一个很大的范围,我在其中要搞明白 自己到底要学习什么,学到什么程度,思考和认知到什么水平,这是一个我需要结合自我现有条件进行考虑的问题,我发自内心的想去 学习一些我现在所不能理解的东西,想去搞明白 我现在解决不了的问题,我想要 让我的思维继续地成长,而不是成为琐事揪心力竭的社畜,我对自己以后的认知定位 只有一个 —— 具有自我思考能力且可以付诸实施的平凡的人! 我将为这个目标而努力,而奋斗余生。
不要长时间让自我保持在傲慢无礼的状态,做回平常状态,当一个平凡的人,定期检省自我,让自我去尝试认识自我,而不是责令别人去了解自我,我思故我在,多思考多学习!最后,照常,努力,奋斗!