Abstract
一维数组
// 724.寻找数组的中心下标 left_sum right_sum 数组
int pivotIndex(vector<int>& nums);
// 1.2 搜索插入位置, 排序数组插入后有序性不变
// 1.3 合并区间
// 1.4 至少是其他数字两倍大的最大数
// 1.5 加一
双指针 -- 快慢指针
// 83.删除排序链表中的重复元素 --right_p != nullptr,先覆盖后移动
ListNode* deleteDuplicates(ListNode* head);
// 26.删除有序数组中的重复项 -- right < n,先移动后覆盖 nums[left]有效,nums[left+1]待填充
int removeDuplicates(vector<int>& nums);
// 27.移除元素(简单) -- right < n,先覆盖后移动 nums[left-1]有效,nums[left]待填充
int removeElement(vector<int>& nums, int val);
// 283.移动零 -- while 循环跳出后还需要注意left 和 right之间的元素赋值为0
void moveZeroes(vector<int>& nums);
// 长度最小的子数组 [slow, fast]
// 最大连续1的个数 [slow, fast]
双指针 -- 左右指针
// 1.两数之和 - 数组未排序,双重for循环o(n^2),找到break(仅一组答案)
vector<int> twoSum(vector<int>& nums, int target);
// 167.两数之和 II - 数组已经排序 左右双指针o(n), 下标从1开始
vector<int> twoSum2(vector<int>& nums, int target)
// 15.三数之和-排序, 两重for + 一重while, 控制复杂度为o(n^2)
vector<vector<int>> threeSum(vector<int>& nums) {
// 344.反转字符串 -- left right 原地操作
void reverseString(vector<char>& s);
// 647.回文子串(数) -- 中心扩展法(左右指针)/aux数组(暴力枚举)
int countSubstrings(string s);
// 5.最长回文子串 -- 中心扩展法(左右指针)/aux数组(暴力枚举)
string longestPalindrome(string s);
前缀和 pre_sum[i][j] 累加到[i-1][j-1]的前缀和
// 303. 区域和 检索
class NumArray; int sumRange(int left, int right);
// 304. 二维区域和检索
class NumMatrix; iint sumRegion(int row1, int col1, int row2, int col2);
差分数组
// 370. 区间加法 🔒
// 598. 区间加法 II -- 求最大数的量,维护所有操作的交集,不用二维差分数组
int maxCount(int m, int n, vector<vector<int>>& ops);
// 1109 航班预订 -- 差分数组,由各个航班预订列表统计各个航班的预订总数。
vector<int> corpFlightBookings(vector<vector<int>>& books, int n)
// 1094.拼车 -- 车上的人只会待到 to - 1站, diff[to] -= n, not diff[to+1] -=n
bool carPooling(vector<vector<int>>& trips, int capacity);
在一般的数组题中,只采用一个指针来进行迭代(from begin to end);有些时候需要用两个指针来迭代数组。
两类双针:
- 两个指针从不同的位置出发(速度相同):两端向中间、起点不同方向相同 【left right】典型题:翻转字符串(两头往中间),回文子串(中心扩展)
- 两个指针以不同的速度移动:一个指针快一些,另一个指针慢一些 【slow, fast】典型题:原地操作, 去重,去值,去零
Introduction
集合: 一个/多个确定的元素构成的整体。一般种类相同,当然也可以不同。集合中的元素没有顺序。很多编程语言中拥有一些数据结构,是在集合的概念中增加了一些规则而来的。无序。
列表:(又称线性列表)一种数据项构成的有限的序列。即按一定顺序排列的数据项的集合。有序,长度可变。列表常见的表现形式有:数组,链表,堆栈,队列。
–> 数组: 是一种最基本的数据结构,用于按顺序存储数据。数组中元素通过索引来实现读写操作。数组有一维数组,也有多维数组。一般编程语言中,数组具有固定容量,需要在初始化的时候确定数组大小,后续无法修改其长度。操作不是十分便利,于是大多编程语言提供了动态数组的数据结构,其大小可变。在Python中就没这么复杂,一个list数据结构就可以灵活满足众多需求。
数组基本操作:增、删、查、改
读:通过访问索引的方式来读取的,索引一般从 0 开始。访问的时间复杂度为o(1)。
1. 一维数组
1.1 寻找数组的中心索引
724.寻找数组的中心下标 – 给定一个整数类型的数组,编程实现返回数组的中心索引。中心索引的定义:中心索引左侧所有元素的和大于右侧所有元素的和。特殊情况:如果中心索引不存在,返回-1;如果存在多个中心所以,返回最左边的一个
定义两个数组:
l
e
f
t
_
s
u
m
[
i
]
=
∑
j
=
0
i
−
1
n
u
m
s
[
j
]
,
i
=
1
,
2
,
3
,
.
.
.
,
n
−
1
left\_sum[i]=\sum_{j=0}^{i-1}nums[j],\ \ i=1,2,3,...,n-1
left_sum[i]=j=0∑i−1nums[j], i=1,2,3,...,n−1
r i g h t _ s u m [ i ] = ∑ j = i + 1 n − 1 n u m s [ j ] , i = n − 2 , n − 3 , . . . , 0 right\_sum[i]=\sum_{j=i+1}^{n-1}nums[j], \ \ i=n-2,n-3,...,0 right_sum[i]=j=i+1∑n−1nums[j], i=n−2,n−3,...,0
逐个比较left_sum与right_sum的元素,如果相同则返回对应索引,如果遍历完也没有相同的,则返回-1
例子:nums = [1, 7, 3, 6, 5, 6]
left_sum = [0,1,8,11,17,22]
right_sum = [27,20,17,11,6,0]
class Solution(object):
def pivotIndex(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
res = -1
n = len(nums)
left_sum = [0] * n
right_sum = [0] * n
for i in range(1, n):
left_sum[i] = left_sum[i-1] + nums[i-1]
for i in range(n-2, -1, -1):
right_sum[i] = right_sum[i+1] + nums[i+1]
for i in range(n):
if left_sum[i] == right_sum[i]:
res = i
break
return res
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int n = nums.size();
vector<int> left_sum(n, 0);
vector<int> right_sum(n, 0);
for (int i = 1; i < n; i++) {
left_sum[i] = left_sum[i-1] + nums[i-1];
int j = n - 1 - i;
right_sum[j] = right_sum[j + 1] + nums[j + 1];
}
for (int i = 0; i < n; i++) {
if (left_sum[i] == right_sum[i]) {
return i;
}
}
return -1;
}
};
1.2 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。
二分查找能够实现 O(log n) 的时间复杂度。
class Solution(object):
def searchInsert(self, nums, target):
left_idx, right_idx = 0, len(nums)-1
while(left_idx <= right_idx):
# print(left_idx, right_idx)
mid = int(left_idx + right_idx) / 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left_idx = mid + 1
elif nums[mid] > target:
right_idx = mid - 1
return left_idx
1.3 合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
思路:先排序,然后再逐个合并就好了。
class Solution(object):
def merge(self, intervals):
intervals.sort(key = lambda x:x[0])
res = []
for interval in intervals:
if res == [] or res[-1][1] < interval[0]:
res.append(interval)
else:
res[-1][1] = max(res[-1][1], interval[1])
return res
1.4 至少是其他数字两倍大的最大数
在一个给定的数组nums中,总是存在一个最大元素 。
查找数组中的最大元素是否至少是数组中每个其他数字的两倍。
如果是,则返回最大元素的索引,否则返回-1。
思路:
一次搜索,找最大值
二次搜索,判断是否符合两倍条件
class Solution(object):
def dominantIndex(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n=len(nums)
max_num=float("-INF")
max_ind=-1
for i in range(n):
if nums[i]>max_num:
max_num=nums[i]
max_ind=i
for i in range(n):
if i ==max_ind:
continue
# 判断是否是nums[i]的两倍
if max_num<nums[i]*2:
return -1
return max_ind
1.5 加一
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
核心难点:有进位的情况该如何处理,因为是加一操作,大大简化了进位难度,因为只有可能产生的进位是1
思路1在这里插入代码片
:直接在数组上操作,判断数组每位置数字是否产生进位:
class Solution(object):
def plusOne(self, digits):
"""
:type digits: List[int]
:rtype: List[int]
"""
n=len(digits)
digits[n-1]+=1
# 判断进位,如何存储进位信息。
flag=0
for i in range(n-1,-1,-1):
if flag==1:
digits[i]+=1
flag=0
if digits[i]==10:
digits[i]=0
flag=1
if flag!=0:
res=[flag]
for i in range(n):
res.append(digits[i])
return res
return digits
思路2:数组转数字,数字+1处理,再转数组。
def plusOne(self, digits):
"""
:type digits: List[int]
:rtype: List[int]
"""
# 整数转数字,加一之后转会数组
n=len(digits)
nums=0
e=0
for i in range(n-1,-1,-1):
nums+=digits[i]*10**e
e+=1
nums+=1
res=[]
while(nums>0):
digit=nums%10
res.append(digit)
nums=(nums-digit)/10
res.reverse()
return res
2. 一维数组双指针
在一般的数组题中,只采用一个指针来进行迭代(from begin to end);有些时候需要用两个指针来迭代数组。
两类双针:
- 两个指针从不同的位置出发(速度相同):两端向中间、起点不同方向相同 【left right】
- 两个指针以不同的速度移动:一个指针快一些,另一个指针慢一些 【slow, fast】
快慢双指针
2.1 移动元素–覆盖去重、移除元素、移动零 [slow, fast]
26.删除有序数组中的重复项 – 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
27.移除元素 – 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
83.删除排序链表中的重复元素 – 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。(类似题型)
283.移动零 – 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。
class Solution {
public:
// 26. 删除有序数组中的重复项
int removeDuplicates(vector<int>& nums) {
int right = 0;
int left = 0;
int n = nums.size();
while(right < n) {
if (nums[right] != nums[left]) {
left++; // nums[left]有效,nums[left+1]待填充
nums[left] = nums[right];
}
right++;
}
return left+1;
}
// 27.移除元素
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
int left = 0;
int right = 0;
while(right < n) {
if (nums[right] != val) {
nums[left] = nums[right]; // nums[left] 待填充,nums[left-1]有效
left++;
}
right++;
}
return left;
}
// 83. 删除排序链表中的重复元素
ListNode* deleteDuplicates(ListNode* head) {
ListNode* dummy = new ListNode(-101, head);
ListNode* right_p = dummy;
ListNode* left_p = dummy;
while(right_p != nullptr) {
if (right_p->val != left_p->val) {
left_p->next = right_p; //
left_p = left_p->next;
}
right_p = right_p->next;
}
left_p->next = nullptr; // 截断后续
return dummy->next;
}
// 283.移动零
void moveZeroes(vector<int>& nums) {
int n = nums.size();
int rihgt = 0;
int left = 0;
while (rihgt < n) {
if (nums[rihgt] != 0) {
nums[left] = nums[rihgt];
left++;
}
rihgt++;
}
while(left < rihgt) {
nums[left] = 0;
left++;
}
}
};
2.2 最大连续1的个数 [slow, fast]
给定一个二进制数组,计算其中最大连续1的个数。
k,m=0,0
快指针遇到1一直加,直至遇到0,统计一下当前1的个数,更新慢指针的位置。
慢指针指向连续1的开头,快指针指向连续1的结尾。
当快指针遇到0之后,统计一下结果。当遇到下一个1时慢指针才更新。
class Solution(object):
def findMaxConsecutiveOnes(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n=len(nums)
count=0
max_count=0
for i in range(n):
if nums[i]==1:
count+=1
if nums[i]==0:
max_count=max(max_count,count)
count=0
return max(max_count,count)
2.3 长度最小的子数组 [slow, fast]
给定一个含有n个正整数的数组和一个正整数s,找出该u数组中满足其和》=s的长度最小的子数组,并返回其长度。如果不存在符合条件的连续子数组,返回0.
子数组是一个连续的序列
解法1-暴力法
class Solution(object):
def minSubArrayLen(self, s, nums):
"""
:type s: int
:type nums: List[int]
:rtype: int
"""
n=len(nums)
res=n+1
for i in range(n):
temp=0
for j in range(i,n):
temp+=nums[j]
if temp>=s:
res=min(res,j-i+1)
break
#print (res)
if res==n+1:
return 0
else:
return res
解法2-双指针
以上思路是保持子数组的左端点不动,去寻找右端点。其实一旦知道这个位置开始的子数组不是最优答案,我们就可以移动左端点。使用两个指针,一个指向数组的开始位置,一个指向数组的最后位置。维护区间内的和>=s,且长度最小。
class Solution(object):
def minSubArrayLen(self, s, nums):
"""
:type s: int
:type nums: List[int]
:rtype: int
"""
n=len(nums)
res=n+1
temp_sum=0
left=0
for i in range(n):
temp_sum+=nums[i]
while(temp_sum>=s):
res=min(res,i-left+1)
temp_sum-=nums[left]
left+=1
#print (res)
if res==n+1:
return 0
else:
return res
左右双指针
2.4 反转字符串 [left, right]
344.反转字符串 – 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。要求原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
example:
输入:[“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]
思路:利用两个指针来完成迭代,一个指针从第一个元素开始,另一个指针从末尾开始,持续交换他们所指向的元素,直至俩个指针相遇)
class Solution(object):
def reverseString(self, s):
"""
"""
i=0
j=len(s)-1
while(i<j):
s[i],s[j]=s[j],s[i]
i+=1
j-=1
return s
class Solution {
public:
void reverseString(vector<char>& s) {
int n = s.size();
int left = 0, right = n - 1;
while (left < right) {
char tmp_s = s[left];
s[left] = s[right];
s[right] = tmp_s;
left++;
right--;
}
}
};
2.5 数组拆分 [left, right]
给定长度为2n的数组,将这些数字分为n对,例如 ( a 1 , b 1 ) , ( a 2 , b 2 ) , . . . , ( a n , b n ) (a_1,b_1),(a_2,b_2),...,(a_n,b_n) (a1,b1),(a2,b2),...,(an,bn),使得从1-n的 m i n ( a i , b i ) min(a_i,b_i) min(ai,bi)总和最大。
举例:
a
1
,
a
2
,
a
3
,
a
4
,
a
5
,
a
6
a_1,a_2,a_3,a_4,a_5,a_6
a1,a2,a3,a4,a5,a6升序排列
6个数分三对,能出现在和计算式中的最大数为
a
5
a_5
a5(此时
a
5
和
a
6
a_5和a_6
a5和a6配对),同理剩下的4个数中,依次出现在和式中的为
a
1
,
a
3
a_1,a_3
a1,a3
解题思路:先对数组排序,然后依次叠加偶数索引元素。
class Solution(object):
def arrayPairSum(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n=len(nums)
res=0
# nums.sort()
for i in range(n):
if i%2==0:
res+=nums[i]
return res
2.6 两数之和2 [left, right]
167.两数之和 II – 输入有序数组 --给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列,请你从数组中找出满足相加之和等于目标数 target 的两个数。(假设每个输只对应一组下标 ,数组元素不重复使用组和。只使用常量级的额外空间。
【Note-数组已经排序 左右双指针,时间复杂度o(n), 下标从1开始】
class Solution {
public:
// 167.两数之和 II - 数组已经排序 左右双指针o(n), 下标从1开始
vector<int> twoSum2(vector<int>& nums, int target) {
int n = nums.size();
int left = 0, right = n - 1;
vector<int> res(2, -1);
while(left < right) {
if (nums[left] + nums[right] == target) {
res[0] = left + 1;
res[1] = right + 1;
return res;
}else if (nums[left] + nums[right] < target) {
left++;
} else {
right--;
}
}
return res;
}
}
2.7 回文子串(数),最长回文子串(长)
647.回文子串(数) – 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。子字符串是字符串中的由连续字符组成的一个序列。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
5.最长回文子串(串) – 给你一个字符串 s,找到 s 中最长的回文子串。如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
class Solution {
public:
// 647.回文子串(数)-解1: 遍历所有子串,判断是否为回文子串,借助axu数组利用空间换时间
int countSubstrings(string s) {
int n = s.size();
int res = 0;
vector<vector<bool>> aux(n, vector<bool>(n, false));
for (int i = 0; i < n; i++) {
aux[i][i] = true;
res++;
}
for (int i = n -2; i > -1; i--) {
for (int j = i + 1; j < n; j++) {
if (j == i + 1) {
aux[i][j] = (s[i] == s[j]);
} else {
aux[i][j] = (s[i] == s[j] && aux[i+1][j-1]);
}
if (aux[i][j]) {
res++;
}
}
}
return res;
}
// 647.回文子串(数)-解2:真正中心扩展法,以 单/双字符为中心往两端扩展,每次扩展更新计数器,遇到非回文扩展即停止扩展
// 解1 aux数组解法的剪枝版本
int countSubstrings2(string s) {
int n = s.size();
int res = 0;
for (int i = 0; i < n; i++) {
res += palidrome_num(s, i, i);
res += palidrome_num(s, i, i+1);
}
return res;
}
int palidrome_num(string s, int left, int right) {
int n = s.size();
int res = 0;
while(left > -1 && right < n && s[left] == s[right]) {
res++;
left--;
right++;
}
return res;
}
// 5.最长回文子串-解1: aux解
string longestPalindrome(string s) {
int n = s.size();
vector<vector<bool>> aux(n, vector<bool>(n, false));
string res;
for (int i = 0; i < n; i ++) {
aux[i][i] = true;
res = s[0];
}
for (int i = n - 2; i > -1; i--) {
for (int j = i + 1; j < n; j++) {
if (j == i + 1) {
aux[i][j] = (s[i] == s[j]);
} else {
aux[i][j] = (s[i] == s[j] && aux[i+1][j-1]); // caution 已有条件使用
}
if (aux[i][j] && (res.size() < j - i + 1)) {
res = s.substr(i, j - i + 1);
}
}
}
return res;
}
// 5.最长回文子串-解2: 中心扩展法
string longestPalindrome(string s) {
int n = s.size();
string res;
if (n > 0) {
res = s[0];
}
for (int i = 0; i < n; i++) {
int left = i, right = i;
string s1 = palindrome(s, i, i);
string s2 = palindrome(s, i, i+1);
res = s1.size() > res.size() ? s1 : res;
res = s2.size() > res.size() ? s2 : res;
}
return res;
}
string palindrome(string s, int left, int right) {
int n = s.size();
while (left > -1 && right < n && s[left] == s[right]) {
left--;
right++;
}
return s.substr(left+1, (right - 1) - (left+ + 1) + 1); // 返回字符串,有效的left 和 right 要各自相中心缩小一格
}
};
3. 二维数组
二维数组是一种结构较为特殊的数组,只是将数组中的每个元素变成了一维数组。所以二维数组的本质上仍然是一个一维数组,内部的一维数组仍然从索引 0 开始,可以将二维数组看作一个矩阵,并处理矩阵的相关问题。
数组在内存上连续存储。
3.1 旋转矩阵
将 N × N 矩阵顺时针旋转90度。
1
2
3
4
5
6
7
8
9
(1)
\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix} \tag{1}
147258369(1)
解题思路:
1.旋转90度后,(i, j)位置上的数字会换到(j, N-1-i)
2.每4个数字可以形成个循环圈,4 * 90 = 360:1会到3的位置,3会到9的位置,9会到7的位置,7会到1的位置。即这四个相互置换数字坐标满足:
{
M
[
j
,
N
−
1
−
i
]
←
M
[
i
,
j
]
M
[
N
−
1
−
i
,
N
−
1
−
j
]
←
M
[
j
,
N
−
1
−
i
]
M
[
N
−
1
−
j
,
i
]
←
M
[
N
−
1
−
i
,
N
−
i
−
j
]
M
[
i
,
j
]
←
M
[
N
−
1
−
j
,
i
]
\left\{ \begin{aligned} M[j, N-1-i] & \leftarrow M[i, j]\\ M[N-1-i, N-1-j] & \leftarrow M[j, N-1-i] & \\ M[N-1-j, i] & \leftarrow M[N-1-i, N-i-j] \\ M[i, j] & \leftarrow M[N-1-j, i] \\ \end{aligned} \right.
⎩
⎨
⎧M[j,N−1−i]M[N−1−i,N−1−j]M[N−1−j,i]M[i,j]←M[i,j]←M[j,N−1−i]←M[N−1−i,N−i−j]←M[N−1−j,i]
3.仅需要旋转关键位置的数字即可:当 N为偶数时,我们需要枚举
n
2
/
4
=
(
n
/
2
)
×
(
n
/
2
)
n^2/4=(n/2)×(n/2)
n2/4=(n/2)×(n/2) 个位置,可以将该图形分为四块,当 n为奇数时,由于中心的位置经过旋转后位置不变,我们需要
枚举
(
n
2
−
1
)
/
4
=
(
(
n
−
1
)
/
2
)
×
(
(
n
+
1
)
/
2
)
(n^2−1)/4=((n−1)/2)×((n+1)/2)
(n2−1)/4=((n−1)/2)×((n+1)/2)个位置.
class Solution(object):
def rotate(self, matrix):
N = len(matrix)
for i in range(N//2):
for j in range((N+1)//2):
matrix[i][j], matrix[j][N-1-i], matrix[N-1-i][N-1-j], matrix[N-1-j][i]\
= matrix[N-1-j][i], matrix[i][j], matrix[j][N-1-i], matrix[N-1-i][N-1-j]
return matrix
3.2 零矩阵
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
记录某行某列存在0即可,某行某列存在多个0, 也只是被删除一次。
class Solution(object):
def setZeroes(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: None Do not return anything, modify matrix in-place instead.
"""
delet_x, delet_y = [], []
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if matrix[i][j] == 0:
delet_x.append(i)
delet_y.append(j)
for i in delet_x:
for j in range(len(matrix[0])):
matrix[i][j] = 0
for j in delet_y:
for i in range(len(matrix)):
matrix[i][j] = 0
class Solution(object):
def setZeroes(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: None Do not return anything, modify matrix in-place instead.
"""
n, m = len(matrix), len(matrix[0])
delet_x, delet_y = [0] * n, [0] * m
for i in range(n):
for j in range(m):
if matrix[i][j] == 0:
delet_x[i] = 1
delet_y[j] = 1
for i in range(n):
for j in range(m):
if delet_x[i] == 1 or delet_y[j] == 1:
matrix[i][j] = 0
3.3 对角线遍历
给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。
法一:直接模拟
思路与算法
根据题目要求,矩阵按照对角线进行遍历。设矩阵的行数为 mmm, 矩阵的列数为 nnn, 我们仔细观察对角线遍历的规律可以得到如下信息:
一共有 m+n−1m + n - 1m+n−1 条对角线,相邻的对角线的遍历方向不同,当前遍历方向为从左下到右上,则紧挨着的下一条对角线遍历方向为从右上到左下;
设对角线从上到下的编号为 i∈[0,m+n−2]i \in [0, m + n - 2]i∈[0,m+n−2]:
当 iii 为偶数时,则第 iii 条对角线的走向是从下往上遍历;
当 iii 为奇数时,则第 iii 条对角线的走向是从上往下遍历;
当第 iii 条对角线从下往上遍历时,每次行索引减 111,列索引加 111,直到矩阵的边缘为止:
当 i<mi < mi<m 时,则此时对角线遍历的起点位置为 (i,0)(i,0)(i,0);
当 i≥mi \ge mi≥m 时,则此时对角线遍历的起点位置为 (m−1,i−m+1)(m - 1, i - m + 1)(m−1,i−m+1);
当第 iii 条对角线从上往下遍历时,每次行索引加 111,列索引减 111,直到矩阵的边缘为止:
当 i<ni < ni<n 时,则此时对角线遍历的起点位置为 (0,i)(0, i)(0,i);
当 i≥ni \ge ni≥n 时,则此时对角线遍历的起点位置为 (i−n+1,n−1)(i - n + 1, n - 1)(i−n+1,n−1);
根据以上观察得出的结论,我们直接模拟遍历所有的对角线即可。
class Solution(object):
def findDiagonalOrder(self, mat):
"""
:type mat: List[List[int]]
:rtype: List[int]
"""
m, n = len(mat), len(mat[0])
res = []
for i in range(m + n - 1):
if i % 2 == 1:
# 由上向下
x = 0 if i < n else i - n + 1
y = i if i < n else n - 1
while (x < m and y >= 0 ):
res.append(mat[x][y])
x += 1
y -= 1
else:
# 由下向上
x = i if i < m else m - 1
y = 0 if i < m else i - m + 1
while (x >= 0 and y < n):
res.append(mat[x][y])
x -= 1
y += 1
return res
3.4 杨辉三角
第i行的元素个数为i+1个,i的取值范围为0,1,…,numRows-1
第i行元素的第一个元素(索引为0)和最后一个元素(索引为i),该行中其它元素的索引j取值范围为0,1,…,i-1.
class Solution(object):
def generate(self, c):
"""
:type numRows: int
:rtype: List[List[int]]
"""
res=[]
for i in range(numRows):
if i==0:
res.append([1])
elif i==1:
res.append([1,1])
else:
res.append([1]*(i+1))
for j in range(1,i):
res[i][j]=res[i-1][j-1]+res[i-1][j]
return res
4. 数组典型题
1.数组的常用排序算法及其时间复杂度分析十分重要
2.二分查找是一种十分重要的技术,用于在排序数组中搜索特i定元素
3.灵活应用双指针技巧十分重要,双指针技可以应用于:链表中快慢指针问题,滑动窗口问题
4.1 旋转数组
给定一个数组,将数组中的元素向右移动K个位置,其中k为非负数。
空间复杂度:o(1)
思路1:三次求逆:先整体求逆,然后nums[0,k]求逆,然后再nums[k+1:]求逆
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: None Do not return anything, modify nums in-place instead.
"""
def rever_nums(nums,l,r): # 原地置换,用index操作,nums[:flag]只是一个备份,没有修改元数组
while(l<r):
nums[l],nums[r]=nums[r],nums[l]
l+=1
r-=1
n=len(nums)
flag=k%n
rever_nums(nums,0,n-1)
rever_nums(nums,0,flag-1)
rever_nums(nums,flag,n-1)
return nums
思路2:计算每个数组移动到的目标位置,将目标位置的信息记录下来作为下一个操作的对象(30/35)—[-1,-100,3,99]2测试用例没过.
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: None Do not return anything, modify nums in-place instead.
"""
n=len(nums)
action=0
index=0
current_val=nums[0]
while(action<=n):
target_index=(index+k)%n
target_val=nums[target_index]
nums[target_index]=current_val
action+=1
index=target_index
current_val=target_val
return nums
修正思路:环装替换法
参考官网解题思路:https://leetcode-cn.com/problems/rotate-array/solution/xuan-zhuan-shu-zu-by-leetcode/
思路3,暴力法,每次移动一个,移动k次
(34/35)时间超出限制
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: None Do not return anything, modify nums in-place instead.
"""
n=len(nums)
flag=k%n
for i in range(flag):
current=nums[n-1]
for j in range(n): # j:[0,n-1]
temp=nums[j]
nums[j]=current
current=temp
return nums
4.2 杨辉三角2
返回杨辉三角的第k行,算法空间复杂度,o(k)
思路1:计算整个杨辉三角,返回最后一行
class Solution(object):
def getRow(self, rowIndex):
res=[]
for i in range(rowIndex+1): # i:[0,rowIndex] rowIndex+1行
if i==0:
res.append([1])
elif i==1:
res.append([1,1])
else:
res.append([1]*(i+1))
for j in range(1,i):
res[i][j]=res[i-1][j-1]+res[i-1][j]
return res[-1]
思路2:空间复杂度优化,只有上一行计算下一行:
class Solution(object):
def getRow(self, rowIndex):
if rowIndex==0:
return [1]
if rowIndex==1:
return [1,1]
pre=[1,1]
for i in range(2,rowIndex+1):
cur=[1]*(i+1)
for j in range(1,i):
cur[j]=pre[j-1]+pre[j]
pre=cur
return cur
4.3 反转字符串中的单词
151.反转字符串中的单词 – 给定一个字符串,逐个翻转字符串中的每个单词。单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
输入:s = “the sky is blue”
输出:“blue is sky the”
557.反转字符串中的单词 III – 给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
输入:s = “Let’s take LeetCode contest”
输出:“s’teL ekat edoCteeL tsetnoc”
class Solution(object):
def reverseWords(self, s):
"""
:type s: str
:rtype: str
151 解1:要识别字符串中的每个单词,然后调整顺序。从最后开始遍历,识别一个单词后,往结果中添加。
"""
res =""
n=len(s)
flag=0 # 判断是否开始识别一个单词
for i in range(n-1,-1,-1):
#print(i,s[i],flag)
if s[i]!=" " and flag==0: # 一个单词的起始
flag=1
right=i
l_word=1
elif s[i]!=" " and flag==1: # 单词左下标往前走
l_word+=1
elif s[i]==" " and flag==1: # 单词左下标走到了边界
word=s[right-l_word+1:right+1]
flag=0
res+=word
res+=" "
elif s[i]==" " and flag==0: # 多个空格的时候
continue
if flag==1: #最后还需要判断是否还有一个单词
word=s[right-l_word+1:right+1]
flag=0
res+=word
len_res=len(res)
if len_res==0:
return ""
if res[-1]==" ":
res=res[0:-1]
return res
def reverseWords(self, s):
"""
:type s: str
:rtype: str
151 解2:借助内置的函数
split() 将字符串按空格分成字符串数组
reverse() 将字符串数组翻转
join 将字符串数组拼接鞥一个字符串。
"""
s_arr=s.split()
s_arr.reverse()
res=" ".join(s_arr)
return res
def reverseWords(self, s):
"""
:type s: str
:rtype: str
557解:识别一个单词,送去翻转,添加至结果后
"""
def rever_word(word):
n=len(word)
re_word=""
for i in range(n-1,-1,-1):
re_word+=word[i]
#print(re_word)
return re_word
n=len(s)
l,r=0,0
res=""
while(r<n):
if s[r]!=" ":
r+=1
elif s[r]==" ":
word=s[l:r]
res+=rever_word(word)
res+=" "
l=r+1
r+=1
word=s[l:r+1]
res+=rever_word(word)
return res
4.4 前缀和
特点:原始数组不会被修改,频繁查询某个区间累加和。
技巧:构建 pre_sum[i] = pre_sum[i-1] + nums[i-1];
303.区域和检索 - 数组不可变 – 给定一个整数数组 nums,处理以下类型的多个查询:
- 计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
- 实现 NumArray 类:
NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )
304.二维区域和检索
// 303.区域和检索 - 数组不可变
class NumArray {
vector<int> pre_sum;
public:
NumArray(vector<int>& nums) {
int n = nums.size();
pre_sum.resize(n + 1);
// pre_sum[i] = sum(nums[0], nums[1], ..., nums[i-1])
for (int i = 1; i < n + 1; i++) {
pre_sum[i] = pre_sum[i-1] + nums[i-1];
}
}
int sumRange(int left, int right) {
// sum(nums[left], nums[left + 1], nums[right])
return pre_sum[right+1] - pre_sum[left];
}
};
// 304.二维区域和检索 -- 护所有操作的交集
class NumMatrix {
vector<vector<int>> pre_sum;
public:
NumMatrix(vector<vector<int>>& matrix) {
// 1 <= n, m <= 200
int n = matrix.size(), m = matrix[0].size();
pre_sum.resize(n + 1);
for (int i = 0 ; i < n + 1; i++) {
pre_sum[i].resize(m + 1);
}
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
pre_sum[i][j] = pre_sum[i][j-1] + pre_sum[i-1][j] - pre_sum[i-1][j-1] + matrix[i-1][j-1];
}
}
}
int sumRegion(int row1, int col1, int row2, int col2) {
return pre_sum[row2+1][col2+1] + pre_sum[row1][col1] - pre_sum[row2+1][col1] - pre_sum[row1][col2+1];
}
};
4.5 差分数组
特点:频繁对数组的某个区间元素进行增减,一通操作之后问数组变成什么样子了。
技巧:
- 构建 diff[i] = nums[i] - nums[i-1], 通过diff 数组可以反推原始数组,在diff 的i位置+k, 反推原始数组时,后续位置均会+k,
- 对matrix[i, j] 均+k的操作,在diff上留下的印记:diff[i] +=k, diff[j+1] - =k (当j+1==n 时无需操作)
370.区间加法 🔒
598.区间加法 II – 给你一个 m x n 的矩阵 M 和一个操作数组 op 。矩阵初始化时所有的单元格都为 0 。ops[i] = [ai, bi] 意味着当所有的 0 <= x < ai 和 0 <= y < bi 时, M[x][y] 应该加 1。
在 执行完所有操作后 ,计算并返回 矩阵中最大整数的个数 。
109.航班预订 – 这里有 n 个航班,它们分别从 1 到 n 进行编号。 有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。
1094.拼车 – 车上最初有 capacity 个空座位。车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向)给定整数 capacity 和一个数组 trips , trip[i] = [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassengersi 乘客,接他们和放他们的位置分别是 fromi 和 toi 。这些位置是从汽车的初始位置向东的公里数。当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true,否则请返回 false。
class Solution {
public:
// 598. 区间加法 II -- 求最大数的量,维护所有操作的交集,不用二维差分数组
int maxCount(int m, int n, vector<vector<int>>& ops) {
int min_row = m, min_col = n;
for (vector<int> op : ops) {
int i = op[0], j = op[1];
min_row = min(i, min_row);
min_col = min(j, min_col);
}
return min_row * min_col;
}
// 1109 航班预订 -- 差分数组,由各个航班预订列表统计各个航班的预订总数。
vector<int> corpFlightBookings(vector<vector<int>>& books, int n) {
vector<int> res(n, 0);
vector<int> diff(n, 0);
// 当k+1==n 时无需操作
for (vector<int>& book : books) {
int i = book[0], j = book[1], seats = book[2];
diff[i-1] += seats;
if (j < n) {
diff[j] -= seats;
}
}
res[0] = diff[0];
for (int i = 1; i < n; i++) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
// 1094.拼车 -- 车上的人只会待到 to - 1站, diff[to] -= n, not diff[to+1] -=n
bool carPooling(vector<vector<int>>& trips, int capacity) {
vector<int> diff_vec(1000, 0);
for (vector<int>& trip : trips) {
int n = trip[0], from = trip[1], to = trip[2];
diff_vec[from] += n;
if (to < 1000) {
// diff_vec[to+1] -= n;
diff_vec[to] -= n; // 该站下车,所以人数得减少
}
}
int cur_n = diff_vec[0]; // 第 0 km也可以上人
for (int i = 1; i < 1000; i++) {
if (cur_n > capacity) { // 判断的是i-1时的运载量
return false;
}
cur_n += diff_vec[i];
}
return true;
}
};