T1 两数之和
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
平生不识TwoSum,刷尽LeetCode也枉然
暴力搜索,复杂度 O(n^2)
想用线性时间复杂度,需要只遍历一个数字,另一个数字事先用HashMap存起来,建立数字和坐标的映射关系。
HashMap是常数级的查找效率。
我们在遍历数组的时候,用target减去遍历到的数字,就是另一个需要的数字了,直接在HashMap中查找其是否存在即可
注意要判断查找到的数字不是第一个数字,比如target是4,遍历到了一个2,那么另外一个2不能是之前那个2。
整个实现步骤为:先遍历一遍数组,建立HashMap映射,然后再遍历一遍,开始查找,找到则记录index。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> m; // 无序哈希表
vector<int> res;
for(int i=0; i<nums.size(); i++) // 先将数组的值、索引存成键值对的形式
m[nums[i]] = i;
for(int i=0; i<nums.size(); i++)
{
int temp = target - nums[i]; //第二个数
if(m.count(temp) && m[temp]!=i) // 如果哈希表内存在第二个数,且索引和第一个数的索引不一样
{
res.push_back(i);
res.push_back(m[temp]);
break;
}
}
return res;
}
};
T15 三数之和
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[ [-1, 0, 1] [-1, -1, 2] ]
(1)加和为0,先固定一个数,再找另外两个,三个有正有负
(2)排序,升序,用双指针
(3)遍历到 倒数第三个停止,
(4)剪枝处理,第一个是正数的,break
(5)重复就跳过,不想相同的数字固定两次
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int> > res;
sort(nums.begin(),nums.end());
for(int k=0; k<nums.size(); k++)
{
if(nums[k]>0) break;
if(k>0 && nums[k]==nums[k-1]) continue;
int target = 0-nums[k];
int i=k+1, j=nums.size()-1;
while(i<j)
{
if(nums[i]+nums[j]==target)
{
res.push_back( {nums[k],nums[i],nums[j]} );
while(i<j && nums[i]==nums[i+1]) ++i;
while(i<j && nums[j]==nums[j-1]) --j;
++i;
--j;
}
else if(nums[i]+nums[j]<target) ++i;
else if(nums[i]+nums[j]>target) --j;
}
}
return res;
}
};
T26 删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
给定 nums = [0,0,1,1,1,2,2,3,3,4],函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.empty()) return 0;
int k=0;
for(int i=0; i<nums.size(); i++) //双指针i,k ,只遍历一次
{
if(nums[i] != nums[k]) //两个指针数字相同时,快指针i向前走一步
nums[++k] = nums[i]; //两个指针数字不同时,都走一步
}
return k+1;
}
};
T39 组合之和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 :
输入: candidates = [2,3,5], target = 8,
所求解集为:
[ [2,2,2,2], [2,3,3], [3,5] ]
返回所有符合要求解的题 十有八九都是要利用到递归
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int> > res;
vector<int> vec;
sort(candidates.begin(), candidates.end());
combinationSumDFS(candidates, target, 0, vec, res);
return res;
}
// {2,3,5} 8 ; [2,2,2,2],[2,3,3],[3,5]
void combinationSumDFS(vector<int>& candidates, int target, int start,
vector<int>& vec, vector<vector<int> >& res)
{
if(target<0) return;
else if(target==0) res.push_back(vec);
else
{
for(int i=start; i<candidates.size(); i++)
{
vec.push_back(candidates[i]);
combinationSumDFS(candidates, target-candidates[i], i, vec, res);
vec.pop_back();
}
}
}
};
T62 不同路径
一个机器人位于一个 m x n 网格的左上角
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
说明:m 和 n 的值均不超过 100。
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
跟跳台阶一样,需要动态规划DP来解
我们可以维护一个二维数组dp,其中dp[i][j]表示到当前位置不同的走法的个数,然后可以得到递推式为: dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
这里为了节省空间,我们使用一维数组dp,一行一行的刷新也可以,代码如下:
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> dp(n,1);
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
dp[j] = dp[j]+dp[j-1];
}
return dp[n-1];
}
};
T64 最小路径和
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
这应该算是DP问题中比较简单的一类,我们维护一个二维的dp数组,其中dp[i][j]表示当前位置的最小路径和,
递推式也容易写出来 dp[i][j] = grid[i][j] + min(dp[i - 1][j],dp[i][j-1])
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
int dp[m][n];
dp[0][0] = grid[0][0];
for(int i=1;i<m;i++)
dp[i][0] = grid[i][0]+dp[i-1][0];
for(int j=1;j<n;j++)
dp[0][j] = grid[0][j]+dp[0][j-1];
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
dp[i][j] = grid[i][j] + min(dp[i-1][j],dp[i][j-1]);
}
return dp[m-1][n-1];
}
};
T78 子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
最开始我在想的时候,是想按照子集的长度由少到多全部写出来,比如子集长度为0的就是空集,空集是任何集合的子集,满足条件,直接加入。下面长度为1的子集,直接一个循环加入所有数字,子集长度为2的话可以用两个循环,但是这种想法到后面就行不通了,因为循环的个数不能无限的增长,所以我们必须换一种思路。
我们可以一位一位的网上叠加,比如对于题目中给的例子[1,2,3]来说,最开始是空集,那么我们现在要处理1,就在空集上加1,为[1],现在我们有两个自己[]和[1],下面我们来处理2,我们在之前的子集基础上,每个都加个2,可以分别得到[2],[1, 2],那么现在所有的子集合为[], [1], [2], [1, 2],同理处理3的情况可得[3], [1, 3], [2, 3], [1, 2, 3], 再加上之前的子集就是所有的子集合了,代码如下:
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int> > res(1);
sort(nums.begin(), nums.end());
for(int i=0; i<nums.size(); i++)
{
int size = res.size();
for(int j=0; j<size; j++)
{
res.push_back(res[j]); //将res内已有的加在后面
res.back().push_back(nums[i]); //再在后面添加新元素
}
}
return res;
}
};
整个添加的顺序为:
[]
[1]
[2]
[1 2]
[3]
[1 3]
[2 3]
[1 2 3]
T105 从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
—- / \
—- 15 7
由于先序的顺序的第一个肯定是根,所以原二叉树的根节点可以知道,题目中给了一个很关键的条件就是树中没有相同元素,有了这个条件我们就可以在中序遍历中也定位出根节点的位置,并以根节点的位置将中序遍历拆分为左右两个部分,分别对其递归调用原函数。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return build(preorder,0,preorder.size()-1,
inorder, 0,inorder.size()-1);
}
TreeNode* build(vector<int>& preorder, int preStart, int preEnd,
vector<int>& inorder, int inStart, int inEnd)
{
if(preStart>preEnd || inStart>inEnd) return NULL;
int i=0;
for(i=inStart; i<=inEnd; i++)
if(preorder[preStart] == inorder[i]) break; // 在中序遍历中,找到根节点位置i,break
// 中序遍历,i左侧是根节点的左子树,i右侧是根节点的右子树
TreeNode* cur = new TreeNode(preorder[preStart]); // 根节点是前序遍历的,第一个元素
cur->left = build(preorder, preStart+1, preStart+i-inStart, inorder, inStart, i-1); // i-inStart是根节点左子树的节点个数
cur->right = build(preorder, preStart+i-inStart+1, preEnd, inorder, i+1, inEnd);
return cur;
}
};
T106 从中序与后序遍历序列构造二叉树
由于后序最后一个肯定是根,所以原二叉树的根节点可以知道
在中序遍历中也定位出根节点的位置,并以根节点的位置将中序遍历拆分为左右两个部分,分别对其递归调用原函数
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
return build(inorder,0,inorder.size()-1, postorder,0,postorder.size()-1);
}
TreeNode* build(vector<int>& inorder, int inStart, int inEnd,
vector<int>& postorder, int postStart, int postEnd)
{
if(inStart>inEnd || postStart>postEnd) return NULL;
int i=0;
for(i=inStart; i<inEnd; i++)
if(inorder[i]==postorder[postEnd]) break;
TreeNode* cur = new TreeNode(postorder[postEnd]);
cur->left = build(inorder,inStart,i-1, postorder,postStart, postStart+i-inStart-1);
cur->right = build(inorder,i+1,inEnd, postorder,postStart+i-inStart,postEnd-1);
return cur;
}
};
T189 旋转数组
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
说明:
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
要求使用空间复杂度为 O(1) 的原地算法。
方案1:复制一个和nums一样的数组,然后利用映射关系i -> (i+k)%n来交换数字
class Solution {
public:
void rotate(vector<int>& nums, int k) {
vector<int> t = nums;
for(int i=0;i<nums.size();i++)
nums[(i+k)%nums.size()] = t[i];
}
};
方案二
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if (nums.empty() || (k %= nums.size()) == 0) return;
int n = nums.size();
for (int i = 0; i < n - k; ++i) {
nums.push_back(nums[0]);
nums.erase(nums.begin());
}
}
};
T628 三个数的最大乘积
给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积。
示例 1:
输入: [1,2,3]
输出: 6
示例 2:
输入: [1,2,3,4]
输出: 24
注意:
给定的整型数组长度范围是[3,10^4],数组中所有的元素范围是[-1000, 1000]。
输入的数组中任意三个数的乘积不会超出32位有符号整数的范围
class Solution {
public:
int maximumProduct(vector<int>& nums) {
int n=nums.size();
sort(nums.begin(),nums.end());
int p=nums[0]*nums[1]*nums[n-1];
return max(p, nums[n-1]*nums[n-2]*nums[n-3]);
}
};
T485 最大连续1的个数
给定一个二进制数组, 计算其中最大连续1的个数。
示例 1:
输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
int res=0,cnt=0;
for(int num:nums) // 遍历数组nums,值给num
{
cnt = (num==0) ? 0 : cnt+1 ;
res = max(res,cnt);
}
return res;
}
};
T581 最短无序连续子数组
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
示例 1:
输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
说明 :
输入的数组长度范围在 [1, 10,000]。
输入的数组可能包含重复元素 ,所以升序的意思是<=。
用了一个辅助数组,我们新建一个跟原数组一摸一样的数组,然后排序。从数组起始位置开始,两个数组相互比较,当对应位置数字不同的时候停止,同理再从末尾开始,对应位置上比较,也是遇到不同的数字时停止,这样中间一段就是最短无序连续子数组了
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
int n=nums.size();
int i=0;
int j=n-1;
vector<int> res=nums;
sort(res.begin(),res.end());
while(i<n && nums[i]==res[i]) ++i;
while(j>i && nums[j]==res[j]) --j;
return j-i+1;
}
};