Last updated on October 11, 2024 pm
尽量保证每天至少做两题,保持手感。累计11题。
2024/5/24 (1)接雨水 42. 接雨水 - 力扣(LeetCode)
记得第一次看到这题,两眼一黑就没管了。昨晚睡觉时突然想到,想了半天,今天写的时候也没写出来。总结错误原因是:1、想到了要记录左边最大值和右边最大值,但是记录的非常笨拙;2、记录的最大值不是真的最大值,而是每个凹陷两边的最大值。还是题感不够以及思路不够灵活。刚好这道题用到的很多方法是我不会的,遂记录。
方法一: (已超时)分别记录高度为1、2、3……等这一行能接多少雨水。需要遍历两层,记录当前列的左边和右边是否有值,若有则这一块可以接到雨水。时间复杂度O(n2);
方法二: 动态规划:1、用两个数组leftmax[]和rightmax[]分别记录某个位置左边高度的最大值和右边高度的最大值。两个最大值中小的那个减去它的高度就是这一列能接到的雨水量;2、如何得到两个数组的值?(动态规划)leftmax[0]=h[0],rightmax[n-1]=h[n-1]。从左向右遍历,得到等式:left[ i ]=max( left[ i-1],h[ i] ) , 从右向左遍历,得到right[i]=max(right[ i+1],h[ i ]);
时间复杂度:O(n),空间复杂度:O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int left[20004 ],right[20004 ]; int cnt,tmp;int trap (vector<int >& h) { int n=h.size (); left[0 ]=h[0 ],right[n-1 ]=h[n-1 ]; for (int i=1 ;i<n;i++){ left[i]=max (left[i-1 ],h[i]); right[n-i-1 ]=max (right[n-i],h[i]); } for (int i=1 ;i<n-1 ;i++){ tmp=min (left[i],right[i]); cnt+=tmp-h[i]; } return cnt; }
方法三: 单调栈:单调栈内存储的是下标。如果该下标高度小于栈顶,入栈;否则开始计算两个下标之间可以接的雨水量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int cnt;int trap (vector<int >& h) { stack<int > r; for (int i=0 ;i<h.size ();i++){ while (!r.empty ()&&h[r.top ()]<h[i]){ int cur=r.top (); r.pop (); if (r.empty ()) break ; cnt+=(min (h[r.top ()],h[i])-h[cur])*(i-r.top ()-1 ); } r.push (i); } return cnt; }
2024/5/28 (1)合并两个有序数组(字符串、数组) 88. 合并两个有序数组 - 力扣(LeetCode)
方法一: 直接合并然后排序(时间复杂度O(m+n)log(m+n))
方法二: 逆序双指针:m、n分别指向两个数组最后一个(不是0的)元素,然后每次作比较,将大的数字放在数组的最后一个位置,相应指针和位置前移。
1 2 3 4 5 6 7 8 9 10 11 12 13 void merge (vector<int >& nums1, int m, vector<int >& nums2, int n) { int i=m+n-1 ; while (n>0 ){ if (m>0 &&nums1[m-1 ]>nums2[n-1 ]){ nums1[i--]=nums2[m-1 ]; m--; } else { nums1[i--]=nums2[n-1 ]; n--; } } }
2024/5/29 (1)长度最小的子数组(二分、前缀和、滑动窗口) 209. 长度最小的子数组 - 力扣(LeetCode)
方法一:暴力法(超时) 方法二: 前缀和+二分查找:在想到使用前缀和之后,需要使用二分查找找到想要的下标才能使时间复杂度下降。而在每个语言中都已经有内置的二分查找函数,找到大于或者等于某个数的第一个位置。c++:lower_bound。时间复杂度:nlog(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int minSubArrayLen (int target, vector<int >& nums) { int n=nums.size (); if (n==0 ) return 0 ; int ans=INT_MAX; vector<int > sum (n+1 ,0 ) ; for (int i=1 ;i<=n;i++) sum[i]=sum[i-1 ]+nums[i]; for (int i=1 ;i<=n;i++){ int s=target+sum[i-1 ]; auto bound=lower_bound (sum.begin (),sum.end (),s); if (bound!=sum.end ()) ans=min (ans,bound-sum.begin ()-(i-1 )); } return ans==INT_MAX?0 :ans; }
方法三: 滑动窗口:设置左右指针表示窗口的两边,保证窗口内元素之和满足要求时,左指针右移,不断保存左右指针相差最小的那个值。当窗口不符合要求时,右指针继续右移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int min (int target,vetor<int > num) { int n=num.size (); if (!n) return 0 ; int ans=INT_MAX,l=0 ,r=0 ,sum=0 ; while (r<n){ sum+=num[r]; while (sum>=target){ ans=min (ans,r-l+1 ); sum-=num[l]; l++; } r++; } return ans==INT_MAX?0 :ans; }
(2)搜索二维矩阵(二分法) 方法一:两次二分查找 1 2 3 4 5 6 7 bool searchMatrix (vector<vector<int >>& num, int t) { auto row=upper_bound (num.begin (),num.end (),t,[](const int b,const vector<int > &a){return b<a[0 ];}); if (row==num.begin ()) return false ; --row; return binary_search (row->begin (),row->end (),t); }
方法二:一次二分查找(假想成一维数组) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool searchMatrix (vector<vector<int >>& num, int t) { int m=num.size (),n=num[0 ].size; int low=0 ,high=m*n-1 ; while (low<high){ int mid=(high-low)/2 +low; int x=num[mid/n][mid%n]; if (x<t) low=mid+1 ; else if (x>t) high=mid-1 ; else return true ; } return false ; }
(3)打家劫舍(一维动态规划) 198. 打家劫舍 - 力扣(LeetCode)
第一步:定义子问题:从k间房屋中可以偷到的最大金额
第二步:列出递推关系。题目要求不能够偷两间相邻的房屋,因此偷k间房屋就有了两个选择,f[k]=max( f[k-1] , f[k-2]+num[k-1] );
第三步:写出已知值。f[0]=0,f[1]=num[0];
1 2 3 4 5 6 7 8 9 10 int rob (vector<int >& num) { int n=num.size (); if (!n) return 0 ; vector<int > add (n+1 ,0 ) ; add[1 ]=num[0 ]; for (int i=1 ;i<n;i++){ add[i+1 ]=max (add[i],add[i-1 ]+num[i]); } return add[n]; }
第四步:空间优化。我们发现每次都只需要用到数组的最后两个值,因此用两个整数保存这两个值即可,不需要多余空间。
1 2 3 4 5 6 7 8 int rob (vector<int >& num) { int pre=0 ,cur=0 ,ans; for (int i:num){ ans=max (pre+i,cur); pre=cur; cur=ans; } }
(4)三角形最小路径和(二维动态规划) 120. 三角形最小路径和 - 力扣(LeetCode)
方法一:动态规划(自底向上)(不改动原数组) 设dp[i] [j]表示第i行第j个元素到最后一行的最短路径,则可以得到状态转移方程: dp[i] [j]=min(dp[i+1] [j] , dp[i+1] [j+1]) +triangle[i] [j],则dp[0] [0]为所需值。
1 2 3 4 5 6 7 8 9 10 int minimumTotal (vector<vector<int >>& triangle) { int n=triangle.size (); int [][] dp=new int [n+1 ][n+1 ]; for (int i=n-1 ;i>=0 ;i--){ for (int j=0 ;j<=i;j++){ dp[i][j]=min (dp[i+j][j],dp[i+1 ][j+1 ])+triangle[i][j]; } } return dp[0 ][0 ]; }
方法二:动态规划(空间优化) 我们会发现每次只需要用到两个先前值,因此不需要用到二维数组。dp数组不断保存每一行每个值遍历到最后一行的最短路径之和。
1 2 3 4 5 6 7 8 9 10 int minimumTotal (vector<vector<int >>& triangle) { int n=triangle.size (); int [] dp=new int [n+1 ]; for (int i=n-1 ;i>=0 ;i--){ for (int j=0 ;j<=i;j++){ dp[j]=min (dp[j],dp[j+1 ])+triangle[i][j]; } } return dp[0 ]; }
2024/5/30 (1)最小路径和(二维动态规划) 64. 最小路径和 - 力扣(LeetCode)
跟昨天做的题思路很像(相当于偷个小懒hhh),也是丝滑地ac了,就是没有空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int minPathSum (vector<vector<int >>& grid) { int m=grid.size (),n=grid[0 ].size (); int dp[205 ][205 ]; dp[m-1 ][n-1 ]=grid[m-1 ][n-1 ]; for (int i=m-2 ;i>=0 ;i--) dp[i][n-1 ]=dp[i+1 ][n-1 ]+grid[i][n-1 ]; for (int i=n-2 ;i>=0 ;i--) dp[m-1 ][i]=dp[m-1 ][i+1 ]+grid[m-1 ][i]; for (int i=m-2 ;i>=0 ;i--){ for (int j=n-2 ;j>=0 ;j--){ dp[i][j]=min (dp[i+1 ][j],dp[i][j+1 ])+grid[i][j]; } } return dp[0 ][0 ]; } };
试着空间优化一下:(分类讨论地好狗屎,但是不知道怎么分情况更简单了。。。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int minPathSum (vector<vector<int >>& grid) { int m=grid.size (),n=grid[0 ].size (); vector<int > dp (n,0 ) ; dp[0 ]=grid[0 ][0 ]; for (int i=0 ;i<m;i++){ for (int j=0 ;j<n;j++){ if (i==0 &&j!=0 ) dp[j]=dp[j-1 ]+grid[i][j]; else if (j==0 &&i!=0 ) dp[j]=dp[j]+grid[i][j]; else if (i!=0 &&j!=0 ) dp[j]=min (dp[j-1 ],dp[j])+grid[i][j]; } } return dp[n-1 ]; }
(2)不同路径(二维动规、障碍物) 63. 不同路径 II - 力扣(LeetCode)
方法一:动规数组存储该位置的路径和,遇到障碍物该位置路径数变为零。 AC,但是空间复杂度有点高,而且在初始化的时候容易犯错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int uniquePathsWithObstacles (vector<vector<int >>& obstacleGrid) { int m=obstacleGrid.size (),n=obstacleGrid[0 ].size (); int dp[105 ][105 ]; dp[0 ][0 ]=obstacleGrid[0 ][0 ]==1 ?0 :1 ; for (int i=1 ;i<m;i++) dp[i][0 ]=dp[0 ][0 ]; for (int i=1 ;i<n;i++) dp[0 ][i]=dp[0 ][0 ]; for (int i=1 ;i<m;i++){ for (int j=1 ;j<n;j++){ if (obstacleGrid[i][j]) dp[i][j]=0 ; else dp[i][j]=dp[i-1 ][j]+dp[i][j-1 ]; } } return dp[m-1 ][n-1 ]; }
方法二:空间优化(滚动数组) 1 2 3 4 5 6 7 8 9 10 11 12 13 int uniquePathsWithObstacles (vector<vector<int >>& obstacleGrid) { int m=obstacleGrid.size (),n=obstacleGrid[0 ].size (); vector<int > dp (n,0 ) ; dp[0 ]=obstacleGrid[0 ][0 ]==1 ?0 :1 ; for (int i=0 ;i<m;i++){ for (int j=0 ;j<n;j++){ if (obstacleGrid[0 ][0 ]) dp[j]=0 ; else if (j>0 ) dp[j]+=dp[j-1 ]; } } }
(3)最长回文子串 5. 最长回文子串 - 力扣(LeetCode)
方法一:中心扩展法 分为两种情况:1、回文串长度是奇数;2、回文串长度是偶数。因此从中心向外扩展时也就有两种情况:1、中心是当前值;2、中心是当前值与右边值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 string longestPalindrome (string s) { int n=s.size (),pre=0 ,maxL=0 ; for (int i=0 ;i<n;i++){ int l=i-1 ,r=i; for (;l>=0 &&r<n&&s[l]==s[r];l--,r++); if (maxL<r-l+1 ){ maxL=r-l+1 ; pre=l+1 ; } l=i-1 ,r=i+1 ; for (;l>=0 &&r<n&&s[l]==s[r];l--,r++); if (maxL<r-l+1 ){ maxL=r-l+1 ; pre=l+1 ; } } return s.substr (pre,maxL); }
方法二:动态规划 不知道为什么力扣不能完全通过……不理解
理解了,因为没有初始化为false啊啊啊啊我要气死了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 string longestPalindrome (string s) { int len=s.length (); int left=0 ,right=0 ,res=0 ; bool dp[1005 ][1005 ]={false }; for (int i=len-1 ;i>=0 ;i--){ for (int j=i;j<len;j++){ if (s[i]==s[j]&&(j - i <= 1 || dp[i + 1 ][j - 1 ])){ dp[i][j] = true ; if (j-i>res){ res=j-i; left=i; right=j; } } } } return s.substr (left,res+1 ); }
(4)删除有序数组中的重复项 II(双指针) 80. 删除有序数组中的重复项 II
方法一:双指针(?) 自己写的,有点丑陋……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int removeDuplicates (vector<int >& nums) { if (nums.size ()<3 ) return nums.size (); int i=0 ; while (i<nums.size ()-1 ){ int j=i+2 ; while (j<nums.size ()&&nums[i]==nums[i+1 ]&&nums[i]==nums[j]){ nums.erase (nums.begin ()+j); } if (nums[i]==nums[i+1 ]) i+=2 ; else i+=1 ; } return nums.size (); }
方法二:单指针 首先,定义一个指针 i
,用来记录不重复元素的位置。
遍历数组 nums
,对于数组中的每个元素 num
:
如果 i
小于 2(即数组前两个元素),或者当前元素 num
大于 nums[i - 2]
(说明当前元素和前两个元素不相同),则将当前元素 num
赋值给 nums[i]
,并将指针 i
向后移动一位,相当于将当前元素保留下来。
如果当前元素 num
和 nums[i - 2]
相同(说明当前元素已经重复出现两次以上),则不做任何操作,直接继续遍历下一个元素。
最后返回指针 i
,即为删除重复元素后数组的新长度。
1 2 3 4 5 6 7 8 9 10 11 class Solution { public int removeDuplicates (int [] nums) { int i = 0 ; for (int num : nums) { if (i < 2 || num > nums[i - 2 ]) { nums[i++] = num; } } return i; } }
(5)买卖股票的最佳时机 121. 买卖股票的最佳时机
自己写的代码太丑陋了而且还有bug(不知道为啥),这是chatgpt写的,跟我一样的思路怎么就这么简洁呢呃呃呃,怎么不算天才呢
我用了两个数组去记录……左边最小值和右边最大值,怎么不算愚蠢呢……
1 2 3 4 5 6 7 8 9 int maxProfit (vector<int >& prices) { int minPrice = INT_MAX; int maxProfit = 0 ; for (int price : prices) { minPrice = min (minPrice, price); maxProfit = max (maxProfit, price - minPrice); } return maxProfit; }
2024/5/31 (1)买卖股票的最佳时机||(动规、贪心) 122. 买卖股票的最佳时机 II - 力扣(LeetCode)
方法一:直接求解/贪心 按照直接求解的思路,如果要使利润最大化,只需要把股票价格看成折线图,计算每一次上升的利润。如果把距离看为1,就看每两天是否有利润,只要有就增加利润值。
按照贪心的思路,由于股票的购买没有限制,因此相当于找到若干个不相交的区间,使区间内利润和最大。如果区间长度为1,也就等同于了刚刚的思路。
1 2 3 4 5 6 int maxProfit (vector<int >& prices) { int maxp=0 ,n=prices.size (); for (int i=0 ;i<n-1 ;i++) maxp+=max (0 ,prices[i+1 ]-prices[i]); return maxp; }
方法二:动态规划 虽然知道可以动态规划但是就是想不到怎么规划!!!
首先要设置状态,可以设置dp[i] [0]表示第i天交易完时手里没有股票的最大利润,dp[i] [1]则表示手里有股票。下面就可以列状态转移方程。(一开始真的想复杂了,所以完全不知道这怎么转移)对于dp[i] [0],有两种情况:1、dp[i] [0]=dp[i-1] [0](和前一天保持一致) 2、dp[i] [0]=dp[i-1] [1]+prices[i](前一天有股票,今天卖出,获得今天的收益,不用管成本)对于dp[i] [1]:1、dp[i] [1]=dp[i-1] [1];2、dp[i] [1]=dp[i-1] [0]-prices[i]
遍历结束后,我们手中有两个值,dp[n-1] [0]和dp[n-1] [1],显然最后手里没有股票的话利润才是最大的,因此应该返回dp[n-1] [0]。
对于这道题也可以空间优化,因为每次都只是用到了前一天的两个值而已。
1 2 3 4 5 6 7 8 9 10 11 int maxProfit (vector<int >& prices) { int n=prices.size (),dp0,dp1,newdp0,newdp1; dp0=0 ,dp1=-prices[0 ]; for (int i=1 ;i<n;i++){ newdp0=max (dp0,dp1+prices[i]); newdp1=max (dp1,dp0-prices[i]); dp0=newdp0; dp1=newdp1; } return dp0; }
(2)三数之和(双指针) 15. 三数之和 - 力扣(LeetCode)
方法一:自己写的(我也想写双指针法??) 通过了308/313个用例呜呜呜我哭死。最后是时间超了,我的代码好丑陋……感觉超时间应该是因为用到了查找函数,有点丑陋。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 vector<vector<int >> threeSum (vector<int >& nums) { int n=nums.size (),cur; vector<vector<int >> num; sort (nums.begin (),nums.end ()); if (n<3 ||nums[0 ]>0 ||nums[n-1 ]<0 ) return num; vector<int > tmp; for (int i=0 ;i<n-2 ;i++){ if (nums[i]>0 ) break ; if (i>0 &&nums[i]=nums[i+1 ]) continue ; for (int j=i+1 ;j<n-1 ;j++){ cur=nums[i]+nums[j]; cur=0 -cur; auto it=find (nums.begin ()+j+1 ,nums.end (),cur); if (it!=nums.end ()){ tmp={nums[i],nums[j],nums[it-nums.begin ()]}; if (num.empty ()||find (num.begin (),num.end (),tmp)==num.end ()) num.push_back (tmp); } } } return num; }
方法二:(相向)双指针法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 vector<vector<int >> threeSum (vector<int >& nums) { sort (nums.begin (),nums.end ()); vector<vector<int >> num; int n=nums.size (); if (nums[n-1 ]+num[n-2 ]+nums[n-3 ]<0 ) return num; for (int i=0 ;i<n-2 ;i++){ int x=nums[i]; if (i&&x=nums[i-1 ]) break ; if (nums[i]+num[i+1 ]+nums[i+2 ]>0 ) break ; if (nums[n-1 ]+num[n-2 ]+nums[i]<0 ) continue ; int j=i+1 ,k=n-1 ; while (j<k){ int t=x+nums[j]+nums[k]; if (t>0 ) k--; else if (t<0 ) s--; else { num.push_back{x,nums[j],nums[k]}; for (++j;j<k&&nums[j]==nums[j-1 ];++j); for (--k;j<k&&nums[k]==nums[k+1 ];--k); } } } return num; }
(3)买卖股票的最佳时机|||(k次)(动态规划) 188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
我是真的没有想到还有|||……后面好像还有IV。今天下午都要买卖股票啦,哈哈哈(苦笑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int maxProfit (vector<int >& prices) { const int inf=INT_MIN/2 ; int n=prices.size (); int buy1=inf; int sell1=0 ; int buy2=inf; int sell2=0 ; for (int i=0 ;i<n;i++){ sell1=max (sell1,buy1+prices[i]); buy1=max (buy1,-prices[i]); sell2=max (sell2,buy2+prices[i]); buy2=max (buy2,sell1-prices[i]); } return sell2; }
(4)买卖股票的最佳时期(k次) 188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int maxProfit (int k, vector<int >& prices) { int n=prices.size (); k=min (k,n/2 ); vector<int > buy (k+1 ) ; vector<int > sell (k+1 ) ; for (int i=0 ;i<n;i++){ buy[i]=INT_MIN/2 ; sell[i]=0 ; } for (int i=0 ;i<n;i++){ for (int j=1 ;j<=k;j++){ buy[j]=max (buy[j],sell[j-1 ]-prices[i]); sell[j]=max (sell[j],buy[j-1 ]+prices[i]); } } return sell[k]; }
(5)阶乘后的0(数学) 172. 阶乘后的零 - 力扣(LeetCode)
这居然是中等??你的中等我的中等好像不一样??自己ac了但是贴个复杂度更小的解法
1 2 3 4 5 6 7 8 int trailingZeroes (int n) { int ans=0 ; while (n){ n=n/5 ; ans+=n; } return ans; }
2024/6/1 休息一天。。头疼
2024/6/2 (1)直线上最多的点数 149. 直线上最多的点数 - 力扣(LeetCode)
枚举直线+哈希表:外层循环遍历每一个点,求出跟该点在同一条直线上的点的最大值,然后再得到所有点中的最大值。题目中用字符串保存斜率,求出x坐标差和y坐标差后,求出他们的最大公约数,分别除去再存储,可以有效的维护相同斜率的哈希表,也可以很好地使x坐标差为0的情况方便存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : int maxPoints (vector<vector<int >>& points) { int n = points.size (), ans = 1 ; for (int i = 0 ; i < n; i++) { map<string, int > map; int maxv = 0 ; for (int j = i + 1 ; j < n; j++) { int x1 = points[i][0 ], y1 = points[i][1 ], x2 = points[j][0 ], y2 = points[j][1 ]; int a = x1 - x2, b = y1 - y2; int k = gcd (a, b); string key = to_string (a / k) + "_" + to_string (b / k); map[key]++; maxv = max (maxv, map[key]); } ans = max (ans, maxv + 1 ); } return ans; } int gcd (int a, int b) { return b == 0 ? a : gcd (b, a % b); } };
求最小公倍数的代码:
1 2 3 4 5 6 int lcm (int a, int b) { int num = gcd (a, b); return (a * b) / num; }
(2)零钱兑换(一维动规) 322. 零钱兑换 - 力扣(LeetCode)
很好的题目,使我小脑萎缩。
1 2 3 4 5 6 7 8 9 10 int coinChange (vector<int >& coins, int amount) { int MAX=amount+1 ; vector<int > dp (amount+1 ,MAX) ; dp[0 ]=0 ; for (int i=0 ;i<=amount;i++) for (int j=0 ;j<(int )coins.size ();++j) if (coins[j]<=i) dp[i]=min (dp[i],dp[i-coins[j]]+1 ); return dp[amount]==MAX?-1 :dp[amount]; }