LeetCode刷题总结period 1
11. Container With Most Water
思路:
最简单的使用暴力法破解,根据面积公式S=min(height[i],height[j])*(j-i);
求解.
改进版本,使用双指针,题目这里有个证明,即:
线之间形成的区域总是受限于短线的高度。此外,线越远,所获得的区域就越多。 我们使用两个指针,一个在开头,一个在数组末尾,构成行长度。此外,我们还保留了一个变量max来存储到目前为止获得的最大面积。在每步中,我们找到它们之间形成的区域,更新max并将指针指向另一端的指针。因为木桶原理,容积取决于行长度和最短高度的积,所以,两个端点高度较低的需要移动,因为高度较高的移动不可能大于原来的两端点积。这样,每次都是高度低的移动,直到两指针相邻。
int maxArea(vector<int>& height) {
int water = 0;
int i = 0, j = height.size() - 1;
while (i < j) {
int h = min(height[i], height[j]);
water = max(water, (j - i) * h);
while (height[i] <= h && i < j) i++;
while (height[j] <= h && i < j) j--;
}
return water;
}
22. Generate Parentheses
典型的dfs模板题,需要注意dfs条件的控制,即左括号open必须在右括号close的前面产生,且在递归过程中右括号不能比左括号多。代码如下:
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> ans;
dfs(ans,"",0,0,n);
return ans;
}
void dfs(vector<string>& ans,string cur,int open,int close,int len){
if(cur.size()==len*2){
ans.push_back(cur);
}
if(open<len){
dfs(ans,cur+"(",open+1,close,len);
}
if(close<open)
dfs(ans,cur+")",open,close+1,len);
}
};
88. Merge Sorted Array
注意此题有个很简洁的写法,从末尾开始比较,详见代码:
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int i = m - 1, j = n - 1, tar = m + n - 1;
while (j >= 0) {
nums1[tar--] = i >= 0 && nums1[i] > nums2[j] ? nums1[i--] : nums2[j--];
}
}
};
123. Best Time to Buy and Sell Stock III
思路1:推导法
定义int buyFirst = INT_MIN, sellFirst = 0, buySecond = INT_MIN, sellSecond = 0;
,分别表示第一次买入时的余额。第一次卖出的余额、第二次买入后的余额、第二次卖出后的余额,则题目转化为求:
sellSecond=max(sellSecond,buySecond +price[i])
最大值,因而我们希望buySecond
也尽可能大,又buySecond = max(buySecond, sellFirst - price[i])
,即要求sellFirst
尽可能大,又sellFirst = max(sellFirst, buyFirst + price[i])
,则为buyFirst
求最大,又buyFirst = max(buyFirst, -price[i])
,故代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int buyFirst = INT_MIN, sellFirst = 0, buySecond = INT_MIN, sellSecond = 0;
for (int p : prices) {
buyFirst = max(buyFirst, -p);
sellFirst = max(sellFirst, buyFirst + p);
buySecond = max(buySecond, sellFirst - p);
sellSecond = max(sellSecond, buySecond + p);
}
return sellSecond;
}
};
思路二:dp
使用二维dp,其中dp[i][j]表示购买次数为i,当前价格为price[j]的时候的最大收益。
对于当前价格,最大收益只有两种情况:
- 要么已经用完了购买次数,此时
dp[i,j]=dp[i,j-1]
,和当前价格无关 - 若还有购买机会,则要考虑当前价格是否能生成更大的增益,即考虑第k位到当前位的一次购买+第k为前的最大收益,则此时
dp[i,j]=price[j]-price[k]+dp[i-1,k]
,其中k取值范围在[0,j-1]。
故dp[i,j]=max{dp[i,j-1],price[j]-price[k]+dp[i-1,k]}
class Solution {
public:
int maxProfit(vector<int> &prices) {
/*
dp[i,j]表示购买i次,价格为price[j]时的最大收益
dp[i,j]=max{dp[i,j-1],price[j]-price[k]+dp[i-1,k]},其中k in range of [0,j-1]
=max{dp[i,j-1],max{dp[i-1,k]-price[k]}+price[j]}
dp[0,j]=0; // 0 times transation makes 0 profit
dp[i,0]=0 //only one price data point
*/
if (prices.size() <= 1) return 0;
else {
int K = 2; // number of max transation allowed
int maxProf = 0;
vector<vector<int>> f(K+1, vector<int>(prices.size(), 0));
for (int kk = 1; kk <= K; kk++) {
int tmpMax = f[kk-1][0] - prices[0];
for (int ii = 1; ii < prices.size(); ii++) {
f[kk][ii] = max(f[kk][ii-1], prices[ii] + tmpMax);
tmpMax = max(tmpMax, f[kk-1][ii] - prices[ii]);
maxProf = max(f[kk][ii], maxProf);
}
}
return maxProf;
}
}
};
219. Contains Duplicate II
此题一开始用两层for循环来做,结果超时了。
我们改用one pass的思路,使用unordered_map
来做,一次遍历依次加入即可,碰到重复的元素则比较下标差是否在k之间。
另外,注意此处选用unordered_map
而不选用map
的原因,两者的区别如下:
map: map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。
unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。
对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map。
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
if(k <= 0) return false;
if(nums.size() == 0 || nums.size() == 1) return false;
//如果nums[i]已经在map中,判断其value和i的距离是否小于等于k,是,则返回true。否,则将nums[i]对应的value修改为当前的i值
unordered_map<int, int> mapArr;
for(int i = 0; i < nums.size(); i ++){
if(mapArr.find(nums[i]) == mapArr.end())
mapArr.insert({nums[i], i});
else{
if(i - mapArr[nums[i]] <= k)
return true;
else
mapArr[nums[i]] = i;
}
}
return false;
}
};
258. Add Digits
此题涉及到余九定理,即
dr(n)表示对于数字n的digit root之和。
若a%n=b%n,则记为:
也即(a-b)/n为一整数
,则代码如下:
class Solution {
public:
int addDigits(int num) {
return 1+(num-1)%9;
}
};
268. Missing Number
两个方法,一种是利用高斯求和减去数组累加的结果;
一种为异或操作。异或操作思路如下:
异或规则有:a^0=a
,数组值为0~n,数组下标为0~n-1
我们只需要初始化异或n,将下标和数值累积异或即可,最终结果即为缺失的数字。
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n=nums.size();
int sum=n;
for(int i=0;i<n;i++){
sum =sum^i^nums[i];
}
return sum;
}
};
278. First Bad Version
该题暴力超时了,考虑二分查找,即问题可简化成在数组中查找第一个比n大的数,相比于传统二分法,需要处理好条件–“第一个”,代码如下:
// Forward declaration of isBadVersion API.
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
long left=1,right=n;
while(left<right){
long mid=(left+right)/2;
if(isBadVersion(mid)){ // 为true,即在左边
if(mid>=2&&isBadVersion(mid-1)==false) // 正好为当前点
return mid;
right=mid-1; // 不是则右指针往mid-1挪
}
else{ // false,则在右边
left=mid+1;
}
}
return left;
}
};
283. Move Zeroes
思路:注意题目要求不改变非0数字的相对顺序,只能使用swap实现。
使用指针,一次遍历数组,利用指针j记录0出现的下标,碰到非0数字则往前交换。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int j=0;
for(int i=0;i<nums.size();i++){
if(nums[i]!=0)
swap(nums[j++],nums[i]);
}
}
};
289. Game of Life
该题难在要求原地操作,也就是O(1)的时间复杂度。此处我们使用位操作,用两位二进制来表示四个状态,即00、01、11、10,两个位置分别代表
下一状态 当前状态
,因为我们只需两次遍历操作,第一次遍历更改整个地图,第二次遍历使用board[i][j] = board[i][j] >> 1
来对整张图原地更新,其中board[newx][newy]&1
即为当前状态,做到了原地更新的效果又不更改状态。代码如下:
class Solution {
public:
void gameOfLife(vector<vector<int>>& board) {
int rows = board.size();
int cols = board[0].size();
for(int i = 0; i < rows; ++i){
for(int j = 0; j < cols; ++j){
int neighbors = getNeighbors(board,i,j);
// 二进制法: 下一状态 当前 ,因而 当前状态=board[i][j]&1,即为低位的值
//如果当前细胞是活的
if(board[i][j]== 1){
//如果活细胞周围邻居有两或三个为活细胞,则下一代继续存活
if(neighbors == 2 || neighbors == 3){
board[i][j] = 3; // 1 1
}
//如果是01,则在更新时,死掉
//如果小于2个或者大于3个,都是导致死亡
}
else{
//如果当前细胞是死的,其相邻有三个活的邻居。则其变成一个活细胞
if(neighbors == 3){
board[i][j] = 2; // 1 0
}
}
}
}
for(int i = 0; i < rows; ++i){
for(int j = 0; j < cols;++j){
board[i][j] = board[i][j] >> 1;
}
}
}
//统计为1的邻居个数
int getNeighbors(vector<vector<int> > &board,int x,int y){
int rows = board.size(); // 行
int cols = board[0].size(); //列
int res = 0; // 统计值
int dx[]={-1,1,0,0,-1,-1,1,1}; // 8个方向
int dy[]={0,0,-1,1,-1,1,1,-1};
for(int i = 0; i <8;++i){
int newx=x+dx[i]; //更新坐标
int newy=y+dy[i];
if(newx>=0&&newx<rows&&newy>=0&&newy<cols){
res += board[newx][newy]&1;
}
}
return res;
}
};
292. Nim Game
该题为较简单的博弈题,即若能被4整除那么怎么拿都一定输。
class Solution {
public:
bool canWinNim(int n) {
if(n<=3)
return true;
else if(n%4==0)
return false;
else
return true;
return false;
}
};
位操作
231. Power of Two
使用位操作,我们注意到
即对于二进制数来说,n若为2的i幂次,则总是第i+1位上为1,其余均为0,而对于n-1来说,第i+1位后的数均为1,该位为0,故有(n&(n-1))==0
恒成立。
class Solution {
public:
bool isPowerOfTwo(int n) {
return n>0&&((n&(n-1))==0);
}
};
389. Find the Difference
我们使用异或的方法,方法同例题 268. Missing Number ,代码如下:
class Solution {
public:
char findTheDifference(string s, string t) {
int len=s.size();
int ans=0;
for(int i=0;i<len;i++){
ans=ans^s[i]^t[i];
}
int tp=t[len];
return (char)ans^tp;
}
};
367. Valid Perfect Square
二分查找
class Solution {
public:
bool isPerfectSquare(int num) {
if(num < 1) return false;
if(num == 1) return true;
int left = 0, right = num/2;
while(left <= right)
{
long mid = (left+right)/2;
long val = mid*mid;
if(val == num) return true;
else if(val > num) right = mid-1;
else left = mid+1;
}
return false;
}
};
牛顿法:(关于牛顿法的介绍)
class Solution {
public:
bool isPerfectSquare(int num) {
long r = num;
while (r*r > num)
r = (r + num/r) / 2;
return r*r == num;
}
};