Day1 求和问题
1、两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
/*int i,j;
for(i=0;i<nums.size()-1;i++)
{
for(j=0;j<nums.size();j++)
{
if(nums[i]+nums[j]==target && i!=j)
return {i,j};
}
}
return {i,j};*/
//哈希表
unordered_map<int, int> map; //哈希表的键是数组中的值,哈希表的值是数组中的索引下标
for(int i = 0; i < nums.size(); i++){
auto iter = map.find(target - nums[i]);//迭代器访问用first second
if(iter != map.end()){
return {iter->second, i};
}
map[nums[i]] = i; //先找后存 避免nums[i]和自己匹配
}
return {};
}
};
2、两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int n = numbers.size() - 1;
int left = 0, right = n;//左指针指向第一个,右指针指向最后一个
while(left < right){
if((numbers[left] + numbers[right]) == target){
return {left + 1, right + 1};
}
else if((numbers[left] + numbers[right]) > target){
right--;
}
else {
left++;
}
}
return {-1, -1};
}
};
Day2 求和问题
1、三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
class Solution {
public:
//双指针
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());//从小到大排序
for(int i = 0; i < nums.size(); i++){
if(nums[i] > 0) return res;//
int left = i + 1;
int right = nums.size() - 1;
if(i > 0 && nums[i] == nums[i - 1]){
continue;
}
while(left < right){
if(nums[i] + nums[left] + nums[right] > 0){
right--;
}
else if(nums[i] + nums[left] + nums[right] < 0){
left++;
}
else{
res.push_back(vector<int> {nums[i], nums[left], nums[right]});
//去重
while(left < right && nums[left] == nums[left + 1]) left++;
while(left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
return res;
}
};
2、四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size(); i++){
//去重
if(i > 0 && nums[i] == nums[i - 1]){
continue;//
}
for(int j = i + 1; j < nums.size(); j++){
//去重
if(j > i + 1 && nums[j] == nums[j - 1]){
continue;//
}
int left = j + 1;
int right = nums.size() - 1;
while(left < right){
// nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
if(nums[i] + nums[j] > target - nums[left] - nums[right]){
right--;
}
else if(nums[i] + nums[j] < target - nums[left] - nums[right]){
left++;
}
else{
res.push_back(vector<int> {nums[i], nums[j], nums[left], nums[right]});
while(left < right && nums[left] == nums[left + 1]) left++;
while(left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
}
return res;
}
};
Day3 斐波那契数列
1、斐波那契数
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
class Solution {
public:
int fib(int n) {
/*if(n < 2)
{
return n;
}
int p = 0,q = 0,r = 1;
for(int i = 2;i <= n;++i)
{
p = q;
q = r;
r = p + q;
}
return r;*/
//递归
/*if(n < 2) return n;
return fib(n - 1) + fib(n - 2);*/
//动态规划
if(n < 2) return n;
vector<int> dp(n + 1);
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
2、爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
class Solution {
public:
int climbStairs(int n) {
/*int p=0,q=0,r=1;
for(int i=1;i<=n;i++)
{
p=q;
q=r;
r=p+q; //f(x)=f(x-1)+f(x-2)
}
return r;
*/
//动态规划
if(n <= 1) return n;
vector<int> dp(n + 1);
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
Day4 动态规划法
1、最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
/*int sum = nums[0];
int n = nums[0];
//判断前几项之和是否大于0,大于则加,小于舍弃重新开始记录,每次记录最大值,最后返回这个最大值
for(int i=1;i<nums.size();i++)
{
if(n>0)
{
n += nums[i];
}
else
{
n = nums[i];
}
if(sum<n)
{
sum = n;
}
}
return sum;*/
//动态规划
int n = nums.size();
vector<int> dp(n);
dp[0] = nums[0];
int res = dp[0];
for(int i = 1; i < n; i++){
if(dp[i - 1] > 0){
dp[i] = dp[i - 1] + nums[i];
}
else{
dp[i] = nums[i];
}
res = max(res, dp[i]);
}
return res;
}
};
2、分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
class Solution {
public:
//要求集合里能否出现总和为 sum / 2 的子集
bool canPartition(vector<int>& nums) {
//每个数组中的元素不会超过 100,数组的大小不会超过 200
// 总和不会大于20000,背包最大只需要其中一半
vector<int> dp(10001, 0);//dp[j]表示 背包总容量是j,最大可以凑成j的子集总和为dp[j]。
int sum = 0;
for(int m = 0; m < nums.size(); m++){
sum += nums[m];
}
if(sum % 2 == 1) return false; //sum为奇数,不可能分成两个子集
int target = sum / 2;
//遍历
for(int i = 0; i < nums.size(); i++){
for(int j = target; j >= nums[i]; j--){
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]); //dp[j]表示不放进去, dp[j - nums[i]] + nums[i]表示放进去
}
}
if(dp[target] == target) return true;//集合中的子集总和正好可以凑成总和target
return false;
}
};
3、零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
class Solution {
public:
//动态规划
//dp[i]表示兑换面额为i所需要的的最少硬币的个数
//dp[i] = min(dp[i], dp[i - coin] + 1) 上一个状态再加1个硬币coin就可以得到下个状态
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount + 1, 1e9);
dp[0] = 0;
for(int i = 1; i <= amount; i++){
for(int j = 0; j < coins.size(); j++){//枚举上一个状态
if(i - coins[j] >= 0) dp[i] = min(dp[i], dp[i - coins[j]] + 1);
}
}
return dp[amount] == 1e9 ? -1 : dp[amount];
}
};
Day5 堆栈
1、有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
class Solution {
public:
bool isValid(string s) {
/* //如果字符串s中的个数为奇数 返回false
int n = s.length();
if(n%2 == 1)
return false;
//定义字典 前面的为key 后面的为value
unordered_map<char , char> pairs={
{')','('},
{']','['},
{'}','{'}
};
stack<char> stk;
//遍历字符串s
for(char ch:s){
if(pairs.count(ch)) //如果为pairs中的')' ']' '}'
{
if(stk.empty() || stk.top() != pairs[ch]) //pairs[ch]指的是map中的第二列字符
{
return false;
}
stk.pop();
}
else //如果为pairs中的'(' '[' '{'
{
stk.push(ch);
}
}
return stk.empty(); //判断堆栈是否为空
*/
stack<int> st;
for(int i = 0; i < s.size(); i++){
if(s[i] == '(') st.push(')');
else if(s[i] == '{') st.push('}');
else if(s[i] == '[') st.push(']');
else if(st.empty() || st.top() != s[i]) return false;
else st.pop();
}
return st.empty();
}
};
2、下一个更大元素 I
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。
class Solution {
public:
//栈头到栈顶 递增的单调栈 //递减栈就是求右边第一个比自己小的元素了
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> vec(nums1.size(), -1);
stack<int> st;
if(nums1.size() == 0) return vec;
unordered_map<int, int> map; //key为值,value为索引
for(int i = 0; i < nums1.size(); i++){
map[nums1[i]] = i;
}
st.push(0);
for(int j = 1; j < nums2.size(); j++){
/*if(nums2[j] < nums2[st.top()]){
st.push(j);
}
else if(nums2[j] == nums2[st.top()]){
st.push(j);
}
else{*/
while(!st.empty() && nums2[j] > nums2[st.top()]){
if(map.count(nums2[st.top()])){ //map中是否存在此时的栈顶元素
int index = map[nums2[st.top()]]; //存在,拿到索引值
vec[index] = nums2[j];
}
st.pop();
}
st.push(j);
//}
}
return vec;
}
};
3、132 模式
给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。
如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。
class Solution {
public:
//单调栈
bool find132pattern(vector<int>& nums) {
stack<int> st;//递增栈(栈头到栈底)
int mid = -INT_MAX; //初始化中间的值为最小
for(int i = nums.size() - 1; i >= 0; i--){
if(nums[i] < mid) return true; //存在nums[i]比次大值小
while(!st.empty() && st.top() < nums[i]){ //栈顶元素小于nums[i] 栈顶元素更新为中间值 将该值弹出
mid = st.top();//mid是nums[i]后面的最大值 即mid为包括nums[i]在内的后面的次大值
st.pop();
}
st.push(nums[i]); //压进去的是包括nums[i]在内的后面的最大值
}
return false;
}
};
Day6 数字
1、杨辉三角 II
给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
class Solution {
public:
//从倒数第二个元素开始往前更新 它等于原来这个位置的数 + 前一个位置的数
//行[i] = 行[i] + 行[i-1]
vector<int> getRow(int rowIndex) {
/*vector<int> Row(rowIndex + 1);//第K行的vector大小为 rowIndex+1
for(int i=0;i<=rowIndex;i++)
{
Row[i] = 1;//行末尾为1
for(int j = i;j>1;j--)//每一行的更新过程
{
Row[j-1] += Row[j-2];
}
}
return Row;*/
vector<vector<int>> res(rowIndex + 1);
for(int i = 0; i <= rowIndex; i++){
res[i].resize(i + 1);
res[i][0] = res[i][i] = 1; //首尾为1
for(int j = 1; j < i; j++){
res[i][j] = res[i - 1][j - 1] + res[i - 1][j];
}
}
return res[rowIndex];
}
};
2、完全平方数
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
class Solution {
public:
//动态规划
//dp[11]的前一个状态可以为:dp[11 - 1*1]=dp[10],dp[11 - 2*2]=dp[7],dp[11 - 3*3]=dp[2],它们再数量上加1即可得到dp[11] (因为只减了1个j*j)
//dp[i] = min(dp[i], dp[i - j * j] + 1) dp[i]表示返回和为 i 的完全平方数的最少数量
int numSquares(int n) {
vector<int> dp(n + 1, 0);
for(int i = 0; i <= n; i++){
dp[i] = i;//初始化为最大值 最坏的情况下是全部由1相加得到 即初始化为1的数量
for(int j = 0; i - j * j >= 0; j++){ //枚举上一个状态
dp[i] = min(dp[i], dp[i - j * j] + 1); //
}
}
return dp[n];
}
};
3、最小好进制
以字符串的形式给出 n , 以字符串的形式返回 n 的最小 好进制 。
如果 n 的 k(k>=2) 进制数的所有数位全为1,则称 k(k>=2) 是 n 的一个 好进制 。
class Solution {
public:
string smallestGoodBase(string n) {
long nVal = stol(n);
int mMax = floor(log(nVal) / log(2));
for(int m = mMax; m > 1; m--){
int k = pow(nVal, 1.0 / m);
long mul = 1;
long sum = 1;
for(int i = 0; i < m; i++){
mul *= k;
sum += mul;
}
if(sum == nVal) return to_string(k);
}
return to_string(nVal - 1);
}
};
Day7 树
1、路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if(root == nullptr) return false;
if(root->left == nullptr && root->right == nullptr) return root->val == targetSum;
return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}
};
2、二叉搜索树中第K小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//中序遍历可得从小到大排列的数组
void inOrder(TreeNode* root, vector<int> &res){
if(root == nullptr) return;
inOrder(root->left, res);
res.push_back(root->val);
inOrder(root->right, res);
}
int kthSmallest(TreeNode* root, int k) {
if(root == nullptr) return 0;
vector<int> res;
inOrder(root, res);
//return res; //res为从小到大的排序数组
/*for(int i = 0; i<res.size();i++){
cout<<res[i]<<endl;
}*/
return res[k - 1];
}
};
3、监控二叉树
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//自下往上 后序遍历
//尽量让叶子节点的父节点安装摄像头,这样摄像头的数量才是最少的
//0表示该节点无覆盖;
//1表示该节点有摄像头;
//2表示该节点有覆盖
int res = 0;
//该函数返回值是 当前根节点的状态
int postOrderPro(TreeNode* root){
if(root == nullptr) return 2;//空节点也要 有覆盖
int left = postOrderPro(root->left);//左边
int right = postOrderPro(root->right);//右边
//中间的做处理
//总情况为 3 * 3 + 1 = 9 + 1 = 10
//情况一:左右节点 都有覆盖 父节点必无覆盖 1
if(left == 2 && right == 2) return 0;
//情况二:左右节点 至少有一个无覆盖 要加一个摄像头--父节点 5
if(left == 0 || right == 0){
res++;
return 1;
}
//情况三:左右节点 至少有一个有摄像头 父节点必有覆盖 3
if(left == 1 || right == 1){
return 2;
}
return -1;
}
int minCameraCover(TreeNode* root) {
//情况四:根节点无覆盖 要加一个摄像头--父节点 1
if(postOrderPro(root) == 0) res++;
return res;
}
};
Day8 字符串
1、词典中最长的单词
给出一个字符串数组 words 组成的一本英语词典。返回 words 中最长的一个单词,该单词是由 words 词典中其他单词逐步添加一个字母组成。
若其中有多个可行的答案,则返回答案中字典序最小的单词。若无答案,则返回空字符串。
class Solution {
public:
string longestWord(vector<string>& words) {
unordered_set<string> S(words.begin(), words.end());
string res;
for(auto w : words){ //遍历字符串数组中的每个字符串
bool flag = true;
for(int i = 0; i < w.size() - 1; i++){
string preword = w.substr(0, i + 1);//拿到w的前i个字符
if(!S.count(preword)){
flag = false;//如果前i个字符在哈希集合里不存在
break; //进行下一个w的判断
}
}
//w的每一个前i个字符在哈希集合中都存在
if(flag){
if(res.empty() || w.size() > res.size() || w.size() == res.size()
&& w < res){ //w的字典序小于当前存储的单词字典序
res = w; //更新res
}
}
}
return res;
}
};
2、无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char> Sc;
int n = s.size();
int right = -1, res = 0; //right为右指针 res为结果
for(int i = 0; i < n; i++){ //i为左指针
//左指针移动一位 从哈希集合中删除一个元素
if(i != 0){
Sc.erase(s[i - 1]);
}
//右指针一直移动 只要没移动到字符串尾 只要哈希集合中没有该元素
while(right + 1 < n && !Sc.count(s[right + 1])){
Sc.insert(s[right + 1]);//向哈希集合中插入右指针所指向的字符
right++;
}
res = max(res, right - i + 1);
}
return res;
}
};
3、交错字符串
给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。
两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
s = s1 + s2 + … + sn
t = t1 + t2 + … + tm
|n - m| <= 1
交错 是 s1 + t1 + s2 + t2 + s3 + t3 + … 或者 t1 + s1 + t2 + s2 + t3 + s3 + …
注意:a + b 意味着字符串 a 和 b 连接。
class Solution {
public:
//DP
bool isInterleave(string s1, string s2, string s3) {
int lengthS1 = s1.size(), lengthS2 = s2.size(), lengthS3 = s3.size();
if(lengthS1 + lengthS2 != lengthS3) return false;//长度不相等 返回false
//定义 dp[i][j] 表示 s1的前 i 个元素和 s2的前 j 个元素是否能交错组成 s3的前 i+j 个元素
vector<vector<int>> dp(lengthS1 + 1, vector<int>(lengthS2 + 1, false));
dp[0][0] = true;
for(int i = 1; i <= lengthS1; i++){
dp[i][0] = dp[i - 1][0] && s1[i - 1] == s3[i - 1];//第一列
}
for(int j = 1; j <= lengthS2; j++){
dp[0][j] = dp[0][j - 1] && s2[j - 1] == s3[j - 1];//第一行
}
for(int i = 1; i <= lengthS1; i++){
for(int j = 1; j <= lengthS2; j++){
dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i + j - 1]) || (dp[i][j - 1] && s2[j - 1] == s3[i +j - 1]);
}
}
return dp[lengthS1][lengthS2];
}
};
Day9 字符串搜索
1、实现 strStr()
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
class Solution {
public:
//获取next数组
int* getNextArray(string needle){
int* next = new int[needle.size()];
if(needle.size() == 1){
next[0] = -1;//人为规定
return next;
}
next[0] = -1;
next[1] = 0;
int cn = 0;//哪一个位置和next[i - 1]进行比较 进而得出next[i]的值 ;(cn所处的索引值等于next[i - 1]的值)
for(int i = 2; i < needle.size();){
if(needle[i - 1] == needle[cn]){
next[i] = cn + 1;
i++; cn++;
//next[i++] = ++cn;
}
else if(cn > 0){//needle[i - 1] != needle[cn] 且cn > 0 则cn跳回到next[cn]所对应的位置 继续参与比较
cn = next[cn];
}
else{//cn不能再往前跳 已到字符串头部 则最长相等前后缀长度为0
next[i] = 0;
i++;
}
}
return next;
}
int strStr(string haystack, string needle) {
/*int n = haystack.size();
int m = needle.size();
for(int i = 0; i + m <= n; i++){
bool flag = true;
for(int j = 0; j < m; j++){
if(haystack[i + j] != needle[j]){
flag = false;
break;
}
}
if(flag) return i;
}
return -1;*/
if(needle.size() == NULL) return 0;
/*if(haystack.size() == NULL || needle.size() == NULL || needle.size() < 1 || haystack.size() < needle.size()){
return -1;
}*/
if(haystack.size() < needle.size()){
return -1;
}
int *nextArray = getNextArray(needle);
int i = 0, j = 0;
while(i < haystack.size() && j < needle.size()){
if(haystack[i] == needle[j]){
i++;
j++;//每匹配成功一次 j移动一次 直到越界则说明匹配成功
}
else if(j == 0){//或者 else if(nextArray[j] == -1) //j不能再往回跳
i++;//移动haystack的指针
}
else{
j = nextArray[j];//跳回去 进行下一次比较
}
}
return j == needle.size() ? i - j : -1; //匹配成功返回 两个指针的差
}
};
2、通过删除字母匹配到字典里最长单词
给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。
class Solution {
public:
string findLongestWord(string s, vector<string>& dictionary) {
//双指针 + 排序
string res = "";
/*sort(dictionary.begin(), dictionary.end(), [](string &x, string &y){
//if(x.size() != y.size()) return x.size() < y.size();
//else return x > y;
return x.size() == y.size() ? x > y : x.size() < y.size();
});*/
for(string str : dictionary){
int i = 0, j = 0;
while(i < s.size() && j < str.size()){
if(s[i] == str[j]) j++;
i++;
}
/*if(j == str.size()) res = str;*/
if(j == str.size()){
//运算符优先级 先与后或
if(str.size() > res.size() || (str.size() == res.size() && str < res)) res = str;
}
}
return res;
}
};
3、最长快乐前缀
「快乐前缀」是在原字符串中既是 非空 前缀也是后缀(不包括原字符串自身)的字符串。
给你一个字符串 s,请你返回它的 最长快乐前缀。
如果不存在满足题意的前缀,则返回一个空字符串。
class Solution {
public:
//kmp
int* nextArray(string s){
int* next = new int[s.size()];
if(s.size() == 1){
next[0] = -1;
return next;
}
next[0] = -1;
next[1] = 0;
int cn = 0;
for(int i = 2; i < s.size();){
if(s[i - 1] == s[cn]){
next[i] = cn +1;
i++; cn++;
}
else if(cn > 0){
cn = next[cn];
}
else{
next[i] = 0;
i++;
}
}
return next;
}
string longestPrefix(string s) {
s +=' ';
int *next = nextArray(s);
//substr 起始位置和数目
return s.substr(0, next[s.size() - 1]); //应该返回的是 字符串中最后一位子串的最长相等前后缀长度即补了空格之后的next[s.size()-1]
}
};
Day10 图
1、不邻接植花
有 n 个花园,按从 1 到 n 标记。另有数组 paths ,其中 paths[i] = [xi, yi] 描述了花园 xi 到花园 yi 的双向路径。在每个花园中,你打算种下四种花之一。
另外,所有花园 最多 有 3 条路径可以进入或离开.
你需要为每个花园选择一种花,使得通过路径相连的任何两个花园中的花的种类互不相同。
以数组形式返回 任一 可行的方案作为答案 answer,其中 answer[i] 为在第 (i+1) 个花园中种植的花的种类。花的种类用 1、2、3、4 表示。保证存在答案。
class Solution {
public:
/*1、根据paths建立邻接表;
2、默认所有的花园先不染色,即染0;
3、从第一个花园开始走,把与它邻接的花园的颜色从color{1,2,3,4}这个颜色集中删除;
4、删完了所有与它相邻的颜色,就可以把集合中剩下的颜色随机选一个给它了,为了简单,将集合中的第一个颜色赋给当前花园;
5、循环3和4到最后一个花园。*/
//vector<int> v[n]:表示n个vector v ,二维数组
//相当于 vector<vector<int > > v(n);二维数组
vector<int> gardenNoAdj(int n, vector<vector<int>>& paths) {
//建立邻接表
vector<vector<int>> graph(n);
for(int i = 0; i < paths.size(); i++){
graph[paths[i][0] - 1].push_back(paths[i][1] - 1);
graph[paths[i][1] - 1].push_back(paths[i][0] - 1);
}
//cout<<graph[0][0]<<endl; cout<<graph[0][1]<<endl; // "0" : "1", "2" //1
//cout<<graph[1][0]<<endl; cout<<graph[1][1]<<endl; // "1" : "0", "2" //2
//cout<<graph[2][0]<<endl; cout<<graph[2][1]<<endl; // "2" : "1", "0" //3
//cout<<graph.size()<<endl; //行数 3
vector<int> res(n, 0); //初始化全为0 都没染色
for(int i = 0; i < n; i++){
set<int> color{1, 2, 3, 4};//
for(int j = 0; j < graph[i].size(); j++){
color.erase(res[graph[i][j]]);//删除相邻的 已染过的颜色
}
res[i] = *(color.begin()); //染色 每次取color集合中的第一个元素
}
return res;
}
};
2、K 站中转内最便宜的航班
有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = [fromi, toi, pricei] ,表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。
class Solution {
public:
//auto&&或函数参数类型的自动推导的T&& 是一个未定的引用类型,它可能是左值引用,也可能是右值引用,取决于初始化的值类型
//T&& 这是一个左值,只不过她的类型是右值引用,只能绑定右值
//用 f[t][i] 表示通过恰好 t 次航班,从出发城市 src 到达城市 i 需要的最小花费
//DP方程: f[t][i] = min(f[t][i], f[t - 1][j] + cost(j, i))
//最多只能中转 k 次,也就是最多搭乘 k+1 次航班,最终的答案即为 f[1][dst],f[2][dst],⋯,f[k+1][dst] 中的最小值。
static constexpr int inf = 101 * 10000 + 1; //constexpr仅发生在编译期
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
vector<vector<int>> f(k + 2, vector<int>(n, inf));
f[0][src] = 0;
for(int t = 1; t <= k + 1; t++){
for(auto& flight : flights){ //for(auto&& flight : flights){
int j = flight[0], i = flight[1], cost = flight[2];
f[t][i] = min(f[t][i], f[t - 1][j] + cost);
}
}
int res = inf;
for(int i = 0; i <= k + 1; i++){
res = min(res, f[i][dst]);
}
return res == inf ? -1 : res;
}
};
Day11 图
1、单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
class Solution {
public:
int dx[4] = {1, 0, -1, 0}; int dy[4] = {0, 1, 0, -1};
bool dfs(vector<vector<char>>& board, string word, int x, int y, int k){
if(board[x][y] != word[k]) return false;
if(k == word.size() - 1) return true;
char tmp = board[x][y];
board[x][y] = '.';//已经搜索过
for(int i = 0; i < 4; i++){
int mx = x + dx[i]; int my = y + dy[i];
if(mx < 0 || mx >= board.size() || my < 0 || my >= board[0].size() || board[mx][my] == '.'){
continue;
}
if (dfs(board, word, mx, my, k + 1)) return true;
}
board[x][y] = tmp;
return false;
}
bool exist(vector<vector<char>>& board, string word) {
int m = board.size();
int n = board[0].size();
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(dfs(board, word, i, j, 0)) return true;
}
}
return false;
}
};
2、矩阵中的最长递增路径
给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)。
class Solution {
public:
//DFS + DP
int dx[4] = {1, 0, -1, 0}; int dy[4] = {0, 1, 0, -1};
int dfs(vector<vector<int>>& matrix, int x, int y, vector<vector<int>>& visited){
if(visited[x][y] != 0) return visited[x][y];//
visited[x][y]++; //满足条件matrix[mx][my] > matrix[x][y] 后执行
for(int i = 0; i < 4; i++){
int mx = x + dx[i]; int my = y + dy[i];
//if(mx < 0 || mx >= matrix.size() || my < 0 || my >= matrix[0].size() || matrix[mx][my] <= matrix[x][y]) continue;
if(mx >= 0 && mx < matrix.size() && my >= 0 && my < matrix[0].size() && matrix[mx][my] > matrix[x][y]){
visited[x][y] = max(visited[x][y], dfs(matrix, mx, my, visited) + 1);
}
}
return visited[x][y];
}
int longestIncreasingPath(vector<vector<int>>& matrix) {
if(matrix.size() == 0 || matrix[0].size() == 0) return 0;
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> visited(m, vector<int>(n, 0)); //矩阵中(i, j)表示 在matrix上 格子(i, j)能走的 最长的递增路径(包括自身)
int res = 0;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
res = max(res, dfs(matrix, i, j, visited));
}
}
return res;
}
};
Day12 生活趣题
1、买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
class Solution {
public:
int maxProfit(vector<int>& prices) {
/*int inf = 1e9;
int minprice = inf, maxprofit = 0;
for(auto price : prices){
maxprofit = max(maxprofit, price - minprice);
minprice = min(minprice, price);
}
return maxprofit;*/
//DP
//dp[i][0]表示第i天持有股票所得的现金
//dp[i][1]表示第i天不持有股票所得的现金
if(prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2));
dp[0][0] = -prices[0];
dp[0][1] = 0;
for(int i = 1; i < prices.size(); i++){
dp[i][0] = max(dp[i - 1][0], -prices[i]); //第i天持有股票所得的现金:要么是昨天持有;要么是今天买入
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);//第i天不持有股票所得的现金:要么是昨天也没有;要么是今天卖出
}
return dp[prices.size() - 1][1];
}
};
2、买卖股票的最佳时机 II
给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。
在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
class Solution {
public:
int maxProfit(vector<int>& prices) {
/*//贪心算法
int maxprofit = 0;
for(int i = 1; i < prices.size(); i++){
maxprofit += max(prices[i] - prices[i - 1], 0); //只收集每天的正利润,最后稳稳的就是最大利润了
}
return maxprofit;*/
//DP
//dp[i][0]表示第i天持有股票所得的现金
//dp[i][1]表示第i天不持有股票所得的现金
if(prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2));
dp[0][0] = -prices[0];
dp[0][1] = 0;
for(int i = 1; i < prices.size(); i++){
// 注意这里是和121. 买卖股票的最佳时机唯一不同的地方 :
//因为一只股票可以买卖多次,当第i天买入股票的时候,所持有的现金可能包含有之前买卖过的利润。
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]-prices[i]); //第i天持有股票所得的现金:要么是昨天持有;要么是今天买入加上之前不持有股票时的利润
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);//第i天不持有股票所得的现金:要么是昨天也没有;要么是今天卖出
}
return dp[prices.size() - 1][1];
}
};
Day13 生活趣题
1、天际线问题
城市的 天际线 是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回 由这些建筑物形成的 天际线 。
每个建筑物的几何信息由数组 buildings 表示,其中三元组 buildings[i] = [lefti, righti, heighti] 表示:
lefti 是第 i 座建筑物左边缘的 x 坐标。
righti 是第 i 座建筑物右边缘的 x 坐标。
heighti 是第 i 座建筑物的高度。
你可以假设所有的建筑都是完美的长方形,在高度为 0 的绝对平坦的表面上。
天际线 应该表示为由 “关键点” 组成的列表,格式 [[x1,y1],[x2,y2],…] ,并按 x 坐标 进行 排序 。关键点是水平线段的左端点。列表中最后一个点是最右侧建筑物的终点,y 坐标始终为 0 ,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。
注意:输出天际线中不得有连续的相同高度的水平线。例如 […[2 3], [4 5], [7 5], [11 5], [12 7]…] 是不正确的答案;三条高度为 5 的线应该在最终输出中合并为一个:[…[2 3], [4 5], [12 7], …]
//(此题暂时放弃)
2、保持城市天际线
给你一座由 n x n 个街区组成的城市,每个街区都包含一座立方体建筑。给你一个下标从 0 开始的 n x n 整数矩阵 grid ,其中 grid[r][c] 表示坐落于 r 行 c 列的建筑物的 高度 。
城市的 天际线 是从远处观察城市时,所有建筑物形成的外部轮廓。从东、南、西、北四个主要方向观测到的 天际线 可能不同。
我们被允许为 任意数量的建筑物 的高度增加 任意增量(不同建筑物的增量可能不同) 。 高度为 0 的建筑物的高度也可以增加。然而,增加的建筑物高度 不能影响 从任何主要方向观察城市得到的 天际线 。
在 不改变 从任何主要方向观测到的城市 天际线 的前提下,返回建筑物可以增加的 最大高度增量总和 。
class Solution {
public:
//先创建两个数组 记录每一行和每一列的最大值
//该建筑物增加后的最大高度是 min(rowMax[i],colMax[j])。由于该建筑物的原始高度是 grid[i][j],
//因此该建筑物高度可以增加的最大值是 min(rowMax[i],colMax[j])−grid[i][j]。
int maxIncreaseKeepingSkyline(vector<vector<int>>& grid) {
int n = grid.size();
vector<int> rowsMax(n, 0);
vector<int> colsMax(n, 0);
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
rowsMax[i] = max(rowsMax[i], grid[i][j]); //每一行的最大值
colsMax[j] = max(colsMax[j], grid[i][j]); //每一列的最大值
}
}
int res = 0;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
res += min(rowsMax[i], colsMax[j]) - grid[i][j];
}
}
return res;
}
};
Day14 生活趣题
1、盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
class Solution {
public:
int maxArea(vector<int>& height) {
/*会超时
int res = 0;
vector<int> dp(height.size());
dp[0] = 0;
for(int i = 1; i < height.size(); i++){
for(int j = 0; j < i; j++){
dp[i] = max(dp[i], min(height[i], height[j]) * (i - j));
}
}
sort(dp.begin(), dp.end());
return dp[height.size() - 1];*/
//双指针
int res = 0;
int left = 0, right = height.size() - 1;
while(left < right){
res = max(res, min(height[left], height[right]) * (right - left));
if(height[left] < height[right]) left++;
else right--;
}
return res;
}
};
2、接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
class Solution {
public:
//以列为单位求和
int trap(vector<int>& height) {
/*//双指针 会超时
int res = 0;
for(int i = 0; i < height.size(); i++){
//第一个柱子和最后一个柱子不接雨水
if(i == 0 || i == height.size() - 1) continue;
int leftHeight = height[i]; //某一列左边柱子的最高值
int rightHeight = height[i]; //某一列右边柱子的最高值
for(int l = i - 1; l >= 0; l--){//for(int l = 0; l < i; l++){
if(leftHeight < height[l]) leftHeight = height[l];
}
for(int r = i + 1; r < height.size(); r++){
if(rightHeight < height[r]) rightHeight = height[r];
}
//计算该列的雨水高度 :该列 左边柱子的最高值 和 右边柱子的最高值 的较小值 减去 该列的柱子高度
int h = min(leftHeight, rightHeight) - height[i];
//对h求和
if(h > 0) res += h;
}
return res;*/
//优化:动态规划
//把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight)
//当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。
//即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
//从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
if(height.size() <= 2) return 0;
vector<int> leftHeight(height.size(), 0);
vector<int> rightHeight(height.size(), 0);
//记录每个柱子左边柱子最大高度(从左往右遍历)
leftHeight[0] = height[0];
for(int i = 1; i < height.size(); i++){
leftHeight[i] = max(leftHeight[i - 1], height[i]);
}
//记录每个柱子右边柱子最大高度(从右往左遍历)
rightHeight[rightHeight.size() - 1] = height[height.size() - 1];
for(int j = rightHeight.size() - 2; j >= 0; j--){
rightHeight[j] = max(rightHeight[j + 1], height[j]);
}
//求和
int res = 0;
for(int s = 0; s < height.size(); s++){
//计算该列的雨水高度 :该列 左边柱子的最高值 和 右边柱子的最高值 的较小值 减去 该列的柱子高度
int h = min(leftHeight[s], rightHeight[s]) - height[s];
if(h > 0) res += h;
}
return res;
}
};