53. 最大子数组和

使用动态规划方法,后面再看。
56. 合并区间


思路与解法
本题的核心在于区间合并,首先要对区间进行排序,然后通过遍历并合并相邻区间的方式来消除重叠的部分。
1、排序:将所有区间按左端点升序排列,这样重叠的区间会相邻。
2、遍历合并:遍历排序后的区间,维护一个结果列表 merged,逐个判断当前区间是否与结果中最后一个区间重叠:
若不重叠 ,即已合并的最后一个区间的右端点小于当前区间的左端点,直接加入结果 。
若重叠 ,则更新最后一个区间的右端点为两者右端点的较大值。
整体思路:建立结果数组merged------sort排序------遍历合并(判断当前区间与前一个区间是否重叠)
核心代码:
cpp
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> merged;
if (intervals.empty()) return merged;
// 1. 按照区间的起点排序
sort(intervals.begin(), intervals.end());
for (auto& interval : intervals) {
// 2. 如果 merged 为空或者当前区间与前一个区间不重叠,直接加入
if (merged.empty() || merged.back()[1] < interval[0]) {
merged.push_back(interval);
} else {
// 3. 否则有重叠,更新 merged 最后一个区间的右边界
merged.back()[1] = max(merged.back()[1], interval[1]);
}
}
return merged;
}
};
【注】
1、sort(intervals.begin(), intervals.end())
intervals.begin()指向 intervals 的第一个区间(即第一个 vector<int>)。
intervals.end()指向容器最后一个元素的下一个位置(即尾后迭代器),通常作为结束标记。
sort默认 按每个区间的第一个元素(即左端点)升序排序。
2、merged.back()[1] < interval[0]
merged.back() 返回merged中最后一个元素的引用,即最后一个区间(vector<int>&)。
merged.back()[1]访问该区间的第二个元素,即右端点值。
interval 是当前遍历到的原始区间(来自 intervals),interval[0] 是其左端点。
因此,该语句意思是:已合并的最后一个区间的右端点,是否小于当前区间的左端点。
如果为真 (小于),说明两个区间没有交集(前一个区间完全在当前区间左边),可以将当前区间作为新区间加入 merged。
如果为假(大于或等于),说明有重叠,需要合并,即更新最后一个区间的右端点为两者右端点的较大值。
ACM:
cpp
#include <vector>
#include <iostream>
#include<algorithm>
using namespace std;
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> merged;
sort(intervals.begin(),intervals.end());
for(auto &interval:intervals){
if(merged.empty()||merged.back()[1]<interval[0]){
merged.push_back(interval);
}
else{
merged.back()[1]=max(merged.back()[1],interval[1]);
}
}
return merged;
}
};
int main(){
int n;
cin>>n;
vector<vector<int>> intervals(n,vector<int>(2));
for(int i=0;i<n;i++){
cin>>intervals[i][0]>>intervals[i][1];
}
Solution solution;
vector<vector<int>> results = solution.merge(intervals);
for(auto& interval:results){
cout<<interval[0]<<" "<<interval[1]<<endl;
}
return 0;
}
【注】
1、intervals(n, vector<int>(2)):这是 vector 的构造函数调用,接受两个参数:
第一个参数 n:指定外部向量的大小,即 intervals 中将包含 n 个元素(每个元素是一个 vector<int>)。
第二个参数 vector<int>(2):指定一个初始值,用于填充外部向量的每一个元素。这里 vector<int>(2) 创建一个匿名临时对象,它是一个包含两个 int 的向量,两个 int 默认初始化为 0。
189. 轮转数组

思路与解法:
假设数组长度为 n,向右旋转 k 位(k 可能大于 n,先取模 k %= n),最终结果相当于将原数组的后 k 个元素移到前面,前 n-k 个元素移到后面,且两部分内部的相对顺序保持不变。即:
原数组: A[0] A[1] ... A[n-k-1] | A[n-k] ... A[n-1]
旋转后: A[n-k] ... A[n-1] | A[0] A[1] ... A[n-k-1]
反转一个数组(或子数组)可以完全颠倒元素的顺序。如果我们能通过反转操作,将上面的"前后交换"效果实现,同时恢复各部分的顺序,就可以得到目标数组。
自然得到三次反转的步骤:
自己举个12345的例子就好理解了
1、反转整个数组:将后 k 个元素移到前面(但顺序颠倒),同时前 n-k 个元素移到后面(顺序也颠倒)。
2、反转前 k 个元素:将前面部分顺序纠正,得到正确的后 k 个元素。
3、反转后 n-k 个元素:将后面部分顺序纠正,得到正确的前 n-k 个元素。
核心代码:
cpp
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n=nums.size();
k=k%n;
reverse(nums.begin(),nums.end());
reverse(nums.begin(),nums.begin()+k);
reverse(nums.begin()+k,nums.end());
}
};
【注】
1、k=k%n;防止k过大!!
2、想移动迭代器,直接+k即可。
随机访问迭代器支持算术运算,例如 + 和 -,可以像指针一样进行偏移。因此 nums.begin() + k 表示将迭代器向后移动 k 个位置,得到指向索引为 k 的元素的迭代器。
3、不是reverse(nums.begin()+k+1,nums.end()); !!!
因为reverse是左闭右开的!!!
ACM:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n=nums.size();
k=k%n;
reverse(nums.begin(),nums.end());
reverse(nums.begin(),nums.begin()+k);
reverse(nums.begin()+k,nums.end());
}
};
int main() {
int n, k;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
cin >> k;
Solution solution;
solution.rotate(nums, k); // 调用类的成员函数
for (int i = 0; i < n; i++) {
if(i>0) cout<< " ";
cout << nums[i];
}
cout << endl;
return 0;
}
238. 除了自身以外数组的乘积

思路与解法:

核心代码:
cpp
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
//初始化结果数组,所有元素先设为1,因为要乘,不能设为0!!
vector<int> answer(n,1);
int prefix=1;
for(int i =0;i<n;i++){
answer[i]=prefix; //将当前左边乘积存入answer[i]
prefix*=nums[i]; //更新前缀积,为下一个位置做准备
}
int suffix=1;
for(int i=n-1;i>=0;i--){
answer[i]*=suffix; //将右边乘积乘到answer[i]上
suffix*=nums[i]; //更新后缀积,为前一个位置做准备
}
return answer;
}
};
【注】
1、vector<int> answer(n,1); int prefix=1; int suffix=1;
注意都初始化为1,因为要做乘法!!
2、for(int i=n-1;i>=0;i--){ 注意是i>=0
3、 answer[i]=prefix; 计算前缀积不是*=!!
answer[i]*=suffix; 计算后缀积才是*=,因为answer[i] = prefix[i] * suffix[i],在原来计算好的前缀积的基础上再乘以后缀积。
ACM:
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> answer(n,1);
int prefix=1;
for(int i =0;i<n;i++){
answer[i]=prefix;
prefix*=nums[i];
}
int suffix=1;
for(int i=n-1;i>=0;i--){
answer[i]*=suffix;
suffix*=nums[i];
}
return answer;
}
};
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
Solution solution;
vector<int> answer = solution.productExceptSelf(nums);
for (int i = 0; i < n; i++) {
if(i>0) cout <<" ";
cout << answer[i];
}
cout << endl;
return 0;
}
41.缺失的第一个整数

思路与解法
本题要求时间复杂度O(n)且常数级 额外空间。常见方法包括:
1、哈希表:O(n) 时间,但空间 O(n),不符合要求。
2、排序:O(n log n),不符合要求。
3、"原地置换" 方法:符合要求,利用数组本身作为哈希表。
对于一个长度为 n 的数组,最小的缺失正数一定在 [1, n+1] 范围内。因为如果 1到n 都出现了,那么答案就是 n+1。因此,我们可以利用数组本身来标记 1~n 这些数是否出现。
具体做法:将每个正数 x(1 ≤ x ≤ n)放到数组下标为 x-1 的位置上(即索引 0 放 1,索引 1 放 2,以此类推)。这样,经过一次遍历调整后,所有在范围内的正数都回到了它们"应该"在的位置。然后第二次遍历,第一个不满足 nums[i] == i+1 的位置 i 就对应缺失的正数 i+1。
如何处理重复的数字?
通过条件 nums[i] != nums[nums[i] - 1] (nums[i]不在正确的位置)避免了无限交换。每个在范围内的数最多被交换一次,因为一旦它到达正确位置,就不会再被移动。重复的数会被"挤"到后面,不会影响正确位置的判断。
核心代码:
cpp
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
//第一次遍历:将每个在 [1, n] 范围内的数放到正确的位置
for(int i = 0;i<n;i++){
//当 nums[i] 是正数且 ≤ n,并且它不在正确的位置上时,循环交换
while(nums[i]>0&&nums[i]<=n&&nums[i]!=nums[nums[i]-1]){
swap(nums[i],nums[nums[i]-1]);
}
}
//第二次遍历:找出第一个位置 i 使得 nums[i] != i+1
for(int i = 0;i<n;i++){
if(nums[i]!=i+1){
return i+1;
}
}
// 如果所有位置都正确,则缺失的是 n+1
return n+1;
}
};
ACM:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
for(int i = 0;i<n;i++){
//当 nums[i] 是正数且 ≤ n,并且它不在正确的位置上时,循环交换
while(nums[i]>0&&nums[i]<=n&&nums[i]!=nums[nums[i]-1]){
swap(nums[i],nums[nums[i]-1]);
}
}
//第二次遍历:找出第一个位置 i 使得 nums[i] != i+1
for(int i = 0;i<n;i++){
if(nums[i]!=i+1){
return i+1;
}
}
// 如果所有位置都正确,则缺失的是 n+1
return n+1;
}
};
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
Solution solution;
cout << solution.firstMissingPositive(nums) << endl;
return 0;
}