给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
分析: 对于某一个数字,如果我们知道其前面所有数字的乘积,同时也知道后面所有的数乘积,那么二者相乘就是我们要的结果,所以我们只要分别创建出这两个数组即可,分别从数组的两个方向遍历就可以分别创建出乘积累积数组。
优化: 不用单独的数组来保存乘积,而是直接累积到结果 res 中,我们先从前面遍历一遍,将乘积的累积存入结果 res 中,然后从后面开始遍历,用到一个临时变量 right,初始化为1,然后每次不断累积,最终得到正确结果
class Solution {
public:
vector productExceptSelf(vector& nums) {
vector res(nums.size(), 1);
for (int i = 1; i < nums.size(); ++i) {
res[i] = res[i - 1] * nums[i - 1];
}
int right = 1;
for (int i = nums.size() - 1; i >= 0; --i) {
res[i] *= right;
right *= nums[i];
}
return res;
}
};
给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列。
数学表达式如下:
如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ≤ n-1,使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否则返回 false 。
说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1) 。
分析: 遍历数组,如果m1大于等于当前数字,则将当前数字赋给m1;如果m1小于当前数字且m2大于等于当前数字,那么将当前数字赋给m2,一旦m2被更新了,说明一定会有一个数小于m2,那么我们就成功的组成了一个长度为2的递增子序列,所以我们一旦遍历到比m2还大的数,我们直接返回ture。如果我们遇到比m1小的数,还是要更新m1,有可能的话也要更新m2为更小的值,毕竟m2的值越小,能组成长度为3的递增序列的可能性越大
class Solution {
public:
bool increasingTriplet(vector& nums) {
int m1 = INT_MAX, m2 = INT_MAX;
for (auto a : nums) {
if (m1 >= a) m1 = a;
else if (m2 >= a) m2 = a;
else return true;
}
return false;
}
};
给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。
分析:
class Solution {
public:
int maxProduct(vector& nums) {
int res = nums[0], mx = res, mn = res;
for (int i = 1; i < nums.size(); ++i)
{
if (nums[i] < 0) swap(mx, mn);
mx = max(nums[i], mx * nums[i]);
mn = min(nums[i], mn * nums[i]);
res = max(res, mx);
}
return res;
}
};
打乱一个没有重复元素的数组。
C++11中,获取随机数的新方法default_random_engine,使用方法。
class Solution {
public:
vectorori;
vectorcur;
int n;
default_random_engine e;
Solution(vector& nums) {
ori=nums;
cur=nums;
n=nums.size();
}
vector reset() {
return ori;
}
vector shuffle() {
for (int i = 0; i < n; ++i) {
int j = (e() % (n - i)) + i;
swap(cur[i], cur[j]);
}
return cur;
}
};
给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
class Solution {
public:
int m, n;
vector> memo;
int dfs(vector>& matrix, int x, int y) {
if(memo[x][y] != -1)
return memo[x][y];
int ret = 1;
if(x>0 && matrix[x-1][y]>matrix[x][y])
ret = max(ret, 1 + dfs(matrix, x-1, y));
if(xmatrix[x][y])
ret = max(ret, 1 + dfs(matrix, x+1, y));
if(y>0 && matrix[x][y-1]>matrix[x][y])
ret = max(ret, 1 + dfs(matrix, x, y-1));
if(ymatrix[x][y])
ret = max(ret, 1 + dfs(matrix, x, y+1));
memo[x][y] = ret;
return ret;
}
int longestIncreasingPath(vector>& matrix) {
m = matrix.size();
if(m == 0) return 0;
n = matrix[0].size();
memo.resize(m);
int ans = 1;
for(int i = 0; i < m; ++i) memo[i].resize(n, -1);
for(int i = 0; i < m; ++i)
for(int j = 0; j < n; ++j)
ans=max(ans,dfs(matrix, i, j));
return ans;
}
};
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
分析:dp[i]表示钱数为i时的最小硬币数
class Solution {
public:
int coinChange(vector& coins, int amount) {
vectordp(amount+1,amount+1);
dp[0]=0;
for(int i=1;i<=amount;i++)
{
for(int j=0;jamount?-1:dp[amount];
}
};
给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。
class Solution {
public:
int longestConsecutive(vector &num) {
unordered_map len;
int max=0;
for(auto i:num)
{
if(len[i]==0)//避免重复元素
{
int l=len[i-1],r=len[i+1];
len[i]=l+r+1;
len[i+r]=l+r+1;
len[i-l]=l+r+1;
max=max>len[i]?max:len[i];
}
}
return max;
}
};
给定一个无序的整数数组,找到其中最长上升子序列的长度。
class Solution {
public:
int lengthOfLIS(vector& nums) {
if(nums.size()==0)return 0;
vectordp(nums.size(),1);
for(int i=1;i=0;j--)
{
if(nums[j]
lower_bound 返回数组中第一个不小于指定值的元素,跟上面的算法类似,还需要一个一维数组v,然后对于遍历到的 nums 中每一个元素,找其 lower_bound,如果没有 lower_bound,说明新元素比一维数组的尾元素还要大,直接添加到数组v中,跟解法二的思路相同了。如果有 lower_bound,说明新元素不是最大的,将其 lower_bound 替换为新元素,这个过程跟算法二的二分查找法的部分实现相同功能,最后也是返回数组v的长度,注意数组v也不一定是真实的 LIS.
class Solution {
public:
int lengthOfLIS(vector& nums) {
vector v;
for (auto a : nums) {
if (find(v.begin(), v.end(), a) != v.end()) continue;
auto it = upper_bound(v.begin(), v.end(), a);
if (it == v.end()) v.push_back(a);
else *it = a;
}
return v.size();
}
};
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
看到这个题的第一眼就知道用动态规划做,因为贪心的话样例就过不了,但是脑子不够用写不出来状态表达式dp[i]=min(dp[i],dp[i-j*j]+1)。
class Solution {
public:
int numSquares(int n) {
int dp[n+1]={0};
fill(dp,dp+n+1,INT_MAX);
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++)
{
for(int j=1;j*j<=i;j++)
{
dp[i]=min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
};
还有一种方法就是四平方和定理,第一次听说这个定理完全是懵的。
根据四平方和定理,任意一个正整数均可表示为4个整数的平方和,其实是可以表示为4个以内的平方数之和,那么就是说返回结果只有 1,2,3 或4其中的一个,首先我们将数字化简一下,由于一个数如果含有因子4,那么我们可以把4都去掉,并不影响结果,比如2和8,3和12等等,返回的结果都相同,读者可自行举更多的栗子。还有一个可以化简的地方就是,如果一个数除以8余7的话,那么肯定是由4个完全平方数组成,这里就不证明了,因为我也不会证明,读者可自行举例验证。那么做完两步后,一个很大的数有可能就会变得很小了,大大减少了运算时间,下面我们就来尝试的将其拆为两个平方数之和,如果拆成功了那么就会返回1或2,因为其中一个平方数可能为0. (注:由于输入的n是正整数,所以不存在两个平方数均为0的情况)。注意下面的 !!a + !!b 这个表达式,可能很多人不太理解这个的意思,其实很简单,感叹号!表示逻辑取反,那么一个正整数逻辑取反为0,再取反为1,所以用两个感叹号!!的作用就是看a和b是否为正整数,都为正整数的话返回2,只有一个是正整数的话返回1.
class Solution {
public:
int numSquares(int n) {
while (n % 4 == 0) n /= 4;
if (n % 8 == 7) return 4;
for (int a = 0; a * a <= n; ++a) {
int b = sqrt(n - a * a);
if (a * a + b * b == n) {
return !!a + !!b;
}
}
return 3;
}
};
你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。
每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。
你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。
每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。
你的目标是确切地知道 F 的值是多少。
无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?
class Solution {
public:
int calcMaximumCoverage(int iTime, int K)
{
if (iTime == 1) return 2;
if (K == 1) return iTime + 1;
return calcMaximumCoverage(iTime - 1, K - 1) + calcMaximumCoverage(iTime - 1, K);
}
int superEggDrop(int K, int N)
{
int ans = 1;
while (calcMaximumCoverage(ans, K) < N + 1)
{
++ans;
}
return ans;
}
};