除自身以外數組的乘積
給定長度爲 n 的整數數組 nums,其中 n > 1,返回輸出數組 output ,其中 output[i] 等於 nums 中除 nums[i] 之外其餘各元素的乘積。
說明: 請不要使用除法,且在 O(n) 時間複雜度內完成此題。
分析: 對於某一個數字,如果我們知道其前面所有數字的乘積,同時也知道後面所有的數乘積,那麼二者相乘就是我們要的結果,所以我們只要分別創建出這兩個數組即可,分別從數組的兩個方向遍歷就可以分別創建出乘積累積數組。
優化: 不用單獨的數組來保存乘積,而是直接累積到結果 res 中,我們先從前面遍歷一遍,將乘積的累積存入結果 res 中,然後從後面開始遍歷,用到一個臨時變量 right,初始化爲1,然後每次不斷累積,最終得到正確結果
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
vector<int> 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<int>& 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<int>& 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:
vector<int>ori;
vector<int>cur;
int n;
default_random_engine e;
Solution(vector<int>& nums) {
ori=nums;
cur=nums;
n=nums.size();
}
vector<int> reset() {
return ori;
}
vector<int> 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<vector<int>> memo;
int dfs(vector<vector<int>>& 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(x<m-1 && matrix[x+1][y]>matrix[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(y<n-1 && matrix[x][y+1]>matrix[x][y])
ret = max(ret, 1 + dfs(matrix, x, y+1));
memo[x][y] = ret;
return ret;
}
int longestIncreasingPath(vector<vector<int>>& 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<int>& coins, int amount) {
vector<int>dp(amount+1,amount+1);
dp[0]=0;
for(int i=1;i<=amount;i++)
{
for(int j=0;j<coins.size();j++)
{
if(coins[j]<=i)
dp[i]=min(dp[i],dp[i-coins[j]]+1);
}
}
return dp[amount]>amount?-1:dp[amount];
}
};
最長連續序列
給定一個未排序的整數數組,找出最長連續序列的長度。
要求算法的時間複雜度爲 O(n)。
class Solution {
public:
int longestConsecutive(vector<int> &num) {
unordered_map<int,int> 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<int>& nums) {
if(nums.size()==0)return 0;
vector<int>dp(nums.size(),1);
for(int i=1;i<nums.size();i++)
{
for(int j=i-1;j>=0;j--)
{
if(nums[j]<nums[i])
{
dp[i]=max(dp[j]+1,dp[i]);
}
}
}
sort(dp.begin(),dp.end());
return dp[nums.size()-1];
}
};
lower_bound 返回數組中第一個不小於指定值的元素,跟上面的算法類似,還需要一個一維數組v,然後對於遍歷到的 nums 中每一個元素,找其 lower_bound,如果沒有 lower_bound,說明新元素比一維數組的尾元素還要大,直接添加到數組v中,跟解法二的思路相同了。如果有 lower_bound,說明新元素不是最大的,將其 lower_bound 替換爲新元素,這個過程跟算法二的二分查找法的部分實現相同功能,最後也是返回數組v的長度,注意數組v也不一定是真實的 LIS.
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> 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;
}
};