原文欢迎关注http://blackblog.tech/2018/06/03/LeetCodeReview/
欢迎关注我的个人博客 http://blackblog.tech
这是一篇笔记型Blog,主要存一下最近练的代码的笔记。LeetCode的代码,在云端,复习起来麻烦,就这样存下来。
目前的练习为LeetCode中级算法与每日模拟赛.
没事刷一刷LeetCode还是可以提高一下基本的代码能力的。
LeetCode15 三数之和
题目
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
C++代码
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> vec;
sort(nums.begin(),nums.end());
for(int k=0;k<nums.size();k++)
{
if(k>0 && nums[k]==nums[k-1])
continue;
int i,j;
for(i=k+1,j=nums.size()-1;i<j;)
{
if(i>k+1 && nums[i]==nums[i-1])
{
i++;
continue;
}
if(j<nums.size()-1 && nums[j]==nums[j+1])
{
j--;
continue;
}
int sum = nums[i]+nums[j]+nums[k];
if(sum==0)
{
vector<int> m_vec;
m_vec.push_back(nums[k]);
m_vec.push_back(nums[i]);
m_vec.push_back(nums[j]);
vec.push_back(m_vec);
j--;
i++;
}
else if(sum<0) i++;
else if(sum>0) j--;
}
}
return vec;
}
};
体会
此题直接穷举一定爆时间
采用暴力+双指针解法
首先对数据进行排序。
第一次遍历确定一个数字n,则剩下两个数字的和必须是-n,这样才满足条件。确定两个指针i、j,从左至右,从右至左依次遍历。
单个指针遍历的过程中,如果重复直接跳过,防止结果中出现重复的数字。
如果sum为0时,得到答案,存储,继续遍历。
如果sum小于0,则证明当前情况的左指针小了,左指针++。
如果sum大于0,则证明当前情况的右指针大了,右指针--。
此题尝试了二重暴力+二分,时间没问题,但是结果总出错,不知道为什么。
LeetCode73 矩阵置零
题目
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。
样例1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
样例2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
C++代码
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
bool Row[matrix.size()]={false};
bool Col[matrix[0].size()]={false};
if (matrix.size()==1 && matrix[0].size()==1) return ;
for(int i=0;i<matrix.size();i++)
{
for(int j=0;j<matrix[0].size();j++)
{
if(matrix[i][j]==0){
Row[i]=true;
Col[j]=true;
}
}
}
for(int i=0;i<matrix.size();i++)
{
for(int j=0;j<matrix[0].size();j++)
{
if(Row[i]) matrix[i][j]=0;
else if(Col[j]) matrix[i][j]=0;
}
}
return ;
}
};
体会
这里使用的算法,空间复杂度O(m + n) ,并不是最优算法。
最优算法空间复杂度为常数级别
注意几个细节就好
if (matrix.size()==1 && matrix[0].size()==1) return ;
这句话用来判断[[1]]这样的特殊情况
用于存储0位的数字这样开辟
bool Row[matrix.size()]={false};
bool Col[matrix[0].size()]={false};
不要用vec去存储0,不然难以匹配坐标,用两个数组一一对应就好。
常数级别空间复杂度的算法并不复杂
就需要巧妙地利用原来矩阵的空间,这里利用第一行和第一列保存额外信息:
1 先判断号第一行和第一列是否需要全部置零
2 有任何一个该行或者该列的元素为零那么这个第一行或者第一列的元素必然是零,就保存这个零,最后用来判断整个矩阵的这一行或者这一列是否需要置零。
3 最后再根据前面判断,决定是否把这个第一行和第一列置零。
LeetCode49 字谜分组
题目
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
C++代码
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
map<string,vector<string>> str_map;
vector<vector<string>> res;
for(auto str : strs)
{
string tmp=str;
sort(tmp.begin(),tmp.end());
str_map[tmp].push_back(str);
}
for(auto val : str_map)
{
//sort(val.second.begin(),val.second.end());
res.push_back(val.second);
}
return res;
}
};
体会
题目逻辑很清晰
自己手动实现了一个算法,但是最后一组数据没有过啊!!!难受!!!等一下附上代码。
这个题目的思路很清晰,利用Map,将每次排序过的字符串作为Key,将剩下的字符串作为Val存放进去,最后整体存放在一个二维Vector里面就可以了。
附一下差一组数据没过的代码
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>> res;
vector<string> fir_str;
fir_str.push_back(strs[0]);
res.push_back(fir_str);
for(int i=1;i<strs.size();i++)
{
string tmp = strs[i];
sort(tmp.begin(),tmp.end());
bool flag=false;
for (int j = 0; j < res.size(); j++) {
string m_tmp = res[j][0];
sort(m_tmp.begin(), m_tmp.end());
if (tmp == m_tmp) {
res[j].push_back(strs[i]);
flag = true;
break;
}
}
if(!flag)
{
vector<string> new_str;
new_str.push_back(strs[i]);
res.push_back(new_str);
}
sort(res.begin(),res.end());
}
return res;
}
};
LeetCode75 颜色排序
题目
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?
C++代码
骚操作法:
void sortColors(vector<int>& nums) {
map<int,vector<int>> m;
for(int i=0;i<nums.size();i++)
{
int k=nums[i];
m[k].push_back(k);
}
nums.erase(nums.begin(),nums.end());
for(auto val : m)
{
for(int i=0;i<val.second.size();i++)
nums.push_back(val.second[i]);
}
}
快速排序:
class Solution {
public:
void qsort(vector<int>& nums,int l,int r)
{
if(l>r) return;
int mid = nums.size()/2;
int i=l;
int j=r;
int flag = nums[l];
while(i!=j)
{
while(i<j&&nums[j]>=flag)
j--;
while(i<j&&nums[i]<=flag)
i++;
int tmp;
tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
nums[l] =nums[i];
nums[i] = flag;
qsort(nums,i+1,r);
qsort(nums,l,i-1);
}
void sortColors(vector<int>& nums) {
qsort(nums,0,nums.size()-1);
}
};
体会
用map可以直接排序有点意思
熟练一下快排
LeetCode347 Top K Frequent Elements
题目
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
例如,
给定数组 [1,1,1,2,2,3] , 和 k = 2,返回 [1,2]。
注意:
你可以假设给定的 k 总是合理的,1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
C++代码
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int,int> m;
vector<int> res;
for(int i=0;i<nums.size();i++)
{
m[nums[i]] +=1;
}
vector<pair<int,int>> vtMap;
for(auto it=m.begin();it!=m.end();it++)
vtMap.push_back(make_pair(it->first,it->second));
sort(vtMap.begin(),vtMap.end(),cmp_by_val);
for(int i=0;i<k;i++)
res.push_back(vtMap[i].first);
return res;
}
static bool cmp_by_val(pair<int,int> &a,pair<int,int> &b)
{
return a.second>b.second;
}
};
体会
一道说难不难,说简单不简单的题。
熟练使用stl很重要。
这道题利用到了map通过val排序的方法,将map转化成vector<pair<int,int>>的方法需要掌握,注意自己要写sort函数,sort函数一定是全局变量或者是static类型。
LeetCode215 数组中的第K个最大元素
题目
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
C++代码
class Solution {
public:
int partition(vector<int>&nums, int low, int high)//找枢纽
{
int first = low;
int last = high;
int key = nums[first];//用字表的第一个记录作为枢轴
while (first != last)
{
while (nums[last] >= key && first < last)
last--;
swap(nums[first], nums[last]);
while (nums[first] <= key && first < last)
first++;
swap(nums[first], nums[last]);
}
return first;//返回一个枢纽
}
int find_k(vector<int>& nums,int l,int r,int k)
{
int index=partition(nums,l,r);
int length = r-index+1;
if(length == k) //计算后面一段的长度,如果等于k表示找到了第k大
return nums[index];
else if(length > k)//如果后面的一段的长度大于k,证明第k大的数在后面一段
return find_k(nums,index+1,r,k);
else if(length <k)//如果后面的一段的长度小于k,证明第k大的数在前面一段
return find_k(nums,l,index-1,k-length);//k-length 去掉已经被划分的数字
}
int findKthLargest(vector<int>& nums, int k) {
return find_k(nums,0,nums.size()-1,k);
}
};
体会
利用快排思想查找第k大的数字,注意把patition和find_k分开写,写到一起容易出错。
时间复杂度:
该算法的平均时间复杂度为O(N)(详细的推导过程看算法导论9.2节),最坏情况为N^2,即每次划分把数组变为为(n-1) 和1的两断。
LeetCode425 电话号码的字母组合
题目
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
大概是这样的:
base[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
C++代码
class Solution {
public:
vector<string> letterCombinations(string digits) {
if(digits.length()<=0)
{
vector<string> v;
return v;
}//此处为题目要求
vector<string> res;
int index=0;
string str="";
letterCombinationsSearch(digits,str,index,res);
return res;
}
void letterCombinationsSearch(string digits,string str,int index, vector<string> &res)//注意啊,这里一定是取地址!!!
{
if(index==digits.size())
{
res.push_back(str);//搜索触底 将当前的字符串存入结果中
return;
}
string base[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
for(int i=0;i< base[digits[index]-'0'].size();i++)//自行领悟
{
str +=base[digits[index]-'0'][i];//将base数组对应位置的每个字母都放进字符串。
letterCombinationsSearch(digits,str,index+1,res);//递归,寻找下一个字母。
str.pop_back();//ad找完之后,弹出d,准备放入e
}
}
};
体会
整个问题就是一个dfs+回溯。
举个例子,比如“234”,先一口气找到adg,然后弹出g,找到adh,弹出h,找到adi,弹出i,弹出d,找到aeg,以此类推。
LeetCode46 全排列
题目
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
C++代码
class Solution {
public:
void permute_search(vector<int>&nums,vector<bool>&used,vector<int>&tmp,vector<vector<int>>&res)
{
if(tmp.size()==nums.size())
{
res.push_back(tmp);
return;
}
for(int i=0;i<nums.size();i++)
{
if(!used[i])
{
tmp.push_back(nums[i]);//存入一个没有用过的数字
used[i]=true;//用过的数字标记为true
permute_search(nums,used,tmp,res);
tmp.pop_back();//弹出刚刚用过的数字
used[i]=false;//将所对应的使用状态改为false
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
int index=-1;
vector<int> tmp;
vector<bool> used(nums.size(), false);
permute_search(nums,used,tmp,res);
return res;
}
};
体会
一道非常简单dfs+回溯题目,创建一个used数组用来存储当前每个数字的使用状态。核心部分都写了注释,这里就不赘述了。
LeetCode78 子集
题目
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
C++代码
class Solution {
public:
void subsets_search(vector<int>& nums,vector<int>&tmp,int index,vector<vector<int>> &res)
{
if(index==nums.size())
{
return ;
}
for(int i=index;i<nums.size();i++)
{
tmp.push_back(nums[i]);//将临时数组增加一个数字
res.push_back(tmp);//将临时数组放入答案数组中
subsets_search(nums,tmp,i+1,res);//递归调用,注意这里是i+1,不是index+1,因为使用index会出现1 3已被放入,index=2,递归index=3,将3也放入tmp,这样会出现1 3 3 的情况。
tmp.pop_back();//将临时数组减少一个数字,用于回溯
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> tmp;//创建一个临时数组用于增加删除数字
vector<vector<int>>res;//结果数组
res.push_back(tmp);
int index=0;
subsets_search(nums,tmp,index,res);
return res;
}
};
体会
简单回溯题,重点已经在注释中写的很清晰了。
这个题比较有意思的地方就在于每次都要把tmp都放入res中。
注意递归调用参数是i+1,不是index+1,因为使用index会出现1 3已被放入,index=2,递归index=3,将3也放入tmp,这样会出现1 3 3 的情况。
LeetCode202 快乐数
题目
编写一个算法来判断一个数是不是“快乐数”。
一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
示例:
输入: 19
输出: true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
C++代码
骚操作法:
class Solution {
public:
bool isHappy(int n) {
while(1)
{
int k=0;
while(n)
{
k+=(n%10)*(n%10);
n/=10;
}
if(k==1) return true;
if(k==4) return false;
n = k;
}
}
};
常规解法:
class Solution {
public:
bool isHappy(int n) {
vector<int> vec;
while(1)
{
int k=0;
while(n)
{
k+=(n%10)*(n%10);
n/=10;
}
if(k==1) return true;
for(int i=0;i<vec.size();i++)
{
if(k==vec[i]) return false;
}
vec.push_back(k);
n = k;
}
}
};
体会
有意思的一道题!
先说常规解法:
所有的不开心数最后都无法得到1,意味着他们一定会陷入一个有重复数字出现的循环,所以只要使用一个vector存储一下所有出现的数字,有相同的就返回false。
再来一波骚操作:
不是快乐数的数称为不快乐数(unhappy number),所有不快乐数的数位平方和计算,最後都会进入 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 的循环中。
所以只需要判断一下4就可以了。
LeetCode172 阶乘后的零
题目
给定一个整数 n,返回 n! 结果尾数中零的数量。
示例 1:
输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。
示例 2:
输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.
说明: 你算法的时间复杂度应为 O(log n) 。
C++代码
class Solution {
public:
int trailingZeroes(int n) {
int count=0;
while(n)
{
count+=n/5;
n/=5;
}
return count;
}
};
体会
技巧题!
关键在于5的数量了那么该问题的实质是要求出1~100含有多少个5由特殊推广到一般的论证过程可得:
1、 每隔5个,会产生一个0,比如 5, 10 ,15,20...
2 、每隔 5×5 个会多产生出一个0,比如 25,50,75,100
3 、每隔 5×5×5 会多出一个0,比如125.
所以100!末尾有多少个零为:100/5+100/25=20+4=24那么1000!末尾有多少个零呢?同理得: 1000/5+1000/25+1000/125=200+40+8=248
接着,请问N!的末尾有多少个零呢?
其实 也是同理的
N/5+N/25+……
如计算 2009! 的末尾有多少个0:2009/5 = 401
1~2009之间有 401 个数是 5 的倍数(余数省略).401/5 = 80
1~2009 之间有 80 个数是 25 的倍数.80/5 = 16
1~2009 之间有 16 个数是 125 的倍数. 16/5 = 3
1~2009 之间有 3个数是 625 的倍数. 3/5 = 0
1~2009 之间有 0 个数是 3125 的倍数.
所以, 2009! 的末尾有 401 + 80 + 16 + 3 = 500 个0.
LeetCode50 Pow(x, n)
题目
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1] 。
C++代码
class Solution {
public:
double myPow(double x, int n) {
if(n<0) return 1/power(x,-n);
else return power(x,n);
}
double power(double x,int n)
{
if(n==0) return 1;
double half = power(x,n/2);
if(n%2==0) return half*half;
else return x*half*half;
}
};
体会
技巧题!
这里解释一下代码
比如我们计算2^7
可以拆分为 2^3 * 2^3 * 2
2^3可以继续拆分为 2^1 * 2^1 * 2
所以2^7可以拆分了 2^3 * 2^3 * 2 分为 2^1 * 2^1 * 2 * 2^1 * 2^1 * 2 * 2
LeetCode105 中序遍历二叉树
题目
给定一个二叉树,返回它的中序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,3,2]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
C++代码
递归(不考虑安全性)
class Solution {
public:
vector<int> res;
void inorderTraversal2(TreeNode* root)
{
if(root==NULL) { return;}
inorderTraversal(root->left);//遍历左子节点
res.push_back(root->val);//存入当前节点的值 也就是中节点
inorderTraversal(root->right);//遍历右子节点
return ;
}
vector<int> inorderTraversal(TreeNode* root) {
inorderTraversal2(root);
return res;
}
};
递归(考虑安全性,不使用全局变量)
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
if(root==NULL) { vector<int> v;return v;}
vector<int> res;
vector<int> tmp_left;
tmp_left = inorderTraversal(root->left);//遍历左子节点
if(!tmp_left.empty())
{
for(int i=0;i<tmp_left.size();i++)
{
res.push_back(tmp_left[i]);//上一次递归的结果存储在tmp中,用一个循环获取所有数据。
}
}
res.push_back(root->val);//存入当前节点的值 也就是中节点
vector<int> tmp_right;
tmp_right = inorderTraversal(root->right);//遍历右子节点
if(!tmp_right.empty())
{
for(int i=0;i<tmp_right.size();i++)
{
res.push_back(tmp_right[i]);
}
}
return res;
}
};
迭代法(使用栈完成)
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> s;
TreeNode* current = root;
while(current||!s.empty())
{
if(current)
{
s.push(current);//当前节点压栈
current = current->left;//先走左边
}
else
{
res.push_back(s.top()->val);//将栈顶元素存入向量,现在curr的父节点,此时curr==null
current = s.top()->right;//修改current为当前栈顶元素的右节点,
s.pop();//弹出当前栈顶元素
}
}
return res;
}
};
体会
难度不高
中序遍历,如果不考虑安全性的话,几句话就可以写完呢。
使用栈的方法可以大大提升效率,建议使用。
LeetCode55 跳跃游戏
题目
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
C++代码
class Solution {
public:
bool canJump(vector<int>& nums) {
bool dp[nums.size()*2]={false};
dp[0]=true;
for(int i=0;i<nums.size();i++) //遍历每一个位置
{
for(int j=1;j<=nums[i];j++)//计算当前位置每种可能跳的情况
{
if(dp[i]) dp[i+j]=true;//如果当前位置能跳到,下一个位置才能
}
}
return dp[nums.size()-1];
}
};
体会
emmmmm,很迷,这个也算是DP???
不过细想,也算是划分子问题了。
很简单
二重循环,都遍历一遍,然后计算每一个可以到达的路径。重点在于if(dp[i]) dp[i+j]=true,只有当前位置能够到达,当前位置的下一个位置才能到。
LeetCode62 不同路径
题目
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
说明:m 和 n 的值均不超过 100。
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
C++ 代码
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[200][200]={0};
dp[1][1]=0;
for(int i=1;i<=m;i++)
dp[i][1]=1;
for(int i=1;i<=n;i++)
dp[1][i]=1;
for(int i=2;i<=m;i++)
{
for(int j=2;j<=n;j++)
{
dp[i][j]=std::max(dp[i][j],dp[i][j-1]+dp[i-1][j]);
}
}
return dp[m][n];
}
};
体会
基本DP题
dp[i][j]表示网格为i*j时候的方法数
状态转移方程:dp[i][j] = dp[i][j-1]+dp[i-1][j]
注意初始化的时候要将第一行第一列初始化为1,因为第一行第一列怎么走都是1。
LeetCode322 零钱兑换
题目
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
C++代码
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int min=99999999999;
int dp[100000];
//过滤一下特别大的数字
for(int i=0;i<coins.size();i++)
if(coins[i]>amount) coins[i]=0;
//初始化DP数组
for(int i=1;i<=amount;i++)
dp[i]=999999999;
//当j==coins[i]时,coins[i]所对应的价值至少有一个硬币可以构成
for(int i=0;i<coins.size();i++)
if(coins[i]!=0) dp[coins[i]]=1;
for(int i=0;i<coins.size();i++)
{
for(int j=coins[i];j<=amount;j++)
{
if(coins[i]!=0)
dp[j]=std::min(dp[j],dp[j-coins[i]]+1);
}
}
if(dp[amount] == 999999999) return -1;
else return dp[amount];
}
};
体会
完全背包+最小值+恰好装满
dp[j]表示:前i个硬币,价值为j时候的硬币数量
使用一维数组压缩空间
状态转移方程: dp[j]=min(dp[j],dp[j-coins[i]]+1)
完全背包:正序循环
最小值:min
恰好装满:初始化为inf
LeetCode300 Longest Increasing Subsequence
题目
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
C++代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size()==0) return 0;
int dp[10000];//每一个上升子序列的长度至少是1
int res=1;
for(int i=0;i<nums.size();i++)
dp[i] = 1;
for(int i=0;i<nums.size();i++) //遍历所有的长度
{
for(int j=0;j<i;j++)//模拟添加逐个数字
{
if(nums[j]<nums[i])//如果nums[j]<num[i]保证是递增序列,
{
dp[i] = max(dp[i],dp[j]+1);
}
}
res = max(res,dp[i]);
}
return res;
}
};
体会
首先区分两个题目!
LIS:最长递增子序列(子串)
LCS:最长公共子序列(子串)
最长公共子串(Longest Common Substring)与最长公共子序列(Longest Common Subsequence)的区别:
子串要求在原字符串中是连续的,而子序列则只需保持相对顺序一致,并不要求连续。
例如X = {a, Q, 1, 1}; Y = {a, 1, 1, d, f}那么,{a, 1, 1}是X和Y的最长公共子序列,但不是它们的最长公共字串。
对该题是同理
这个题是求解最长上升子序列
采用动态规划的思想
dp[i] 表示 长度为i的数列中最长递增子序列的长度!注意是长度!
我们划分为子问题
从i开始遍历长度,每次一个新长度就模拟添加数字,从第一个开始添加,如果新添加的一个数字小于当前nums[i]的大小,那么子序列的长度就会增长1。
在这之前要将dp中所有的数字初始化为1,因为递增子序列的长度至少为1。
LeetCode2 两数相加
题目
给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
C++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head = new ListNode(0);
ListNode *curr = head;
int carry=0;//用于存储进位
bool l1_finish=false;//用于判断第一个链表是否走完
bool l2_finish=false;//用于判断第二个链表是否走完
while(1)
{
int val_tmp;//临时存一下
//三种情况,同时加两个,只加一个,记得每次加carry
if(l1_finish==false && l2_finish==false)
val_tmp = l1->val + l2->val + carry;
else if(l1_finish==false && l2_finish==true)
val_tmp = l1->val + carry;
else if(l1_finish==true && l2_finish==false)
val_tmp = l2->val + carry;
carry = val_tmp/10;//存储进位
ListNode *new_node = new ListNode(val_tmp%10);//生成一个新的节点
curr->next = new_node;//append节点
curr = curr->next;//移动游标
//判断是否到头
if(l1->next==NULL)
l1_finish = true;
else l1 = l1->next;
if(l2->next==NULL)
l2_finish = true;
else l2 = l2->next;
//结束循环,注意分情况讨论有无进位情况
if(l1_finish && l2_finish && carry==0) break;
else if(l1_finish && l2_finish && carry!=0)
{
ListNode *carry_node = new ListNode(carry);
curr->next = carry_node;
curr = curr->next;
break;
}
}
return head->next;//head是空哦,返回下一个值
}
};
体会
的确是简单题,直接模拟!
但我写的好复杂啊!!!
应该是没写好,以后再改吧。
注意[5],[5]、[1,8],[0]这样的数据,有诈!
LeetCode328 奇偶链表
题目
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
C++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if(head == NULL || head->next==NULL) return head;//保护一下下 [],[1] 这两种情况
ListNode *new_head = new ListNode(0);
ListNode *curr = head;
ListNode *curr_new = new_head;
int index =0;
while(curr)
{
if(curr->next&&curr->next->next)//1,2,3,4,5这种情况
{
ListNode *new_node = new ListNode(curr->next->val);
curr_new->next = new_node;
curr_new = curr_new->next;
ListNode *tmp_delete = curr->next;//保护内存
curr->next = curr->next->next;
delete(tmp_delete);//保护内存
curr = curr->next;
if(curr->next ==NULL) break;//指针移动到5,直接break;
}
//1,2,3,4,5,6这种情况,最后的6要单独处理
else if(curr->next && !curr->next->next){
curr_new->next = curr->next;
break;
}
}
curr->next = new_head->next;
return head;
}
};
体会
遍历一遍就OK,遇到偶数存到新链表后删除,最后将两个链表连接。
重点考察链表的delete和append!
LeetCode3 无重复字符的最长子串
题目
给定一个字符串,找出不含有重复字符的最长子串的长度。
示例:
给定 "abcabcbb" ,没有重复字符的最长子串是 "abc" ,那么长度就是3。
给定 "bbbbb" ,最长的子串就是 "b" ,长度是1。
给定 "pwwkew" ,最长子串是 "wke" ,长度是3。请注意答案必须是一个子串,"pwke" 是 子序列 而不是子串。
C++代码
class Solution {
public:
int lengthOfLongestSubstring(string s) {
map<char,int> max_str;
int start_index = 0;//用于记录当前字符串开始的位置
map<char,int>::iterator it;
int max_length = 0;//记得初始化
for(int i=0;i<s.length();i++)
{
it = max_str.find(s[i]);//查找现在map中是否有重复的字符
if(it!=max_str.end()&&it->second>=start_index)//如果有重复的字符,且该字符下标比当前最长字符串起始下标大,则修改起始下标。相当于左指针右移动。
{
start_index = it->second+1;//修改下标
}
max_str[s[i]] = i;//将当前的字符及下标存入map
max_length = max(i-start_index+1,max_length);//计算最大长度
}
return max_length;
}
};
体会
感谢STL
这道题的关键需要利用map
整个思路比较清晰
首先我们从头开始遍历字符串
如果当前字符没有在map中出现过,那么就将当前字符存入map,且不修改当前子串的起始位置。
如果当前字符在map中出现过,那么证明当前子串中出现了相同的字母,此时修改子串起始位置为map中与当前字符重复的字符的下标。
举例:
bacdb
i=0 s[i]=b it='/0' start_index = 0 max=1
i=1 s[i]=a it='/0' start_index = 0 max=2
i=2 s[i]=c it='/0' start_index = 0 max=3
i=3 s[i]=d it='/0' start_index = 0 max=4
i=4 s[i]=b it='b' start_index = 1 max=4
return max