Letbook Cookbook题单——数组3
48. 旋转图像
难度中等
给定一个 n × n 的二维矩阵 matrix
表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
提示:
n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000
先把矩阵主对角线翻折再沿中间翻折
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
for(int i=1;i<matrix.size();i++)
for(int j=0;j<i;j++ )
{
swap(matrix[i][j],matrix[j][i]);
}
for(int i=0;i<matrix.size();i++)
for(int j=0;j<matrix.size()/2;j++)
{
swap(matrix[i][matrix.size()-j-1],matrix[i][j]);
}
}
};
53. 最大子数组和
难度中等
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 10^4
**进阶:**如果你已经实现复杂度为 O(n)
的解法,尝试使用更为精妙的 分治法 求解。
这题,我只能说经典中的经典,四种解法
暴力
前缀和预处理,然后枚举每个子数组,超时
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
vector<int>sum(nums.size()+1);
for(int i=1;i<=nums.size();i++)
sum[i]=sum[i-1]+nums[i-1];
int ans=INT_MIN;
for(int i=1;i<=nums.size();i++)
for(int j=i;j<=nums.size();j++)
ans=max(ans,sum[j]-sum[i-1]);
return ans;
}
};
贪心
连续的数组,当前一个子数组和为负数,则显然会丢弃
初始化数组和为0,最大值为第一个元素值
遍历数组,更新数组和,并且更新最大值,当数组和<0,则丢弃数组,数组和置零
class Solution
{
public:
int maxSubArray(vector<int>&nums){
int max=INT_MIN;
int sum=0;
for(int i=0;i<nums.size();i++)
{
sum+=nums[i];
if(sum>max)
max=sum;
if(sum<0)
sum=0;
}
return max;
}
};
动态规划
dp[i]代表必然选择第i个元素且以第i个元素为子数组最后一个元素的最大子数组和,那么状态转移方程就是dp[i]=dp[i-1]>=0?dp[i-1]+nums[i]:nums[i],和贪心一样,如果前面出现负数就没必要加进来
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
vector<int>dp(nums.size()+1);
int ans=INT_MIN;
for(int i=1;i<=nums.size();i++)
{
if(dp[i-1]>=0)
dp[i]=dp[i-1]+nums[i-1];
else
dp[i]=nums[i-1];
ans=max(ans,dp[i]);
}
return ans;
}
};
分治法
分治法虽然效率没有前面两者好,但是它的思想极其深刻,详细解释可以参考算法导论
假定我们要寻找子数组nums[low… high]的最大子数组。使用分治技术意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组nums[low. . mid]和 nums[mid+1…high]。如图所示,nums[low. . high]的任何连续子数组nums[i…j]所处的位置必然是以下三种情况之一:
完全位于子数组nums[low. . mid]中,因此low<=i<=mid。
完全位于子数组nums[mid+1…high]中,因此mid<i<=i<=j<=high。
跨越了中点,因此low<=i<=mid<j<=high。
因此,nums[low… high]的一个最大子数组所处的位置必然是这三种情况之一。实际上,A[low. . high]的一个最大子数组必然是完全位于nums[low. . mid]中、完全位于nums[mid+1…high]中或者跨越中点的所有子数组中和最大者。
**我们可以递归地求解nums[low. . mid]和 nums[mid+1…high]的最大子数组,因为这两个子问题仍是最大子数组问题,只是规模更小。**因此,剩下的全部工作就是寻找跨越中点的最大子数组,然后在三种情况中选取和最大者。
我们可以很容易地在线性时间(相对于子数组 nums[low. . high]的规模)内求出跨越中点的最大子数组。
此问题并非原问题规模更小的实例,因为它加人了限制——求出的子数组必须跨越中点。
如图所示,任何跨越中点的子数组都由两个子数组nums[i…mid]和nums[mid+1…j组成,其中 low≤i≤mid且mid<j≤high。因此,我们只需找出形如nums[i… mid]和 nums[mid+1…j]的最大子数组,然后将其合并即可。
函数find_max_max接收数组nums和下标low、mid和high为输人,返回一个下标元组划定跨越中点的最大子数组的边界,并返回最大子数组中值的和。
早期刷letcode用c语言写的
double find_max_mid(int *nums,int l,int r,int mid)
{
double lmax=-1e10,rmax=-1e10,maxl,maxr;
double sum=0;
for(int i=mid;i>=l;i--)
{
sum+=nums[i];
if(lmax<sum)
{
maxl=i;
lmax=sum;
}
}
sum=0;
for(int i=mid+1;i<=r;i++)
{
sum+=nums[i];
if(rmax<sum)
{
maxr=i;
rmax=sum;
}
}
return lmax+rmax;
}
double find_max(int *nums,int l,int r)
{
if(l==r)
return nums[l];
else
{
int mid=(l+r)/2;
double lmax=find_max(nums,l,mid);
double rmax=find_max(nums,mid+1,r);
double midmax=find_max_mid(nums,l,r,mid);
if(lmax>=rmax&&lmax>=midmax)
return lmax;
else if(rmax>=lmax&&rmax>=midmax)
return rmax;
else
return midmax;
}
}
int maxSubArray(int* nums, int numsSize){
return find_max(nums,0,numsSize-1);
}
54. 螺旋矩阵
难度中等
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100
按照题意模拟,可以用递归写法
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if(matrix.empty() || matrix[0].empty()) return {};
vector<int> res;
int m = matrix.size(), n = matrix[0].size();
// 确定上下左右四条边的位置
int up = 0, down = m - 1, left = 0, right = n - 1;
while (true)
{
for (int i = left; i <= right; i++) res.push_back(matrix[up][i]);
if (++up > down) break;
for (int i = up; i <= down; i++) res.push_back(matrix[i][right]);
if (--right < left) break;
for (int i = right; i >= left; i--) res.push_back(matrix[down][i]);
if (--down < up) break;
for (int i = down; i >= up; i--) res.push_back(matrix[i][left]);
if (++left > right) break;
}
return res;
}
};
55. 跳跃游戏
难度中等
给定一个非负整数数组 nums
,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
提示:
1 <= nums.length <= 3 * 10^4
0 <= nums[i] <= 10^5
尽可能到达最远位置(贪心)。
如果能到达某个位置,那一定能到达它前面的所有位置。
初始化最远位置为 0,然后遍历数组,如果当前位置能到达,并且当前位置+跳数>最远位置,就更新最远位置。最后比较最远位置和数组长度。
class Solution {
public:
bool canJump(vector<int>& nums) {
int r=nums[0];
for(int i=1;i<nums.size();i++)
{
if(i>r)
return false;
else
{
r=max(r,nums[i]+i);
}
}
return true;
}
};
12.7每日一题
1775. 通过最少操作次数使数组的和相等
难度中等127收藏分享切换为英文接收动态反馈
给你两个长度可能不等的整数数组 nums1
和 nums2
。两个数组中的所有值都在 1
到 6
之间(包含 1
和 6
)。
每次操作中,你可以选择 任意 数组中的任意一个整数,将它变成 1
到 6
之间 任意 的值(包含 1
和 6
)。
请你返回使 nums1
中所有数的和与 nums2
中所有数的和相等的最少操作次数。如果无法使两个数组的和相等,请返回 -1
。
示例 1:
输入:nums1 = [1,2,3,4,5,6], nums2 = [1,1,2,2,2,2]
输出:3
解释:你可以通过 3 次操作使 nums1 中所有数的和与 nums2 中所有数的和相等。以下数组下标都从 0 开始。
- 将 nums2[0] 变为 6 。 nums1 = [1,2,3,4,5,6], nums2 = [6,1,2,2,2,2] 。
- 将 nums1[5] 变为 1 。 nums1 = [1,2,3,4,5,1], nums2 = [6,1,2,2,2,2] 。
- 将 nums1[2] 变为 2 。 nums1 = [1,2,2,4,5,1], nums2 = [6,1,2,2,2,2] 。
示例 2:
输入:nums1 = [1,1,1,1,1,1,1], nums2 = [6]
输出:-1
解释:没有办法减少 nums1 的和或者增加 nums2 的和使二者相等。
示例 3:
输入:nums1 = [6,6], nums2 = [1]
输出:3
解释:你可以通过 3 次操作使 nums1 中所有数的和与 nums2 中所有数的和相等。以下数组下标都从 0 开始。
- 将 nums1[0] 变为 2 。 nums1 = [2,6], nums2 = [1] 。
- 将 nums1[1] 变为 2 。 nums1 = [2,2], nums2 = [1] 。
- 将 nums2[0] 变为 4 。 nums1 = [2,2], nums2 = [4] 。
提示:
1 <= nums1.length, nums2.length <= 105
1 <= nums1[i], nums2[i] <= 6
这题的贪心挺有意思,后面还可以用哈希优化
首先,根据题意,我们需要计算出数组nums1和nums2之间,最小的操作次数,使得nums1的总和:sum(nums1)与nums2的总和:sum(nums2)两个值相等。那么我们可以根据如下4个步骤来解决这个问题:
- 步骤1:分别计算sum(nums1)和sum(nums2)的值,确定两个数组加和的差值diff,以及sum(nums1)和sum(nums2)之间的大小关系。
- 步骤2将总和较小的数组赋值为int[] smaller,将总和较大的数组赋值为int[] bigger。
- 对于smaller数组中的每个值,我们要执行变大操作,其中:由于最大值是6,所以每个元素s变大的最大跨度是:6 - s;
对于bigger数组中的每个值,我们要执行变小操作,其中:由于最小值是1,所以每个元素b变大的最大跨度是:b - 1;
贪心
class Solution {
public:
int minOperations(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size() * 6 && nums1.size() * 6 < nums2.size())
return -1;
int sum1 = 0, sum2 = 0;;
for (auto i : nums1)
sum1 += i;
for (auto i : nums2)
sum2 += i;
vector<int>nums(nums1.size() + nums2.size());
int x = 0,dif;
if (sum1 < sum2)
swap(nums1, nums2), dif = sum2 - sum1;
else if(sum1==sum2)
return 0;
else
dif = sum1 - sum2;
for (auto& i : nums1)
nums[x++] =i-1;
for (auto& i : nums2)
nums[x++]=6-i;
sort(nums.begin(), nums.end());
for (int i = nums.size() - 1; i >= 0; i--)
{
dif -= nums[i];
if (dif <= 0)
return nums.size() - i;
}
return -1;
}
};
贪心+哈希
class Solution {
public:
int minOperations(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size() * 6 && nums1.size() * 6 < nums2.size())
return -1;
int sum1 = 0, sum2 = 0;;
for (auto i : nums1)
sum1 += i;
for (auto i : nums2)
sum2 += i;
int x = 0,dif;
if (sum1 < sum2)
swap(nums1, nums2), dif = sum2 - sum1;
else if(sum1==sum2)
return 0;
else
dif = sum1 - sum2;
int a[6]={0};
for (auto i : nums1)
a[i-1]++;
for (auto i : nums2)
a[6-i]++;
int ans=0;
for(int i=5;i>0;i--)
for(int j=0;j<a[i];j++)
{
dif-=i;
ans++;
if(dif<=0)
return ans;
}
return -1;
}
};