Daily LeetCode ---- 数组
Posted 唯有努力生存
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Daily LeetCode ---- 数组相关的知识,希望对你有一定的参考价值。
34. 在排序数组中查找元素的第一个和最后一个位置
注:你的算法时间复杂度必须是 O(log n) 级别。
// 2020.06.30 17:35
// 算法思路:左端点二分查找 + 右端点二分查找
// 时间复杂度 O(logn) 空间复杂度 O(1)
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
// 特例处理
if(nums.size() == 0)
return {-1, -1};
vector<int> res;
int l = 0, r = nums.size() - 1;
int m = l + (r - l) / 2;
// left
while(l <= r){
m = l + (r - l) / 2;
if(nums[m] > target)
r = m - 1;
else if(nums[m] < target)
l = m + 1;
else
r = m - 1; // 查找左端点时 右侧逼近
}
// 全部小于 target 则 l = nums.size() 全部大于 target 则 nums[l] = nums[0] != target
// 终止循环的条件是 l == r + 1
if(l == nums.size() || nums[l] != target) // 如果左端点未找到 也无需找右端点 直接返回
return {-1, -1};
else
res.push_back(l);
// right
l = 0;
r = nums.size() - 1;
m = l + (r - l) / 2;
while(l <= r){
m = l + (r - l) / 2;
if(nums[m] > target)
r = m - 1;
else if(nums[m] < target)
l = m + 1;
else
l = m + 1; // 查找右端点时 左侧逼近
}
// 全部小于 target 则 r = -1 全部大于 target 则 nums[r] = nums[n-1] != target
// 终止循环的条件是 l == r + 1
/*if(r < 0 || nums[r] != target)
r = -1;
else
res.push_back(r); */
// 如果程序可以进行到此处 必然存在右端点
res.push_back(r);
return res;
}
};
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
// 2020.06.30 17:41
// 二分法
// 时间复杂度 O(logn) 空间复杂度 O(1)
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if(nums.size() == 0)
return 0;
int l = 0, r = nums.size() - 1;
while(l <= r){
int m = l + (r - l) / 2;
if(nums[m] == target)
return m;
else if(nums[m] > target)
r = m - 1;
else
l = m + 1;
}
return l;
}
};
39. 组合总和
// 2020.07.01 12:03
// 递归:一种调用自己的编程技巧;回溯:用递归实现的一种算法思想,类似穷举法,有“剪枝功能”
// 时间复杂度 O() 空间复杂度 O(n)
// 减法 dfs target == 0
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void dfs(vector<int>& candidates, int begin, int target){
if(target == 0){
res.push_back(path);
return;
}
for(int i = begin; i < candidates.size() && candidates[i] <= target; i++){
path.push_back(candidates[i]);
dfs(candidates, i, target - candidates[i]);
// 回溯
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
// 排序降低算法复杂度
sort(candidates.begin(), candidates.end());
dfs(candidates, 0, target);
return res;
}
};
// 2020.07.01 12:24
// 加法 dfs target == target
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
int sum; // 用于存储目标值
void dfs(vector<int>& candidates, int begin, int target){
if(target == sum){
res.push_back(path);
return;
}
for(int i = begin; i < candidates.size() && target < sum; i++){
path.push_back(candidates[i]);
dfs(candidates, i, target + candidates[i]); // target + candidates[i]
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
sum = target;
dfs(candidates, 0, 0); // 起初总和为 0 逼近 target
return res;
}
};
40. 组合总和II
// 2020.07.01 12:47
// 注意数组中可能有重复元素
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void dfs(vector<int>& candidates, int begin, int target){
if(target == 0){
res.push_back(path);
return;
}
for(int i = begin; i < candidates.size() && target > 0; i++){
// 去重的核心在此 去掉同层重复 保留上下层的重复
if(i > begin && candidates[i] == candidates[i-1])
continue;
path.push_back(candidates[i]);
dfs(candidates, i+1, target - candidates[i]);
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
// 排序方便“剪枝”
sort(candidates.begin(), candidates.end());
dfs(candidates, 0, target);
return res;
}
};
☆41. 缺失的第一个正数
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。注:你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。
同类问题:442. 数组中重复的数据 以及 448. 找到所有数组中消失的数字
// 2020.07.02 10:38
// 如果不考虑时间复杂度的要求 排序后一层循环即可
// 时间复杂度 O(nlogn) 空间复杂度 O(logn)
// 其他思路:哈希表 依次从1开始判断各数是否在数组中
// 时间复杂度 O(n) 空间复杂度 O(n)
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
sort(nums.begin(), nums.end());
// 考虑到 1 为最小正整数 res 初始化为 1
int res = 1;
// 注意 for 循环里的额外限制条件 res >= nums[i]
for(int i = 0; i < nums.size() && res >= nums[i]; i++){
if(nums[i] <= 0)
continue;
// 比 nums[i] 大一个即可
res = nums[i] + 1;
}
return res;
}
};
// 2020.07.02 11:26
// 原地哈希:把数组看作哈希表
// 算法思路:数组长度为N 要找的数一定在[1,N+1] 把1放在下标0的位置 以此类推
// 遍历数组时 第一个遇到的值不等于下标的那个数 就是所求结果
// 时间复杂度 O(n) -- 均摊复杂度分析 空间复杂度 O(1)
class Solution {
public:
void swapArray(vector<int>& nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
int firstMissingPositive(vector<int>& nums) {
int length = nums.size();
for(int i = 0; i < length; i++){
// 注意这里是 while 需要一直判断互换位置后是否满足条件
while(nums[i] != i + 1 && nums[i] > 0 && nums[i] <= length && nums[i] != nums[nums[i] - 1])
swapArray(nums, i, nums[i]-1);
}
for(int i = 0; i < length; i++){
if(nums[i] != i + 1)
return i + 1;
}
return length + 1;
}
};
442. 数组中重复的数据
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。找到所有出现两次的元素。
注:你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
// 2020.07.02 13:27
// 原地哈希
// 时间复杂度 O(n) 空间复杂度 O(1)
class Solution {
public:
void swapArray(vector<int>& nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
int length = nums.size();
for(int i = 0; i < length; i++){
while(nums[i] != i + 1 && nums[i] != nums[nums[i] - 1])
swapArray(nums, i, nums[i] - 1);
}
for(int i = 0; i < length; i++){
if(nums[i] != i + 1)
res.push_back(nums[i]);
}
return res;
}
};
448. 找到所有数组中消失的数字
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。
注:您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
// 2020.07.03 11:01
// 原地哈希
// 时间复杂度 O(n) 空间复杂度 O(1)
class Solution {
public:
void swapArray(vector<int>& nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> res;
int length = nums.size();
for(int i = 0; i < length; i++){
while(nums[i] != i + 1 && nums[i] != nums[nums[i] - 1])
swapArray(nums, i, nums[i] - 1);
}
for(int i = 0; i < length; i++){
if(nums[i] != i + 1)
res.push_back(i + 1);
}
return res;
}
};
48. 旋转图像
给定一个 n × n 的二维矩阵表示一个图像。将图像顺时针旋转 90 度。
注:你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
// 2020.07.03 11:45
// 找到旋转对应规律 结合哈希表 递归
// 时间复杂度 O(n2) 空间复杂度 O(1)
class Solution {
public:
map<vector<int>,int> hash;
void swapMatrix(vector<vector<int>>& matrix, int i, int j, int value){
if(hash.count({i,j}))
return;
int n = matrix.size();
int temp = matrix[i][j];
matrix[i][j] = value;
hash.insert(pair<vector<int>,int>({i,j},1));
// 注意是 matrix[j][n-1-i]
swapMatrix(matrix, j, n-1-i, temp);
}
void rotate(vector<vector<int>>& matrix) {
// (i,j) --> (j,n-1-i)
int n = matrix.size();
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
// 注意是 matrix[n-1-j][i]
swapMatrix(matrix, i, j, matrix[n-1-j][i]);
}
}
}
};
// 2020.07.03 12:34
// 本质上是 [i,j] --> [j,n-1-i]
// 算法思路1:先转置[i,j] = [j,i] 再翻转 [i,j] = [i,n-j-1]
// 算法思路2:转化为移动4个位置元素的临时列表 外循环找大框 内循环找4个点
// 时间复杂度 O(n2) 空间复杂度 O(1)
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
for(int start = 0, end = n - 1; start < end; start++, end--){
// 大框逐步缩小为小框
for(int i = start, j = end; i < end; i++, j--){
// 同一层框内点的移动
int temp = matrix[start][i];
matrix[start][i] = matrix[j][start];
matrix[j][start] = matrix[end][j];
matrix[end][j] = matrix[i][end];
matrix[i][end] = temp;
}
}
}
};
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
// 2020.07.04 10:20
// 核心在于:如果累加值小于 nums[i] 则以 nums[i] 作为新起点
// 时间复杂度 O(n) 空间复杂度 O(1)
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if(nums.size() == 0)
return 0;
int res = nums[0];
int sum = 0;
for(int i = 0; i < nums.size(); i++){
sum += nums[i];
if(sum < nums[i])
sum = nums[i];
if(sum > res)
res = sum;
}
return res;
}
};
// 2020.07.04 10:51
// 动态规划:f(i) = max{ f(i-1) + nums[i], nums[i] }
// 所以本质上上面我写的思路其实也是动态规划?
// 时间复杂度 O(n) 空间复杂度 O(1)
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if(nums.size() == 0)
return 0;
int pre = 0; // 滚动表示 前 i 个数组元素的最大子序和
int res = nums[0];
for(int i = 0; i < nums.size(); i++){
pre = max(pre + nums[i], nums[i]); // 更新最大自序和
res = max(res, pre); // 更新 f(i) 数组中的最大值
}
return res;
}
};
// 2020.07.04 11:52
// 分治策略:将(l,r)区间 转化为 (l,m) 和 (m+1,r) 两个区间
// 时间复杂度 O(n) 空间复杂度 O(1)
class Solution {
public:
struct Status{
// lsum 以左端点开始的最大子序和 rsum 以右端点结束的最大子序和
// msum 最大子序和 isum 整个区间的元素和
int lsum, rsum, msum, isum;
};
Status pushUp(Status l, Status r){
// 整合两个子区间
int isum = l.isum + r.isum;
int lsum = max(l.lsum, l.isum + r.lsum);
int rsum = max(r.rsum, r.isum + l.rsum);
int msum = max(max(l.msum, r.msum), l.rsum + r.lsum);
return (Status){lsum, rsum, msum, isum};
}
Status get(vector<int>& nums, int l, int r){
if(l == r)
return (Status){nums[l], nums[l], nums[l], nums[l]};
int m = (l + r) / 2;
Status left = get(nums, l, m);
Status right = get(nums, m + 1, r);
return pushUp(left, right);
}
int maxSubArray(vector<int>& nums) {
return get(nums, 0, nums.size() - 1).msum;
}
};
55. 跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
// 2020.07.05 11:17
// 贪心算法:依次遍历数组中的每一个位置,并实时维护“最远可以到达的位置”
// 时间复杂度 O(n) 空间复杂度 O(1)
class Solution {
public:
bool canJump(vector<int>& nums) {
int res = 0; // 最远可以到达的位置
for(int i = 0; i < nums.size(); i++){
// 首先得是 i 位置可以到达 才可以在 nums[i] 基础上继续增加
if(i <= res){
res = max(i + nums[i], res);
if(res >= nums.size() - 1)
return true;
}
}
return false;
}
};
以上是关于Daily LeetCode ---- 数组的主要内容,如果未能解决你的问题,请参考以下文章
[LeetCode] 739. Daily Temperatures
LeetCode 739. 每日温度 Daily Temperatures (Medium)
LeetCode 739. 每日温度 Daily Temperatures (Medium)