题41:最长连续序列(中)
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
示例 3:
输入:nums = [1,0,1,2]
输出:3
思路:
bash
简单来说就是每个数都判断一次这个数是不是连续序列的开头那个数。
1.首先去重得到一个哈希表set
2.依次遍历这个哈希表,当我们遍历set[i],如果哈希表里存在set[i] -1的值,那么此set[i]就不是开头,跳过
3,如果哈希表里不存在set[i] +1,则我们尝试以其为开头,不断的从哈希表里每次找是否存在加1,如果有,则更新当前set[i]开头的最长长度。
4.每次更新最长长度为最大的那一个即可。
简单来说就是每个数都判断一次这个数是不是连续序列的开头那个数。
怎么判断呢,就是用哈希表查找这个数前面一个数是否存在,即num-1在序列中是否存在。存在那这个数肯定不是开头,直接跳过。
因此只需要对每个开头的数进行循环,直到这个序列不再连续,因此复杂度是O(n)。
以题解中的序列举例:
[100,4,200,1,3,4,2]
去重后的哈希序列为:
[100,4,200,1,3,2]
按照上面逻辑进行判断:
元素100是开头,因为没有99,且以100开头的序列长度为1
元素4不是开头,因为有3存在,过,
元素200是开头,因为没有199,且以200开头的序列长度为1
元素1是开头,因为没有0,且以1开头的序列长度为4,因为依次累加,2,3,4都存在。
元素3不是开头,因为2存在,过,
元素2不是开头,因为1存在,过。
完
代码:
class Solution
{
public:
int longestConsecutive(vector<int> &nums)
{
unordered_set<int> num_set;
for (const int &num : nums)
{
num_set.insert(num);
}
int longestStreak = 0;
for (const int &num : num_set)
{
if (!num_set.count(num - 1))
{
int currentNum = num;
int currentStreak = 1;
while (num_set.count(currentNum + 1))
{
currentNum += 1;
currentStreak += 1;
}
longestStreak = max(longestStreak, currentStreak);
}
}
return longestStreak;
}
};
题42:三数之和(中等)
bash
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,
同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
代码:
bash
class Solution
{
public:
vector<vector<int>> threeSum(vector<int> &nums)
{
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int first = 0; first < n; first++)
{
if (nums[first] > 0) // 如果首元素都大于0,则后面的元素只会比首元素大,直接返回
{
return ans;
}
if (first > 0 && nums[first] == nums[first - 1]) // 因为不能重复,所以遇见首元素相同的跳过
{
continue;
}
int target = -nums[first];
int third = n - 1;
for (int second = first + 1; second < n; second++)
{
if (second > first + 1 && nums[second] == nums[second - 1]) // 因为不能重复,所以遇见第二元素相同的跳过
{
continue;
}
while (second < third && nums[second] + nums[third] > target) // 尾部指针,向左移动
{
--third;
}
if (second == third) // 如果当前选定的第二个指针,都找不到满足a+b+c=0的最小条件,说明first +second + second+1 >0,而second会变得更大,所以后面永远无法找到,跳出选定的第二个为首的循环
{
break;
}
if (nums[second] + nums[third] == target)
{
ans.push_back({nums[first], nums[second], nums[third]});
}
}
}
return ans;
}
};
题43:有效的数独(中等)
bash
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
注意:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 '.' 表示。

思路:
bash
思路:
方法一:一次遍历
有效的数独满足以下三个条件:
同一个数字在每一行只能出现一次;
同一个数字在每一列只能出现一次;
同一个数字在每一个小九宫格只能出现一次。
可以使用哈希表记录每一行、每一列和每一个小九宫格中,每个数字出现的次数。只需要遍历数独一次,在遍历的过程中更新哈希表中的计数,并判断是否满足有效的数独的条件即可。
由于数独中的数字范围是 1 到 9,因此可以使用数组代替哈希表进行计数。
创建二维数组 rows[9][9],例如:rows[0][2]的含义是第一行中数字2出现的次数。
创建二维数组 columns[9][9],例如:columns[0][2]的含义是第一列中数字2出现的次数。
创建三维数组 subboxes[3][3][9]subboxes[0][0][2]的含义是数独中第一个小的九个格子中,数字2出现的次数。
如果遍历结束之后没有出现计数大于 1 的情况,则符合有效的数独的条件,返回 true。
时间复杂度:O(1)。数独共有 81 个单元格,只需要对每个单元格遍历一次即可。
空间复杂度:O(1)。由于数独的大小固定,因此哈希表的空间也是固定的。
代码:
bash
class Solution
{
public:
bool isValidSudoku(vector<vector<char>> &board)
{
int rows[9][9];
int columns[9][9];
int subboxes[3][3][9];
memset(rows, 0, sizeof(rows));
memset(columns, 0, sizeof(columns));
memset(subboxes, 0, sizeof(subboxes));
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
{
char c = board[i][j];
if (c != '.')
{
int value = c - '0' - 1;
rows[i][value]++;
columns[j][value]++;
subboxes[i / 3][j / 3][value]++;
if (rows[i][value] > 1 || columns[j][value] > 1 || subboxes[i / 3][j / 3][value] > 1)
{
return false;
}
}
}
}
return true;
}
};
题44:插入区间(中等)
给你一个无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervals[i] = [starti, endi] 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval = [start, end] 表示另一个区间的开始和结束。
在 intervals 中插入区间 newInterval,使得 intervals 依然按照 starti 升序排列,且区间之间不重叠(如果有必要的话,可以合并区间)。
返回插入之后的 intervals。
注意 你不需要原地修改 intervals。你可以创建一个新数组然后返回它。
示例 1:
输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
输出:[[1,5],[6,9]]
示例 2:
输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
输出:[[1,2],[3,10],[12,16]]
解释:这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。
思路:
思路:
1.假设left = newInterval[0],right = newInterval[1];
2.循环遍历intervals
3.如果intervals[i][1]< left,则说明当前intervals[i]在左侧,且无法合并,则放入intervals[i]
4.如果intervals[i][0]> right,则说明当前intervals[i]在右侧,且无法合并,则放入left和right和后面的intervals[i]
5.如果不是上面两种,那就是有交集,则left更新为intervals[i][0]和left中较小的那一个即可,更新则right更新为intervals[i][1]和right中较小的那一个即可
代码:
class Solution
{
public:
vector<vector<int>> insert(vector<vector<int>> &intervals, vector<int> &newInterval)
{
int left = newInterval[0];
int right = newInterval[1];
vector<vector<int>> ans;
bool placed = false;
for (const auto &interval : intervals)
{
if (interval[0] > right)
{
if (!placed)
{
ans.push_back({left, right});
placed = true;
}
ans.push_back(interval);
}
else if (interval[1] < left)
{
ans.push_back(interval);
}
else
{
left = min(left, interval[0]);
right = max(right, interval[1]);
}
}
if (!placed)
{
ans.push_back({left, right});
}
return ans;
}
};
题45:用最少数量的箭引爆气球(中等)
bash
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[7,12]。
示例 2:
输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。
示例 3:
输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:
- 在x = 2处发射箭,击破气球[1,2]和[2,3]。
- 在x = 4处射出箭,击破气球[3,4]和[4,5]。
思路:
bash
思路:
1.首先,先排序points数组。我们暂定一个公共区间sameInterval为points[0]即区间为[1,6]
2.从point[1]开始遍历,如果point[i][0]<=sameInterval[1],代表存在公共区间,则只要在公共区间内,则就只需要一支箭,然后更新公共区间的边界为两者的较小值
3.假如point[i][0]>sameInterval[1],则代表此时公共区间无法满足了,箭的数量+1,重置公共区间为point[i]
代码:
bash
class Solution
{
public:
int findMinArrowShots(vector<vector<int>> &points)
{
if (points.empty())
{
return 0;
}
sort(points.begin(), points.end(), [](const vector<int> &u, const vector<int> &v)
{ return u[1] < v[1]; });
vector<int> sameInterval = points[0];
int ans = 1;
for (int i = 1; i < points.size(); i++)
{
if (points[i][0] <= sameInterval[1])
{
sameInterval[0] = max(sameInterval[0], points[i][0]);
sameInterval[1] = min(sameInterval[1], points[i][1]);
}
else
{
ans++;
sameInterval = points[i];
}
}
return ans;
}
};