经典150题
双指针26、删除有序数组中的重复项I
我的思路:指针遍历,如果相同则原地erase,继续遍历
int removeDuplicates(vector<int>& nums) {
vector<int>::iterator it=nums.begin()+1;
while (it!=nums.end()){
if (*it == *(it-1)){
nums.erase(it);
}
else it+=1;
}
return nums.size();
}
双指针:一快一慢遍历数组,快指针所指的值若非慢指针所指的值,则慢指针后移一位,将此值加入
int removeDuplicates(vector<int>& nums) {
int p=0, q=1;
while (q!=nums.size()){
if (nums[q++]!=nums[p]){
nums[++p] = nums[q-1];
}
}
return p+1;
}
双指针80、删除有序数组中的重复项II
我的思路:指针遍历,如果连续三个相同则原地erase,继续遍历
int removeDuplicates(vector<int>& nums) {
if (nums.size()<3) return nums.size();
vector<int>::iterator it=nums.begin()+2;
while (it!=nums.end()){
if (*it == *(it-1) && *it == *(it-2)){
nums.erase(it);
}
else it += 1;
}
return nums.size();
}
双指针:一快一慢遍历数组,快指针所指的值若非慢指针-2所指的值,则慢指针后移一位,将此值加入
int removeDuplicates(vector<int>& nums) {
int n=nums.size(), slow=2, fast=2;
if (n<3) return n;
while (fast != n){
if (nums[fast]!=nums[slow-2]) {
nums[slow] = nums[fast];
slow += 1;
}
fast += 1;
}
return slow;
}
双向遍历135、分发糖果
思路:初始化全部分发1个糖果,从左向右遍历:若比左邻大则更新糖果数量;从右向左遍历:若比右邻大且糖果数小于等于其糖果数,则更新糖果数量。
int candy(vector<int>& ratings) {
int n=ratings.size();
//初始化
int res[n], sum=0;
for (int i=0;i<n;i++) res[i] = 1;
//从左侧扫一次,判断是否多
for (int i=1;i<n;i++){
if (ratings[i]>ratings[i-1])
res[i]=res[i-1]+1;
}
//从右侧扫一次,判断是否多
for (int i=n-2;i>-1;i--){
if (ratings[i]>ratings[i+1] && res[i]<=res[i+1])
res[i]=res[i+1]+1;
}
//记录总数
for (int i=0;i<n;i++) sum+=res[i];
return sum;
}
贪心55、跳跃游戏I
我的思路:遍历时更新可达到的最远位置,直到边界或无法扩充
bool canJump(vector<int>& nums) {
int n = nums.size();
int now = 0, maxindex = nums[now];
while (maxindex<n && now<maxindex){ //没访问到n-1并且指针可以继续前进
now += 1;
maxindex = max(maxindex, now+nums[now]);
}
if (maxindex >= n-1) return true;
return false;
}
贪心45、跳跃游戏II
思路1:遍历时更新可跳范围的最小跳跃次数
int jump(vector<int>& nums) {
int n = nums.size();
int dp[n];
int now=0;
for (int i=0;i<n;i++) dp[i]=1e5;
dp[0] = 0;
while (now<n){ //遍历数组
for (int i=now+1;i<=now+nums[now]&&i<n;i++){
dp[i] = min(dp[i], dp[now]+1); //取最小跳跃次数
}
now += 1;
}
return dp[n-1];
}
思路2:每次跳跃有一个范围,边更新范围边增加最少跳跃次数
int jump(vector<int>& nums) {
int n = nums.size();
if (n<2) return 0; //不需要跳
int now=0, right=0, cnt=0, maxindex=0;
while (now<n-1){ //遍历数组
maxindex = max(maxindex, now+nums[now]);
if (now == right){ //走到上次的边界了
right = maxindex;
cnt += 1;
}
now += 1;
}
return cnt;
}
数学题274、H指数I
数学题275、H指数II
我的思路:非降序数组,比较大于等于该因子的论文数量和该因子的大小,取较小的数作为当前索引的h值,遍历取最大即可
int hIndex(vector<int>& citations) {
sort(citations.begin(), citations.end()); //有序则不用sort
int n = citations.size(), h=0;
for (int i=0;i<n;i++){
h = max(min(n-i, citations[i]), h);
}
return h;
}
数学题238、除自身以外数组的乘积
我的思路:构造前缀乘积和后缀乘积列表,最后用一次遍历得到答案
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
int left[n], right[n];
left[0] = right[n-1] = 1;
vector<int> res;
for (int i=1;i<n;i++){ //构造前缀连乘
left[i] = left[i-1]*nums[i-1];
}
for (int j=n-2;j>-1;j--){ //构造后缀连乘
right[j] = right[j+1]*nums[j+1];
}
for (int i=0;i<n;i++){
res.push_back(left[i]*right[i]);
}
return res;
}
O(1)空间复杂度的思路:仅构造前缀乘积,后缀乘积用一个变量代替
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
int right = 1;
vector<int> res;
res.push_back(1);
for (int i=1;i<n;i++){ //构造前缀连乘
res.push_back(res[i-1]*nums[i-1]);
}
for (int j=n-2;j>-1;j--){ //构造后缀连乘
right *= nums[j+1];
res[j] = res[j]*right;
}
return res;
}
数学题134、加油站
我的思路:构造净收入数组,将每个元素作为起始位置进行求和&判断,时间复杂度O(n^2)
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size();
int gain[n], sum=0, positive=-1;
for (int i=0;i<n;i++){ //计算纯收入
gain[i] = gas[i]-cost[i];
sum += gain[i];
if (gain[i]>0 && positive==-1) positive=i;
}
if (sum<0) return -1; //没办法跑完一圈
if (positive==-1) return 0; //全部元素都是0
else{
int i, j;
for (i=positive;i<n;i++){
sum=gain[i];
for (j=i+1;j!=n+i&&sum>=0;j++){
sum += gain[j%n];
}
if (j==n+i && sum>=0) return i;
}
return 0;
}
}
O(n)思路:假如当前一段路无法走下去了,就该放弃 换个新的起点了——这个起点最多只能到这,从这段路的任何地方重新开始都到达不了更远的地方。因为放弃前走的路都是帮你的,不然早就无法走下去了。因此 起点只能选在下一站,错的不是你,是你的起点和路。
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size();
int now=0;
while (now<n){ //遍历搜索起始位置
int sum = gas[now]-cost[now]; //初始存油量
if (sum>=0){
int i;
for (i=now+1;i<n+now&&sum>=0;i++){
sum += gas[i%n]-cost[i%n]; //继续向前跑
}
if (sum<0) now=i; //用下个位置作为起始
else return now;
}
else now += 1;
}
return -1;
}
数学题189、轮转数组
我的思路:利用取余操作和另外一个数组空间
void rotate(vector<int>& nums, int k) {
vector<int> res=nums;
int n=nums.size();
for (int i=0;i<n;i++){
res[(i+k)%n] = nums[i];
}
nums = res;
}
方法二:多个环内部移位
设总共有a个圈,每个过程遍历b个元素,因此an=bk,即an一定为n,k的公倍数;
又因为在第一次回到起点时就结束,所以a要尽可能小,故an就是n,k的最小公倍数;
a=lcm(n,k),b=lcm(n,k)/k,这说明单次遍历会访问到 lcm(n,k)/k个元素;
为了访问到所有的元素,需要进行遍历的次数为
void rotate(vector<int>& nums, int k) {
int n=nums.size();
k=k%n;
int cnt = gcd(k, n), now, t, next;
for (int i=0;i<cnt;i++){
now = i;
t = nums[i];
do{ //还没有到环的结尾
next = (now+k)%n;
swap(nums[next], t);
now = next;
}while (now!=i);
}
}
方法三:内部翻转
void reverse(vector<int>& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]);
start += 1;
end -= 1;
}
}
void rotate(vector<int>& nums, int k) {
k %= nums.size();
reverse(nums, 0, nums.size() - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.size() - 1);
}
贪心121、买卖股票的最佳时机I
我的思路:扫描一遍,找当前最小的股票价格和最大的价格差
int maxProfit(vector<int>& prices) {
int minprice=prices[0], maxprice=0, n=prices.size();
for (int i=1;i<n;i++){
maxprice = max(maxprice, prices[i]-minprice); //最大价格差值
minprice = min(minprice, prices[i]); //最小的股票价格
}
return maxprice;
}
动态规划122、买卖股票的最佳时机II
思路:dp0存储当前不持股的最大利润,dp1存储当前持股的最大利润
初始化条件:dp0[0] = 0, dp1[0]=-prices[0];
迭代方程:
(1)手中不持股:要么卖了之前的,要么一直没持股
dp0[i] = max(dp1[i-1]+prices[i], dp0[i-1])
(2)手中持有第i股:要么卖了之前的换今天的,要么一直没持股今天买
dp1[i] = max(dp1[i-1]+prices[i]-prices[i], dp0[i-1]-prices[i])
int maxProfit(vector<int>& prices) {
int n = prices.size();
int dp0[n], dp1[n];
dp0[0] = 0;
dp1[0] = -prices[0];
for (int i=1;i<n;i++){
dp0[i] = max(dp1[i-1]+prices[i], dp0[i-1]); //不持:要么当天卖,要么就没持股
dp1[i] = max(dp1[i-1]+prices[i]-prices[i], dp0[i-1]-prices[i]); //持第i股:之前没持股 当天买;或者卖了再买
}
return dp0[n-1];
}
动态规划198、打家劫舍
我的思路:dp0存储不拿当前房屋的最大价值,dp1存储拿当前房屋现金的最大价值
初始化条件:dp0[0] = 0, dp1[0]=nums[i];
迭代方程:
(1)当前房屋不拿:之前没拿和之前拿了,取最大值
dp0[i] = max(dp0[i-1], dp1[i-1])
(2)要拿当前房屋的,之前房屋的现金不能拿:
dp1[i] = dp0[i-1]+nums[i]
int rob(vector<int>& nums) {
int n = nums.size();
int dp0[n], dp1[n];
dp0[0] = 0;
dp1[0] = nums[0];
for (int i=1;i<n;i++){
dp0[i] = max(dp0[i-1], dp1[i-1]); //不拿:取之前最多的现金总和
dp1[i] = dp0[i-1]+nums[i];
}
return max(dp0[n-1], dp1[n-1]);
}