说明:
- 文章内容为个人的力扣刷题记录,不定时更新。
- 文章内容如有错误,欢迎指正。
文章目录
- 2023-04-24 题目1. 两数之和
- 方法一:暴力解法,循环遍历
- 方法二:哈希表
- 2023-04-25 4. 寻找两个正序数组的中位数
- 方法一:双指针法+使用额外空间
- 方法二 :双指针法+不使用额外空间
- 2023-4-27 11. 盛水最多的容器
- 2023-4-27 15.三数之和
2023-04-24 题目1. 两数之和
1. 两数之和 - 力扣(Leetcode)
方法一:暴力解法,循环遍历
- C++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0; i<nums.size()-1; i++){
for(int j=i+1; j<nums.size(); j++){
if(nums[i]+nums[j] == target){
return {i,j};
}
}
}
return {};
}
};
- python
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
# 方法一:暴力解法:循环遍历
for i in range(len(nums)-1):
for j in range(i+1, len(nums)):
if nums[i]+nums[j] == target:
return [i,j]
return []
方法二:哈希表
参考1. 两数之和 - 力扣(Leetcode)
- 哈希表的查找时间复杂度为O(1),可以大大降低时间复杂度
- 算法思想:
- 构建哈希表hashTable
- 遍历nums,每遍历到一个值nums[i],就判断hashTable中是否存在key为target-nums[i]的元素
- 若存在,则找到这两个元素,并返回下标;若不存在,则将(nums[i],i)作为(key,value)存入哈希表中,继续遍历直至找到
- 若最后没有找到则返回空
- C++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//方法二:哈希表
//利用C++ STL中的unordered_map数据结构构造哈希表
unordered_map<int,int> hashTable;
for(int i=0; i<nums.size(); i++){
auto iter = hashTable.find(target-nums[i]); //返回的是一个迭代器
//如果找到则返回key为target-nums[i]的迭代器
//找不到就返回尾后迭代器
if(iter != hashTable.end()){
return {iter->second, i}; //iter->second即元素下标
}
//没有找到,将(nums[i],i)作为(key,value)存入哈希表
hashTable.insert(pair<int,int>(nums[i], i));
}
return {};
}
};
- python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 方法二:哈希表 python3
# 利用Python的字典数据结构构造哈希表
hashTable = dict()
for i in range(len(nums)):
if target-nums[i] in hashTable: # 在python2中使用的是has_key()函数判断
return [hashTable.get(target-nums[i]), i]
hashTable[nums[i]] = i
return []
2023-04-25 4. 寻找两个正序数组的中位数
4. 寻找两个正序数组的中位数 - 力扣(Leetcode)
都是升序数组,且要求时间复杂度为O(m+n),使用双指针法。
方法一:双指针法+使用额外空间
将两个数组合并为一个升序数组,在这个新数组中寻找中位数,空间复杂度为O(m+n)。
- C++
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
// 双指针法
// 方法一:使用额外空间,空间复杂度为O(m+n)
int m=nums1.size(), n=nums2.size();
vector<int> merge;
int i=0, j=0; // 双指针
// 建立新的升序数组
while(i<m || j<n){
if(i<m && j<n){
if(nums1[i] < nums2[j]) merge.push_back(nums1[i]), i++;
else if(nums2[j] < nums1[i]) merge.push_back(nums2[j]), j++;
else merge.push_back(nums1[i]), merge.push_back(nums2[j]), i++, j++;
}else{ // 如果有一个指针越界,则后移另一个
if(i >= m && j < n) merge.push_back(nums2[j]), j++;
if(j >= n && i < m) merge.push_back(nums1[i]), i++;
}
}
// 寻找中位数
if((m+n)%2 == 0){
return (float)(merge[(m+n)/2 - 1] + merge[(m+n)/2])/2;
}else{
return (float)merge[(m+n)/2];
}
};
- python
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
# 使用额外空间
m,n = len(nums1), len(nums2)
merge = []
i = 0
j = 0
# 建立新的升序数组
while i<m or j<n :
if i<m and j<n:
if nums1[i] < nums2[j]:
merge.append(nums1[i])
i += 1
elif nums2[j] < nums1[i]:
merge.append(nums2[j])
j += 1
else:
merge.append(nums1[i])
merge.append(nums2[j])
i += 1
j += 1
else: # 两个指针有一个越界
if i >= m and j < n:
merge.append(nums2[j])
j += 1
if j >= n and i < m:
merge.append(nums1[i])
i += 1
# 寻找中位数
if (m+n)%2 == 0:
return float((merge[(m+n)//2 - 1] + merge[(m+n)//2])/2)
else:
return float(merge[(m+n)//2])
方法二 :双指针法+不使用额外空间
通过上面的的“使用额外空间”的方法,可以看到,最后有用的是最中间的一个值(或最中间的两个值),那么可以在遍历的过程中只保存中间的值,最后只利用最中间的值。
算法思想:
- 设置两个变量pre,cur来模仿将数值插入新数组的过程。pre保存上一个插入数组的值,cur保存当前插入数组的值;
- 变量mid_iter保存遍历的次数,当遍历次数达到
(m+n)/2
时,结束循环,此时说明我们已找到中位数; - 如果m+n为偶数,中位数就是最中间的两个值,返回(pre+cur)/2;如果m+n为基数,中位数就是最中间的一个值,返回cur。
- C++
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
// 不使用额外空间
int m=nums1.size(), n=nums2.size();
int i=0, j =0;
int mid_iter = 0; // 保存循环的次数
int pre=-1, cur=-1;
// 遍历寻找中位数
while(mid_iter <= (m+n)/2){
if(i<m && j<n){
if(nums1[i] < nums2[j]){
pre = cur, cur = nums1[i];
i++, mid_iter++;
} else if(nums2[j] < nums1[i]){
pre = cur, cur = nums2[j];
j++, mid_iter++;
} else{
// 当两个值相等时,并不同时后移两个指针
// 而是模拟将一个值插入新数组,并后移该指针
pre = cur, cur = nums1[i];
i++, mid_iter++;
}
} else {
if(i >= m && j < n){
pre = cur, cur = nums2[j];
j++, mid_iter++;
}
if(j >= n && i < m){
pre = cur, cur = nums1[i];
i++, mid_iter++;
}
}
}
// 返回中位数
if((m+n)%2 == 0){
return (float)(pre+cur)/2;
}else{
return (float)cur;
}
}
};
- python
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m,n = len(nums1), len(nums2)
i, j = 0, 0
mid_iter = 0
pre, cur = -1, -1
while mid_iter <= (m+n)//2:
if i<m and j<n:
if nums1[i] < nums2[j]:
pre = cur; cur = nums1[i]
i += 1; mid_iter += 1
elif nums2[j] < nums1[i]:
pre = cur; cur = nums2[j]
j += 1; mid_iter += 1
else:
pre = cur; cur = nums1[i]
i += 1; mid_iter += 1
else:
if i >= m and j < n:
pre = cur; cur = nums2[j]
j += 1; mid_iter +=1
if j >= n and i < m:
pre = cur; cur = nums1[i]
i += 1; mid_iter +=1
if (m+n)%2 == 0:
return float((pre+cur)/2)
else:
return float(cur)
说明:关于当两个值相等时,为什么不同时更新pre和cur,并同时后移两个指针?这是因为防止更新过快,而导致我们“跟丢”中位数,考虑下面这种情况:
如下图所示的两个数组,中位数应该是(6+9)/2=7.5,也就是说mid_iter>6的时候就应该结束循环。但是如果我们同时更新pre和cur,并同时后移两个指针,就会出现这种情况:
- 比较
5<6
,此时更新pre=4,cur=5
,执行i++,mid_iter++
,此时nums1[i]=6,nums2[j]=6
; - 判断mid_iter,此时
mid_iter=4
,继续循环。比较6==6
, 此时同时更新pre=6,cur=6
,执行i++, j++,mid_iter+=2
,此时nums1[i]=9, nums2[j]=9
; - 判断mid_iter,此时
mid_iter=6
,继续循环。比较9==9
,此时同时更新pre=9,cur=9
,执行i++,j++,mid_iter+=2
,此时nums1[i]=11,nums2[j]=12
; - 判断mid_iter,此时
mid_iter=8
,结束循环
但这时候计算出的中位数就是(9+9)/2=9,而不是7.5。这就是更新太快,导致中位数被跟丢了。按理说当mid_iter=7的时候就应该结束循环,但由于同步更新的策略,导致我们错过了中位数。
如果采用单步更新策略,那么执行过程就是:
- 比较
5<6
,此时更新pre=4,cur=5
,执行i++,mid_iter++
,此时nums1[i]=6,nums2[j]=6
; - 判断mid_iter,此时
mid_iter=4
,继续循环。比较6==6
, 此时更新pre=5,cur=6
,执行i++,mid_iter++
,此时nums1[i]=9, nums2[j]=6
; - 判断mid_iter,此时
mid_iter=5
,继续循环。比较6<9
,此时更新pre=6,cur=6
,执行j++,mid_iter++
,此时nums1[i]=9,nums2[j]=9
; - 判断mid_iter,此时
mid_iter=6
,继续循环。比较9==9
,此时更新pre=6,cur=9
,执行i++,mid_iter++
,此时nums1[i]=11,nums2[j]=9
; - 判断mid_iter,此时
mid_iter=7
,结束循环。
此时计算结果就是7.5。单步更新策略,就不会由于更新过快导致中位数被跟丢。
有位大佬写了解法三和解法四,利用了二分查找的思想,可以将时间复杂度降为log级别,参考https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/8999/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/
2023-4-27 11. 盛水最多的容器
11. 盛水最多的容器
首先想到的是暴力解法,嵌套循环寻找最大值,但是暴力解法超时了。
思考如何优化,优化的思路就是减少一层循环。可以采用双指针法,一个指针从前向后移动,另一个指针从后向前移动,并记录移动过程中的最大水量,这样就可减少一层循环,降低时间复杂度。
接下来的关键是如何确定指针的移动规则。假设两个指针分别为i(从前向后移动),j(从后向前),我们可以得出每次的容器盛水量为 v o l u m e = m i n { h e i g h t [ i ] , h e i g h t [ j ] } ∗ ( j − i ) volume = min\{height[i], height[j]\}*(j-i) volume=min{height[i],height[j]}∗(j−i)。可以发现,每次无论移动的是 i 还是 j , ( j − i ) (j-i) (j−i)都会减少一,为了得到最大盛水量,我们应想法设法让 m i n { h e i g h t [ i ] , h e i g h t [ j ] } min\{height[i], height[j]\} min{height[i],height[j]}增大,也就是说让指向更小值的那个指针移动,这样才有机会得到更大的height,才有可能使 m i n { h e i g h t [ i ] , h e i g h t [ j ] } min\{height[i], height[j]\} min{height[i],height[j]}增大。
- C++
class Solution {
public:
int maxArea(vector<int>& height) {
int i=0, j=height.size()-1; // 双指针
int min_h = 0;
int max_v = 0; // 最大盛水量
int volume = 0; // 计算每次的盛水量
while(i != j){ // 两个指针相遇,结束循环
min_h = height[i] <= height[j] ? height[i] : height[j];
volume = min_h * (j - i);
if(volume > max_v) max_v = volume; // 更新最大盛水量
// 移动指针
if(height[i] <= height[j]) i++;
else j--;
}
return max_v;
}
};
- python
class Solution:
def maxArea(self, height: List[int]) -> int:
i, j = 0, len(height)-1
max_v = 0
while i != j:
min_h = height[i] if height[i] <= height[j] else height[j]
volume = min_h * (j - i)
if volume > max_v: max_v = volume # 更新最大盛水量
# 移动指针
if height[i] <= height[j]: i += 1
else: j -= 1
return max_v
2023-4-27 15.三数之和
15.三数之和
最容易想到的是暴力解法,但是暴力解法会超时。从暴力解法中思考如何优化,能不能减少循环嵌套的层数。在暴力解法的最内层循环中,我们是固定指针i,j,移动指针k来寻找结果,能不能每次只固定一个指针,然后移动另外两个指针,这样就能减少一层循环。这就需要利用排序。
- 首先对数组进行排序,得到一个升序数组。
- 设置三个指针i, front, rear,i从前向后移动,front从i+1向后移动,rear从nums.size()-1向前移动,front和rear相向而行,寻找结果,相遇即停。
- 当三数之和sum等于0时,则找到一个结果,并更新front和rear;当sum小于0,则front++;当sum大于0,则rear–
- 对结果进行去重
- C++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> results;
sort(nums.begin(), nums.end());
int front = 0, rear = 0; // 两个前后指针
for(int i=0; i<nums.size()-2; i++){
if(nums[i] > 0) break; // 此时说明后续全是正数,无需再循环
if(i > 0 && nums[i] == nums[i - 1]) continue; // 这一步是为了去重
front = i + 1;
rear = nums.size()-1;
while(front < rear){
int sum = nums[i] + nums[front] + nums[rear];
if(sum == 0){
vector<int> temp{nums[i], nums[front], nums[rear]};
sort(temp.begin(), temp.end()); // 便于后续results去重
results.push_back(temp);
front++;
rear--;
}else if(sum < 0){
front++;
}else{
rear--;
}
}
}
// results去重
results.erase(unique(results.begin(), results.end()), results.end());
return results;
}
};
上述代码运行时间约为124ms,二次去重的时候results.erase(unique(results.begin(), results.end()), results.end());
这条语句会比较耗时,可以将二次去重写在循环体内,二次去重去的是重复的nums[front]和nums[rear]。
- C++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> results;
sort(nums.begin(), nums.end());
int front = 0, rear = 0; // 两个前后指针
for(int i=0; i<nums.size()-2; i++){
if(nums[i] > 0) break; // 此时说明后续全是正数,无需再循环
if(i > 0 && nums[i] == nums[i - 1]) continue; // 这一步是为了去重
front = i + 1;
rear = nums.size()-1;
while(front < rear){
int sum = nums[i] + nums[front] + nums[rear];
if(sum == 0){
vector<int> temp{nums[i], nums[front], nums[rear]};
sort(temp.begin(), temp.end()); // 便于后续results去重
results.push_back(temp);
// 可以在这里写results的二次去重
while(front<rear && nums[front]==nums[front+1]) front++;
while(front<rear && nums[rear]==nums[rear-1]) rear--;
front++;
rear--;
}else if(sum < 0){
front++;
}else{
rear--;
}
}
}
// // results去重
// results.erase(unique(results.begin(), results.end()), results.end());
return results;
}
};
这样运行时间会减少至96ms。