文章目录

  • 参考
  • 一、经典手撕 - 分治
    • 1.1 、原地堆排
    • 1.2 、快排
    • 1.3、归并(逆序对)、插入 排序
    • 1.4、并查集、 前缀树
    • 1.5、string类实现
    • 1.6、单例模式 - 懒汉、饿汉
    • 1.6、多线程- 主线程、子线程交替循环;
    • 1.7、死锁演示
    • 1.8、手撕信号量、读写锁、生产者消费者模型
    • 1.8、shared、unique ptr
    • 1.8、小rand 来实现大rand(随机数)、在圆内随机生成点
    • 1.9、手撕线程池
  • 二、字符串
    • 1.1、正则匹配、验证IP地址
    • 1.2、字符串转整数、 翻转字符串里的单词
    • 1.3 、字符串解码、字符串相乘、比较版本号
    • 1.4 、括号匹配问题- 括号生成、有效的括号字符串
  • 三、链表
    • 1.1 、LRU 缓存机制 、LFU 缓存
    • 1.2 、反转整个、部分链表;k个一组反转、两两交换链表;
    • 1.3 、相交链表、环形链表
    • 1.4 、删除倒数第 N 个结点、排序奇升偶降链表、重排链表、合并K个升序链表
    • 1.5 、链表删除连续重复I / II、
    • 1.6 、链表归并排序、冒泡排序、插入排序;
    • 1.7、二叉树与链表转换 - 二叉树展开为单链表、双链表;有序链表转成二叉树;深拷贝带随机指针的链表;
    • 1.8、链表随机节点、删除链表连续和为0的连续节点、旋转链表、指定位置合并链表
  • 四、二叉树
    • 1.1、二叉树的染色法迭代遍历、普通迭代;二叉树右视图、锯齿形层序遍历;
    • 1.2、树的属性 - 对称二叉树、平衡二叉树、 二叉树的直径、二叉树最大宽度、二叉树的最小深度、二叉树的镜像
    • 1.3、 二叉树的最近公共祖先、判断是否是完全二叉树
    • 1.4、 寻找重复的子树 、另一棵树的子树 、树的子结构
    • 1.3、二叉树的所有路径 、最大路径和 、路径总和
    • 1.5、二叉树的改造 - 序列化二叉树
    • 1.6、二叉树的构造- 给定两个序列来构造二叉树
    • 1.7、二叉搜索树的构造-
    • 1.8、二叉搜索树 - 恢复二叉搜索树、最大 BST 子树
    • 1.9、二叉树 -
  • 五、图
    • 1.1 、拓扑排序:最小高度树、课程表 II
  • 六、查找、二分法、
    • 1.1 、二分 - lower、uper bound
    • 1.2 、二分 - 搜索旋转排序数组 I/II、旋转排序数组中的最小值 I/II、
    • 1.3、几数之和
    • 1.4、寻找两个正序数组的中位数、有序数组中的缺失元素、长度最小的子数组
    • 1.5 、爱吃香蕉的珂珂、按权重随机选择
  • 六、滑动窗口
    • 1.1 、最小覆盖子串、无重复字符的最长子串
    • 1.2 、滑动窗口最大值(单调栈)
  • 七、位运算、算数
    • 1.1 、只出现一次的数字 II / III
    • 1.2 、数值次方(快速幂)、两数相除(整数除法)
  • 八、贪心
    • 1.1 、分发糖果、矩形重叠
  • 九、栈、队列、优先队列
    • 1.1 、栈实现队列队列实现栈、最小栈、
    • 1.2 、基本计算器 II
    • 1.3 、有效的括号 、第K个最大元素
    • 1.4 、【单调栈】接雨水、每日温度、柱状图中最大的矩形
  • 七、数组
    • 1.1 、下一个排列、螺旋矩阵
    • 1.2、缺失的第一个正数、合并区间
  • 十、递归和回溯
    • 1、组合、分割 、子集
      • 1.1 、组合 - 组合、组合求和
      • 1.2 、组合 - 二进制手表、号码的字母组合
      • 1.3 、分割 - 分割回文串、复原 IP
      • 1.4 、子集 - 子集、递增子序列;- 组合问题同层去重
    • 2、排列 - 排列问题 - 同层去重
      • 1.1 、全排列
      • 1.2 、安排行程 // 欧拉图;
    • 3、flood fill ;填充;
      • 1.1、岛屿数量、岛屿的最大面积
      • 1.2、被围绕的区域、太平洋大西洋水流问题
    • 4、搜索
      • 1.1、单词搜索
      • 1.2、八皇后、解数独;
  • 十一、动态规划
    • 1、基础DP
      • 1.1、爬楼梯及其深化、不同路径 I 、 II、圆环走回原点问题、约瑟夫环
      • 1.2、剪绳子、不同的二叉搜索树
      • 1.3、打家劫舍 I / II / III
      • 1.4、三角形最小路径和、摆动序列 、解码方法
      • 1.5、最大正方形、最长有效括号、分割数组的最大值
    • 2、背包问题
      • 1.1、0 - 1 背包 - 分割等和子集、最后一块石头的重量、目标和、一和零;
      • 1.2、完全背包
        • 1.2.1、组合问题 - 零钱兑换 II ; 排列问题 - 组合总和Ⅳ
        • 1.2.2、排列问题/组合问题 - 零钱兑换、完全平方数、单词拆分
      • 1.3、多重背包
    • 3、股票买卖问题
      • 1.1、买卖股票时机 I / II / III / IV
      • 1.2、买卖股票时机带冷冻期、手续费
    • 5、子序列问题
      • 1.1、子序列(不连续)- 最长递增子序列、套娃问题;最长公共子序列;
      • 1.2、子数组(连续)- 最长连续递增序列、最长重复子数组、 最大子序和;
      • 1.3、编辑距离 - 判断子序列、不同的子序列、两个字符串的删除操作、编辑距离
      • 1.4、回文子串 - 回文子串、最长回文子串、最长回文子序列、最小插入构成回文串

参考

【学习笔记】【C++】【Leetcode 分门别类讲解】

https://github.com/youngyangyang04/leetcode-master

【C++】【学习笔记】【动态规划问题详解与例题】记忆化搜索与暴力穷举思想 ;0-1 背包问题;子序列问题;

  • 经典问题总结 【代码随想录】

一、经典手撕 - 分治

1.1 、原地堆排
void __shiftDown(vector<int> &arr, int _r, int index) {int temp = arr[index];while (2 * index + 1 < _r) {int j = 2 * index + 1;if (j + 1 < _r && arr[j + 1] > arr[j])j += 1;if (temp >= arr[j]) break;arr[index] = arr[j];index = j;}arr[index] = temp;
}
void heapSort(vector<int>& arr, int n) {// 从(最后一个元素的索引-1)/2开始 //第一个非叶子节点开始for (int i = (n - 1 - 1) / 2; i >= 0; i--)__shiftDown(arr, n, i);for (int i = n - 1; i > 0; i--) {swap(arr[0], arr[i]);__shiftDown(arr, i, 0);}
}
  • 维护堆插入的时候,插到尾部,然后再shift up,一直和父节点比较、交换,一直到合适的位置。
  void shiftUp(vector<int> &arr, int _r,int k){int e = data[k];while( k > 1 && arr[k/2] < e ){//swap( data[k/2], data[k] );arr[k] = arr[k/2];k /= 2;}arr[k] = e;}void insert(int item) {data[count + 1] = item;shiftUp(count + 1);count++;}
1.2 、快排
  • 记得srand(time(0));
// 双路快速排序的partition
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
int _partition2(vector<int>& arr, int _l, int _r) {// 随机在arr[_l..._r]的范围中, 选择一个数值作为标定点pivotswap(arr[_l], arr[rand() % (_r - _l + 1) + _l]);int v = arr[_l];// arr[_l+1...i) <= v; arr(j..._r] >= vint i = _l + 1, j = _r;while (true) {// 注意这里的边界, arr[i] < v, 不能是arr[i] <= v 相等也换,让等于的两边均分while (i <= _r && arr[i] < v)i++;// 注意这里的边界, arr[j] > v, 不能是arr[j] >= v 相等也换,让等于的两边均分while (j >= _l + 1 && arr[j] > v)j--;if (i > j) break;swap(arr[i], arr[j]);i++;j--;}swap(arr[_l], arr[j]);return j;
}
// 对arr[_l..._r]部分进行快速排序
void _quickSort(vector<int>& arr, int _l, int _r) {if (_l >= _r) return;// 调用双路快速排序的partitionint p = _partition2(arr, _l, _r);_quickSort(arr, _l, p - 1);_quickSort(arr, p + 1, _r);
}
void __quickSort3Ways(vector<int>& arr, int l, int r)
{if (l > r) return;swap(arr[l], arr[rand() % (r - l + 1) + l]);int tar = arr[l];int lt = l;     // arr[l+1...lt] < vint gt = r + 1; // arr[gt...r] > vint i = l + 1;    // arr[lt+1...i) == vwhile (i < gt) {if (arr[i] < tar) {swap(arr[i], arr[lt + 1]);i++;lt++;}else if (arr[i] > tar) {swap(arr[i], arr[gt - 1]);gt--;}else { // arr[i] == vi++;}}swap(arr[l], arr[lt]);__quickSort3Ways(arr, l, lt - 1);__quickSort3Ways(arr, gt, r);
}
1.3、归并(逆序对)、插入 排序
void __merge2(vector<int>& arr, vector<int>& aux, int l, int mid, int r) {for (int i = l; i <= r; i++)aux[i] = arr[i];// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1int i = l, j = mid + 1;for (int k = l; k <= r; k++) {if (i > mid)arr[k] = aux[j++];else if (j > r)arr[k] = aux[i++]; else if (aux[i] < aux[j])arr[k] = aux[i++]; else  // 左半部分所指元素 >= 右半部分所指元素arr[k] = aux[j++]; }
}void __mergeSort2(vector<int>& arr, vector<int>& aux, int _l, int _r) {if (_l >= _r) return;int mid = (_l + _r) / 2;__mergeSort2(arr, aux, _l, mid);__mergeSort2(arr, aux, mid + 1, _r);cout << _l << " : " << _r << endl;if (arr[mid] > arr[mid + 1])__merge2(arr, aux, _l, mid, _r);
}
  • 插入
for( int i = 1 ; i < n ; i ++ )
{ int e = arr[i];//模板函数 模板为T int j; // j保存元素e应该插入的位置 for (j = i; j > 0 && arr[j-1] > e; j--) arr[j] = arr[j-1]; arr[j] = e;
}

剑指 Offer 51. 数组中的逆序对

class Solution {public:int result = 0;void sort_merge(vector<int>& nums,int left,int right,vector<int>&nums_temp){if(left>=right ) return ;int mid = left+(right-left)/2;sort_merge(nums,left,mid,nums_temp);sort_merge(nums,mid+1,right,nums_temp);if(nums[mid+1]<nums[mid]){for(int i = left;i<=right;i++)nums_temp[i]  = nums[i];int i=left,j = mid+1,k = left;while(k<=right){if(i>mid)nums[k]  = nums_temp[j++];else if(j>right)nums[k]  = nums_temp[i++];else{if(nums_temp[i]<= nums_temp[j]){nums[k]  = nums_temp[i];++i;}else{   nums[k]  = nums_temp[j];++j;result +=mid-i+1;}}++k;}}}int reversePairs(vector<int>& nums) {vector<int> nums_temp(nums.size());sort_merge(nums,0,nums.size()-1,nums_temp);return result;}
};
1.4、并查集、 前缀树
int find(int p,vector<int> &parent) {// 不断去查询自己的父亲节点, 直到到达根节点// 根节点的特点: parent[p] == p//while (p != parent[p])//    p = parent[p];//return p;if (p != parent[p])parent[p] = find(parent[p], parent);return parent[p];
}void unionElements(int p, int q, vector<int> &parent) {int pRoot = find(p, parent);int qRoot = find(q, parent);if (pRoot == qRoot)return;parent[pRoot] = qRoot;
}int main() {vector<int> nums = { 0,1,2,3,4,5,6 };unionElements(4, 3, nums);unionElements(3, 1, nums);unionElements(6, 3, nums);cout << find(1, nums) << " " << find(6, nums)<< endl;for (auto _it : nums)cout << _it << " ";
}

208. 实现 Trie (前缀树)


class Trie {struct _node {char _key;bool _end;vector<_node*> _next;_node(char k) :_key(k), _end(false) , _next(vector<_node*>(26,nullptr)){}};_node* root;
public:/** Initialize your data structure here. */Trie() {root = new _node(0);}/** Inserts a word into the trie. */void insert(string word) {_node* cur = root;int index = 0;while (index < word.size()){int _in = word[index] - 'a';if (!cur->_next[_in])cur->_next[_in] = new _node(word[index]);cur = cur->_next[_in];index++;}cur->_end = true;}/** Returns if the word is in the trie. */bool search(string word) {int index = 0;_node* cur = root;while (index < word.size()){int _in = word[index] - 'a';if (!cur->_next[_in])return false;cur = cur->_next[_in];index++;}return cur->_end == true? true: false;}/** Returns if there is any word in the trie that starts with the given prefix. */bool startsWith(string prefix) {int index = 0;_node* cur = root;while (index < prefix.size()){int _in = prefix[index] - 'a';if (!cur->_next[_in])return false;cur = cur->_next[_in];index++;}return true;}
};
1.5、string类实现
class String
{public:String(const char *str = NULL);                              // 普通构造函数String(const String &other);                                  // 拷贝构造函数String(String&& other);                                         // 移动构造函数~String(void);                                                         // 析构函数String& operator= (const String& other);             // 赋值函数String& operator=(String&& rhs)noexcept;         // 移动赋值运算符friend ostream& operator<<(ostream& os, const String &c); // cout输出private:char *m_data; // 用于保存字符串
};
// String 的普通构造函数
String::String(const char *str)
{if (str == NULL){m_data = new char[1];if (m_data != NULL)*m_data = '\0';elseexit(-1);}else{int len = strlen(str);m_data = new char[len + 1];if (m_data != NULL)strcpy(m_data, str);elseexit(-1);}
}// 拷贝构造函数
String::String(const String &other)
{int len = strlen(other.m_data);m_data = new char[len + 1];if (m_data != NULL)strcpy(m_data, other.m_data);elseexit(-1);
}// 移动构造函数
String::String(String&& other)
{if (other.m_data != NULL){m_data = other.m_data;// 资源让渡other.m_data = NULL;}
}// 赋值函数
String& String::operator= (const String &other)
{if (this == &other){return *this;}// 释放原有的内容delete[] m_data;// 重新分配资源并赋值int len = strlen(other.m_data);m_data = new char[len + 1];if (m_data != NULL)strcpy(m_data, other.m_data);elseexit(-1);return *this;
}// 移动赋值运算符
String& String::operator=(String&& rhs)noexcept
{if (this != &rhs){delete[] m_data;m_data = rhs.m_data;rhs.m_data = NULL;}return *this;
}// String 的析构函数
String::~String(void)
{if (m_data != NULL){delete[] m_data;}
}ostream& operator<<(ostream& os, const String &c)
{os << c.m_data;return os;
}
1.6、单例模式 - 懒汉、饿汉
  • 懒汉式: 指系统运行中,实例并不存在,只有当需要使用该实例时,才会去创 建并使
    用实例。(这种方式要考虑线程安全)
  • 饿汉式: 指系统一运行,就初始化创 建实例,当需要时,直接调用即可。(本身就线
    程安全,没有多线程的问题)
  • 加锁的懒汉式单例(线程安全)

unique_lock 局部变量,退出自动解锁;

class SingleInstance
{public:// 获取单实例对象static SingleInstance *&GetInstance();//释放单实例,进程退出时调用static void deleteInstance();// 打印实例地址void Print();private:// 将其构造和析构成为私有的, 禁止外部构造和析构SingleInstance();~SingleInstance();// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值SingleInstance(const SingleInstance &signal);const SingleInstance &operator=(const SingleInstance &signal);private:// 唯一单实例对象指针static SingleInstance *m_SingleInstance;static std::mutex m_Mutex;
};//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;
std::mutex SingleInstance::m_Mutex;SingleInstance *&SingleInstance::GetInstance()
{//  这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,//  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。if (m_SingleInstance == NULL) {std::unique_lock<std::mutex> lock(m_Mutex); // 加锁if (m_SingleInstance == NULL){m_SingleInstance = new (std::nothrow) SingleInstance;}}return m_SingleInstance;
}void SingleInstance::deleteInstance()
{std::unique_lock<std::mutex> lock(m_Mutex); // 加锁if (m_SingleInstance){delete m_SingleInstance;m_SingleInstance = NULL;}
}
  • 内部静态变量的懒汉式单例(C++11线程安全)(推荐)

在C++11内部静态变量的方式里是线程安全的,只创建了一次实例

class Single
{public:// 获取单实例对象static Single &GetInstance();// 打印实例地址void Print();private:// 禁止外部构造Single();// 禁止外部析构~Single();// 禁止外部复制构造Single(const Single &signal);// 禁止外部赋值操作const Single &operator=(const Single &signal);
};Single &Single::GetInstance()
{// 局部静态特性的方式实现单实例static Single signal;return signal;
}
  • 饿汉式单例(本身就线程安全)(析构的时候还需要加锁)
class Singleton
{public:// 获取单实例static Singleton* GetInstance();// 释放单实例,进程退出时调用static void deleteInstance();// 打印实例地址void Print();private:// 将其构造和析构成为私有的, 禁止外部构造和析构Singleton();~Singleton();// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值Singleton(const Singleton &signal);const Singleton &operator=(const Singleton &signal);private:// 唯一单实例对象指针static Singleton *g_pSingleton;static std::mutex m_Mutex;
};// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;
std::mutex Singleton ::m_Mutex;Singleton* Singleton::GetInstance()
{return g_pSingleton;
}void Singleton::deleteInstance()
{std::unique_lock<std::mutex> lock(m_Mutex); // 加锁if (g_pSingleton){delete g_pSingleton;g_pSingleton = NULL;}
}
1.6、多线程- 主线程、子线程交替循环;
  • 子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到主线程又循环 100 次;如此循环50次;
int flag = 10;
mutex mu;
condition_variable cv;
void fun(int x, char c)
{for (int i = 0; i < 50; i++){unique_lock<mutex> lock(mu);if (flag != x)cv.wait(lock);//在该线程阻塞之前会执行lock.unlock(),这样别的线程才不会被锁住for (int j = 0; j < x; j++)cout << c << ":" << j << endl;flag = (x == 10) ? 100 : 10;cv.notify_one();}
}
int main()
{thread t1(fun, 10, 'A');fun(100, 'B');t1.join();
}
  • 编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。
int flag = 0;
mutex mu;
condition_variable cv;
void fun(int x)
{for (int i = 0; i < 10; i++){unique_lock<mutex>lock(mu);while (x != flag)cv.wait(lock);cout << static_cast<char>('A' + x) << " ";flag = (flag + 1) % 3;cv.notify_all();}
}
int main()
{thread t1(fun, 1);thread t2(fun, 2);thread t3(fun, 0);//fun(0);t1.join();t2.join();t3.join();
}
  • AB 线程循环打印

std::mutex data_mutex;
std::condition_variable data_var;
bool flag = true;void printA()
{while(1){std::this_thread::sleep_for(std::chrono::seconds(1));std::unique_lock<std::mutex> lck(data_mutex) ;data_var.wait(lck,[]{return flag;});std::cout<<"thread: "<< std::this_thread::get_id() << "   printf: " << "A" <<std::endl;flag = false;data_var.notify_one();}
}void printB()
{while(1){std::unique_lock<std::mutex> lck(data_mutex) ;data_var.wait(lck,[]{return !flag;});std::cout<<"thread: "<< std::this_thread::get_id() << "   printf: " << "B" <<std::endl;flag = true;data_var.notify_one();}
}int main()
{std::thread tA(printA);std::thread tB(printB);tA.join();tB.join();return 0;
}
1.7、死锁演示

通过代码的形式进行演示,需要两个线程和两个互斥量。

//定义两把锁
mutex m_mutex1;
mutex m_mutex2;
int A = 0, B = 0;
//线程1
void* threadFunc1()
{printf("thread 1 running..\n");m_mutex1.lock();A = 1;printf("thread 1 write source A\n");Sleep(100);m_mutex2.lock();B = 1;printf("thread 1 write source B\n");//解锁,实际上是跑不到这里的,因为前面已经死锁了m_mutex2.unlock();m_mutex1.unlock();return NULL;
}
//线程2
void* threadFunc2()
{printf("thread 2 running..\n");m_mutex2.lock();B = 1;printf("thread 2 write source B\n");Sleep(100);m_mutex1.lock();A = 1;printf("thread 2 write source A\n");m_mutex1.unlock();m_mutex2.unlock();return NULL;
}int main()
{thread outMsg(threadFunc1);thread inMsg(threadFunc2);inMsg.join();outMsg.join();return 0;
}

语句1和语句2表示线程A先锁资源1,再锁资源2,语句3和语句4表示线程B先锁资源2再锁资源1,具备死锁产生的条件。

1.8、手撕信号量、读写锁、生产者消费者模型
  • 手撕信号量(主要注意的是P、V操作的原子性)
#include<mutex>
#include<condition_variable>
class semaphore {public:semaphore(long count = 0) :count(count) {}void wait() {std::unique_lock<std::mutex>lock(mx);cond.wait(lock, [&]() {return count > 0; });--count;}void signal() {std::unique_lock<std::mutex>lock(mx);++count;cond.notify_one();}private:std::mutex mx;std::condition_variable cond;long count;
};
  • 读写锁;读优先
  • 写优先就是反过来;
class readwrite_lock
{public:readwrite_lock(): read_cnt(0){}void readLock(){read_mtx.lock();if (++read_cnt == 1)write_mtx.lock();read_mtx.unlock();}void readUnlock(){read_mtx.lock();if (--read_cnt == 0)write_mtx.unlock();read_mtx.unlock();}void writeLock(){write_mtx.lock();}void writeUnlock(){write_mtx.unlock();}private:mutex read_mtx;mutex write_mtx;int read_cnt; // 已加读锁个数
};
  • 通过控制变量,来实现对读优先、写优先的控制
class ReadWriteLock {private:int readWaiting = 0;  //等待读int writeWaiting = 0; //等待写int reading = 0; //正在读int writing = 0;  //正在写std::mutex mx;std::condition_variable cond;bool preferWriter;  //偏向读
public:ReadWriteLock(bool isPreferWriter = false) :preferWriter(isPreferWriter) {}void readLock() {std::unique_lock<std::mutex>lock(mx);++readWaiting;cond.wait(lock, [&]() {return writing <= 0 && (!preferWriter || writeWaiting <= 0); });++reading;--readWaiting;}void writeLock() {std::unique_lock<std::mutex>lock(mx);++writeWaiting;cond.wait(lock, [&]() {return reading <= 0 && writing <= 0; });++writing;--writeWaiting;}void readUnLock() {std::unique_lock<std::mutex>lock(mx);--reading;//当前没有读者时,唤醒一个写者if (reading <= 0)cond.notify_one();}void writeUnLock() {std::unique_lock<std::mutex>lock(mx);--writing;//唤醒所有读者、写者cond.notify_all();}

生产者消费者模型

  • full信号量用于 生产者V 消费者P ;生产者生产一个后V一次;消费者消费前P一次,如果小于0就不消费了,表示没有东西可以消费初始化为0表示刚开始没有;
  • empty信号量用于 生产者P 消费者V ;生产者生产之前P一次,如果小于0了就不生产了;消费者消费一个后V一次,表示消耗了一个;初始化为BUFFER SIZE表示最多能生产多少个,表示的是缓冲区大小;
  • mutex表示互斥信号量;用于进入临界区的互斥性;初始为1 为互斥信号量;
 #include<process.h>#include<windows.h>using namespace std;HANDLE empty,full; //同步信号量 缓冲池的剩余 缓冲池中的产品个数 生产者与消费者同步HANDLE mutex;//互斥信号量,生产者与生产者互斥,消费者与消费者互斥int buf_max;       //缓冲池大小int product=0;     //产品数量
//生产者线程
unsigned __stdcall threadProducer(void *)
{for(int i = 0; i < 10; i++){WaitForSingleObject(empty, INFINITE);//等待同步信号量emptyWaitForSingleObject(mutex, INFINITE);//等待互斥信号量mutexproduct++;cout<<"生产者生产了一个产品!当前产品数量:"<<product<<endl<<endl;Sleep(100);ReleaseSemaphore(mutex, 1, NULL);//释放互斥信号量mutexReleaseSemaphore(full, 1, NULL);//释放同步信号量full}return 1;
}
//消费者线程
unsigned __stdcall threadConsumer(void *)
{for(int i = 0; i < 10; i++){WaitForSingleObject(full, INFINITE);//等待同步信号量fullWaitForSingleObject(mutex, INFINITE);//等待互斥信号量mutexproduct--;cout<<"消费者消费了一个产品!当前产品数量:"<<product<<endl<<endl;Sleep(100);ReleaseSemaphore(mutex, 1, NULL);//释放互斥信号量mutexReleaseSemaphore(empty, 1, NULL);//释放信号量}return 2;
}int main()
{bool flag=false;while(!flag){cout<<"请输入缓冲池大小(大于0):"<<endl;cin>>buf_max;if(buf_max<=0);else flag=true;}//创建信号量empty = CreateSemaphore(NULL, buf_max, buf_max, NULL);//初值为缓冲池大小,最大为缓冲池大小full = CreateSemaphore(NULL, 0, buf_max, NULL);       //初值为0,最大为缓冲池大小mutex = CreateSemaphore(NULL,1,1,NULL);               //初值为1,最大为1HANDLE hth1, hth2;                                    //线程句柄//创建线程hth1 = (HANDLE)_beginthreadex(NULL, 0, threadProducer, NULL, 0, NULL);//生产者线程hth2 = (HANDLE)_beginthreadex(NULL, 0, threadConsumer, NULL, 0, NULL);//消费者线程//等待子线程结束WaitForSingleObject(hth1, INFINITE);WaitForSingleObject(hth2, INFINITE);CloseHandle(hth1);CloseHandle(hth2);CloseHandle(empty);CloseHandle(full);CloseHandle(mutex);
}
1.8、shared、unique ptr

unique_ptr

  • 防拷贝,既然多个智能指针指向同一资源会导致问题,那就干脆不让你这样做。
  • 在需要拷贝的场景下他没有办法使用。
  1. 禁止拷贝构造、复制构造
  2. 可以实现移动语义
template<typename T>
class My_unique_ptr
{private:T* _ptr;
public:My_unique_ptr( T* ptr) : _ptr(ptr) {}My_unique_ptr(const My_unique_ptr<T>&) = delete;My_unique_ptr& operator=(const My_unique_ptr<T>&) = delete;~My_unique_ptr(){if (_ptr){delete _ptr;_ptr = nullptr;}}My_unique_ptr(My_unique_ptr &&p) : _ptr(p._ptr) {p._ptr = nullptr;}My_unique_ptr& operator=( My_unique_ptr&& p){_ptr = p._ptr;p._ptr = nullptr;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
};

shared_ptr

  • 对于计数操作要进行加锁,所以导致其效率相对来说会低一些,并且还存在循环引用的问题
  1. 引用计数实现
  2. 独特地析构函数
  3. 允许拷贝构造
  4. 允许复制构造(注意:需要先释放掉原来所指向地对象)
template<typename T>
class mysharedptr {private:T* _ptr;int* _pcount;std::mutex* _pmtx;//加锁保证线程安全void add_ref_count() {_pmtx->lock();++(*_pcount);_pmtx->unlock();}void release() {bool flag = false;_pmtx->lock();if (--(*_pcount) == 0) {if (_ptr) {delete _ptr;_ptr = nullptr;}delete _pcount;_pcount = nullptr;flag = true;}_pmtx->unlock();if (flag) {delete _pmtx;_pmtx = nullptr;}}public:mysharedptr(T* ptr) : _ptr(ptr), _pcount(new int(1)), _pmtx(new std::mutex) {}mysharedptr(mysharedptr<T>& sp) : _ptr(sp._ptr), _pcount(sp._pcount), _pmtx(sp._pmtx){add_ref_count();}mysharedptr<T>& operator=(mysharedptr<T>& sp) {if (this != &sp ) {release();        //自己原来所指向的对象的引用计数减1_ptr = sp._ptr;_pcount = sp._pcount;_pmtx = sp._pmtx;add_ref_count();}return *this;}~mysharedptr() {release();}T& operator*() {return *_ptr;}T* operator->() {return _ptr;}T* get() const {return _ptr;}size_t use_count() const {return *_pcount;}
};
1.8、小rand 来实现大rand(随机数)、在圆内随机生成点
  • 大的生成小的,只需要大于的时候一直while 等待即可;
  • 小的生成大的,需要先等概率覆盖大于的数,然后再while到等倍数;

rand 7 生成rand 5

int rand5()
{int res = 7;while(res>5)res = rand7();return res;
}
  • 用到1 ~ 21,如果直接大于7就扔掉,太多会被扔掉。尽量少的被扔掉,就直接整倍数,然后来映射

rand5 生成 rand7

int rand7()
{int res = 7;while(res>21)res = rand5()+(rand5()-1)*5;return res&7+1;
}

470. 用 Rand7() 实现 Rand10() //两次rand 7 产生1~ 49,只要1~ 40;映射到1~10中;

class Solution {public:int rand10() {int res;do{res = rand7()+(rand7()-1)*7;}while(res>40);return 1+(res-1)%10;}
};

478. 在圆内随机生成点

class Solution {double r, x, y; //圆的半径,x,y坐标
public:Solution(double radius, double x_center, double y_center) {r = radius, x = x_center, y = y_center; srand(time(0));}vector<double> randPoint() {while(true){   //初始值为正方形的左下角 + 随机值double randx = x - r + (double(rand()) / RAND_MAX * r) * 2;double randy = y - r + (double(rand()) / RAND_MAX * r) * 2;double dis = sqrt((randx - x) * (randx - x) + (randy - y) * (randy - y));if(dis <= r)    return {randx, randy};}}
};
1.9、手撕线程池
class threadpool
{using Task = function<void()>;    //定义类型//typedef void(*Task)();vector<thread> _pool;     //线程池queue<Task> _tasks;            //任务队列mutex _lock;                   //同步condition_variable _task_cv;   //条件阻塞atomic<bool> _run{ true };     //线程池是否执行atomic<int>  _idlThrNum{ 0 };  //空闲线程数量public:inline threadpool(unsigned short size = 4) { addThread(size); }inline ~threadpool(){_run = false;_task_cv.notify_all(); // 唤醒所有线程执行for (thread& thread : _pool) {//thread.detach(); // 让线程“自生自灭”if (thread.joinable())thread.join(); // 等待任务结束, 前提:线程一定会执行完}}// 有两种方法可以实现调用类成员,// 一种是使用   bind: .commit(std::bind(&Dog::sayHello, &dog));// 一种是用   mem_fn: .commit(std::mem_fn(&Dog::sayHello), this)template<class F>auto commit(F&& f){{lock_guard<mutex> lock{ _lock };//对当前块的语句加锁  lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock()_tasks.push([&f]() {(*f)();});}_task_cv.notify_one(); // 唤醒一个线程执行}int idlCount() { return _idlThrNum; }int thrCount() { return _pool.size(); }void addThread(unsigned short size){for (; _pool.size() < THREADPOOL_MAX_NUM && size > 0; --size){   //增加线程数量,但不超过 预定义数量 THREADPOOL_MAX_NUM_pool.emplace_back([this] { //工作线程函数while (_run){Task task; // 获取一个待执行的 task{unique_lock<mutex> lock{ _lock };_task_cv.wait(lock, [this] {return !_run || !_tasks.empty();});if (!_run && _tasks.empty())return;task = move(_tasks.front()); // 按先进先出从队列取一个 task_tasks.pop();}_idlThrNum--;task();//执行任务_idlThrNum++;}});_idlThrNum++;}}
};
void fun1()
{while (1){cout << "void fun1() " << endl;Sleep(5000);}
}int main(int argc, char *argv[])
{{threadpool tp(4);cout << tp.thrCount() << endl;cout << tp.idlCount() << endl;tp.commit(fun1);tp.commit(fun1);}return 0;
}

二、字符串

  1. 字符串相加
1.1、正则匹配、验证IP地址

10. 正则表达式匹配 同 剑指 Offer 19. 正则表达式匹配

  • 很多情况隐式包含在其中了,要注意
class Solution {public:bool isMatch(string s, string p) {vector<vector<bool>> dp(s.size()+1,vector<bool>(p.size()+1,false));dp[0][0] = true;for(int j = 2; j<=p.size();j += 2)// p偶数次为*才能匹配,即,让p始终为空dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';for(int i=1;i<=s.size();i++){for(int j=1;j<=p.size();j++){if(p[j-1] == '*'){//这个*和前面的字不用 // 多次用这个* if(j>1&&dp[i][j-2]) dp[i][j] = true;else if(j>1&&dp[i-1][j]&&(s[i-1] == p[j-2]||p[j-2] =='.')) dp[i][j] = true;}else{//之前都匹配上了,各加一个能匹配上就行;if(dp[i-1][j-1]&&(s[i-1] == p[j-1]||p[j-1] =='.')) dp[i][j] = true;}}}return dp.back().back();}
};

468. 验证IP地址

  • 面对测试历程编程
class Solution {public:bool cal4(vector<string> &arr, int tar){if (tar == 255)for (auto &it : arr){if (it.size() != 1 && it[0] == '0')return false;if (it.size() > 3) return false;for (auto itt : it)if (itt > '9' || itt < '0')return false;int temp = stoi(it);if (temp < 0 || temp>255)return false;}elsefor (auto &it : arr){if (it.size() > 4) return false;for (auto itt : it)if ((itt > '9'&&itt < '0') || (itt > 'F'&&itt <= 'Z') || (itt > 'f'&&itt <= 'z'))return false;}return true;}string validIPAddress(string IP) {vector<string> arr;bool _ip4 = true, wrong = false;for (int i = 0, j = 0; i <= IP.size(); i++)if (i == IP.size() || IP[i] == '.' || IP[i] == ':'){if (_ip4&&IP[i] == ':') _ip4 = false;arr.push_back(IP.substr(j, i - j));if (i - j < 1) wrong = true;j = i + 1;}if (wrong||(_ip4&&arr.size() != 4) || (!_ip4&&arr.size() != 8)) return "Neither";if (_ip4&&cal4(arr, 255)) return "IPv4";else if (!_ip4&&cal4(arr, 1e9)) return "IPv6";return "Neither";}
};
1.2、字符串转整数、 翻转字符串里的单词

8. 字符串转换整数 (atoi)

  • 用了 long
class Solution {public:int myAtoi(string s) {long res =0;int _l = 0,_r = 0;bool neg = false;while(_r<s.size()&&s[_r] ==' ') _r++;if(_r<s.size()&&(s[_r] =='-'||s[_r] =='+')) {neg = s[_r] =='-';_r++;}_l = _r;while(_r<s.size() &&s[_r]<='9'&&s[_r]>='0') _r++;while(_l<_r){res = res*10+s[_l++]-'0';if(!neg&&res>=INT_MAX||neg&&res>INT_MAX){if(neg) res = INT_MIN;else  res = INT_MAX;break;}}return neg? -res:res;}
};
  • 只用 int
class Solution {public:int myAtoi(string s) {int res =0,_l = 0,_r = 0;bool neg = false;while(_r<s.size()&&s[_r] ==' ') _r++;if(_r<s.size()&&(s[_r] =='-'||s[_r] =='+')) {neg = s[_r] =='-';_r++;}_l = _r;while(_r<s.size() &&s[_r]<='9'&&s[_r]>='0') _r++;while(_l<_r){int temp = ((neg? INT_MIN:INT_MIN+1)+s[_l]-'0')/10;if(temp>res)return neg? INT_MIN:INT_MAX;res = res*10 - (s[_l++]-'0');}return neg? res:-res;}
};

151. 翻转字符串里的单词

  • 原地反转
class Solution {public:string reverseWords(string s) {reverse(s.begin(),s.end());int _l =0,_r =0,_index = 0;while(_r<s.size()){if(s[_r]!=' '){if(_index !=0) s[_index++] =' ';_l = _r;while(_r<s.size()&&s[_r] !=' ') s[_index++] = s[_r++];reverse(s.begin()+_index-(_r-_l),s.begin()+_index);}_r++;}s.erase(s.begin()+_index,s.end());return s;}
};

1.3 、字符串解码、字符串相乘、比较版本号

394. 字符串解码

class Solution {public:int index;string dfs(string& s){string res;while (index < s.size()){int temp_index = index;while (index < s.size() && s[index] >= '0'&&s[index] <= '9') index++;int len_d = 1;if (index > temp_index)len_d = stoi(s.substr(temp_index, index - temp_index));string temp_str;if (s[index] == '['){index++;temp_str = dfs(s);index++;}while(len_d--)res += temp_str;while (index < s.size() && s[index] >= 'a'&&s[index] <= 'z')res += s[index++];// cout << index << " "<< res << endl;if (s[index] == ']') break;}return res;}string decodeString(string s) {index = 0;return dfs(s);}
};

43. 字符串相乘

class Solution {public:string res;void merge(string &num1){int _l  =res.size()-1,_r = num1.size()-1;int pre = 0;string cur;while(_l>=0||_r>=0||pre!=0){int temp =0;if(_l>=0)temp +=res[_l--]-'0';if(_r>=0)temp +=num1[_r--]-'0';if(pre!=0)temp +=pre;pre = temp/10;cur+= temp%10 + '0';}res = string(cur.rbegin(),cur.rend());}string multiply(string num1, string num2) {int len1 =num1.size(),len2 =num2.size();for(int i=len1-1;i>=0;i--){int temp1 = num1[i]-'0';string cur;int pre =0;for(int k=0;k<len1-1-i;k++) cur+='0';for(int j=len2-1;j>=0;j--){int temp2 = num2[j]-'0';temp2 *=temp1;temp2+= pre;cur += temp2%10+'0';pre  = temp2/10;}while(pre!=0){cur += pre%10+'0';pre /=10;}while(cur.size()>1&&cur.back() =='0')cur.pop_back();reverse(cur.begin(),cur.end());merge(cur);}return res;}
};

165. 比较版本号

class Solution {public:void cal(string &str,queue<int> &_v){int i=0,j=0;while(j<=str.size()){if(str[j] =='.'||j ==str.size()){while(str[i] =='0') i++;// cout<<str.substr(i,j-i)<<endl;if(i!=j) _v.push(stoi(str.substr(i,j-i)));else   _v.push(0);i = j+1;}j++;}}int compareVersion(string version1, string version2) {queue<int> v1,v2;cal(version1,v1);cal(version2,v2);int i=0;while(!v1.empty()||!v2.empty()){if(v1.empty()){while(!v2.empty()){if(v2.front() !=0)return -1;v2.pop();}}else if(v2.empty()){while(!v1.empty()){if(v1.front() !=0)return 1;v1.pop();}}else {if(v1.front()<v2.front()) return -1;if(v1.front()>v2.front()) return 1;v1.pop();v2.pop();}}return 0;}
};
1.4 、括号匹配问题- 括号生成、有效的括号字符串

22. 括号生成

  • dfs,左括号还能生成就生成;右括号只有左括号更多的情况下才能生成
class Solution {public:vector<string> res;string temp;void dfs(int _l,int _r){if(_l==0&&_r ==0){res.push_back(temp);return;}if(_l>0){temp.push_back('(');dfs(_l-1,_r);temp.pop_back();}if(_l<_r){temp.push_back(')');dfs(_l,_r-1);temp.pop_back();}}vector<string> generateParenthesis(int n) {dfs(n,n);return res;}
};

678. 有效的括号字符串

  • _l、_r表示「可能多余的左括号」,一个下界,一个上界,很直观。执行起来就是

    • 遇到左括号:_l++, _r++
    • 遇到星号:_l–, _r++(因为星号有三种情况)
    • 遇到右括号:_l–, _r–
  • _l要保持不小于0。(要理解_l的意思,考虑下这个例子 (**)(
  • 如果_r < 0,说明把星号全变成左括号也不够了,False
  • 最後,如果_l > 0,说明末尾有多余的左括号,False
class Solution {public:bool checkValidString(string s) {int _l = 0;int _r = 0;for (int i = 0; i < s.size(); i++){auto & it = s[i];if (it == '*') {if(_l>0)_l--;_r++;}else if (it == '(')  {_l++;_r++;}else{if(_l>0)_l--;_r--;if(_r<0) return false;}}cout << _l << " " << _r << endl;if (_l>0) return false;return true;}
};

20. 有效的括号

  • 用栈匹配即可

32. 最长有效括号

  • dp[i] 表示以下标 i 字符结尾的最长有效括号的长度

三、链表

1.1 、LRU 缓存机制 、LFU 缓存

146. LRU 缓存机制

struct dlnode {int key;int value;dlnode* pre;dlnode* next;dlnode() :key(0), value(0), pre(nullptr), next(nullptr) {}dlnode(int k, int v) :key(k), value(v), pre(nullptr), next(nullptr) {}dlnode(int k, int v, dlnode*_p, dlnode* _n) :key(k), value(v), pre(_p), next(_n) {}
};class LRUCache {dlnode* _head;dlnode* _tail;int _size;int _cap;unordered_map<int, dlnode*>_hash;
public:LRUCache(int capacity) :_cap(capacity), _size(0) {_head = new dlnode();_tail = new dlnode();_head->next = _tail;_tail->pre = _head;}int get(int key) {if (_hash.find(key) != _hash.end()){dlnode* temp = _hash[key];temp->pre->next = temp->next; // remove temptemp->next->pre = temp->pre;temp->pre = _head;      // add temp to headtemp->next = _head->next;_head->next->pre = temp;// head link temp_head->next = temp;return temp->value;}elsereturn -1;}void put(int key, int value) {if (_hash.find(key) == _hash.end()){if (_size < _cap){dlnode* temp = new dlnode(key, value, _head, _head->next);// add temp to head_size++;_hash[key] = temp;_head->next->pre = temp; // head link temp_head->next = temp;}else{dlnode* temp = _tail->pre;_hash.erase(_tail->pre->key); //recode new temp;_hash[key] = temp;temp->key = key;temp->value = value;_tail->pre = temp->pre;     //remove temp from tailtemp->pre->next = temp->next;temp->pre = _head;          // add temp to headtemp->next = _head->next;_head->next->pre = temp;    // head link temp_head->next = temp;}}else{dlnode* temp = _hash[key]; //recode new temp;temp->value = value;temp->pre->next = temp->next;    //remove temptemp->next->pre = temp->pre;temp->pre = _head;              // add temp to headtemp->next = _head->next;_head->next->pre = temp;        // head link temp_head->next = temp;}}
};

460. LFU 缓存

struct Node {int cnt, time, key, value;Node(int _cnt, int _time, int _key, int _value):cnt(_cnt), time(_time), key(_key), value(_value){}bool operator < (const Node& rhs) const {return cnt == rhs.cnt ? time < rhs.time : cnt < rhs.cnt;}
};
class LFUCache {// 缓存容量,时间戳int capacity, time;unordered_map<int, Node> key_table;set<Node> S;
public:LFUCache(int _capacity) {capacity = _capacity;time = 0;key_table.clear();S.clear();}int get(int key) {if (capacity == 0) return -1;auto it = key_table.find(key);// 如果哈希表中没有键 key,返回 -1if (it == key_table.end()) return -1;// 从哈希表中得到旧的缓存Node cache = it -> second;// 从平衡二叉树中删除旧的缓存S.erase(cache);// 将旧缓存更新cache.cnt += 1;cache.time = ++time;// 将新缓存重新放入哈希表和平衡二叉树中S.insert(cache);it -> second = cache;return cache.value;}void put(int key, int value) {if (capacity == 0) return;auto it = key_table.find(key);if (it == key_table.end()) {// 如果到达缓存容量上限if (key_table.size() == capacity) {// 从哈希表和平衡二叉树中删除最近最少使用的缓存key_table.erase(S.begin() -> key);S.erase(S.begin());}// 创建新的缓存Node cache = Node(1, ++time, key, value);// 将新缓存放入哈希表和平衡二叉树中key_table.insert(make_pair(key, cache));S.insert(cache);}else {// 这里和 get() 函数类似Node cache = it -> second;S.erase(cache);cache.cnt += 1;cache.time = ++time;cache.value = value;S.insert(cache);it -> second = cache;}}
};
1.2 、反转整个、部分链表;k个一组反转、两两交换链表;

206. 反转链表

  • 递归
class Solution {public:ListNode* reverse(ListNode* _pre,ListNode* _cur) {if(!_cur) return _pre;ListNode* temp = reverse(_cur,_cur->next);_cur->next = _pre;return temp;}ListNode* reverseList(ListNode* head) {return reverse(nullptr,head);}
};
  • 迭代
class Solution {public:ListNode* reverseList(ListNode* head) {ListNode* _pre = nullptr;ListNode* cur = head;while(cur){ListNode* _next = cur->next;cur->next = _pre;_pre = cur;cur = _next;}return _pre;}
};

92. 反转链表 II 部分反转

class Solution {public:ListNode* reverseBetween(ListNode* head, int left, int right) {ListNode* dummy_head = new ListNode(-1,head);ListNode* pre  = dummy_head,*_pre = nullptr,*_next = nullptr;for(int i=1;i<left;i++)pre = pre->next;ListNode*  cur = pre->next;right = right-left;_pre = pre;for(int i=0;i<=right;i++){_next = cur->next;cur->next = _pre;_pre = cur;cur = _next;}pre->next->next = cur;pre->next = _pre;return dummy_head->next;}
};

25. K 个一组翻转链表

class Solution {public:ListNode* reverseKGroup(ListNode* head, int k) {ListNode* dummy_head = new ListNode(-1,head);ListNode* pre = dummy_head; ListNode* cur = head;while(1){int i=0;for(ListNode* temp = cur;i<k&&temp;i++)temp = temp->next;if(i<k) break;ListNode* _pre = pre;for(int i=0;i<k;i++){ListNode* _next = cur->next;cur->next = _pre;_pre = cur;cur = _next;}ListNode*  temp_pre = pre->next;pre->next->next = cur;pre->next = _pre;pre = temp_pre;}return dummy_head->next;}
};

24. 两两交换链表中的节点

class Solution {public:ListNode* swapPairs(ListNode* head) {if(!head||!head->next) return head;ListNode* _new = head->next;head->next = swapPairs(_new->next);_new->next = head;return _new;}
};
1.3 、相交链表、环形链表

  • 两个题都是指针走过等长的路径作为难点

160. 相交链表

class Solution {public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode * cur_A = headA;ListNode * cur_B = headB;while(1){if(cur_A == cur_B)return cur_A;if(!cur_A) cur_A = headB;else cur_A = cur_A->next;if(!cur_B) cur_B = headA;else cur_B = cur_B->next; }}
};

142. 环形链表 II

  • 包括了判断是否有环、计算环长度、找到环入口;
class Solution {public:ListNode *detectCycle(ListNode *head) {ListNode *_f = head;ListNode *_s = head;while(1) // has a cycle{if(!_f||!_f->next||!_s) return nullptr;_f = _f->next->next;_s = _s->next;if(_f ==_s) break;}int len_q = 0;do // cal cycle len{len_q++;_s = _s->next;}while(_f!=_s);_s = head;for (int i = 0; i < len_q; i++)_s = _s->next;_f = head;while(_f !=_s) //find cycle entrance{_f = _f->next;_s = _s->next;}return _s;}
};
1.4 、删除倒数第 N 个结点、排序奇升偶降链表、重排链表、合并K个升序链表

19. 删除链表的倒数第 N 个结点

class Solution {public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* dummy_head = new ListNode(-1,head);ListNode* cur =dummy_head,* temp =dummy_head;for(int i=0;;i<n&&cur;i++)cur = cur->next;while(cur->next){temp = temp->next;cur = cur->next;}temp->next = temp->next->next;return dummy_head->next;}
};

排序奇升偶降链表

  • 按奇偶位置拆分链表、反转偶链表、合并两个有序链表、
class Solution {public:ListNode* reverse(ListNode* _pre,ListNode* _cur) {if(!_cur) return _pre;ListNode* temp = reverse(_cur,_cur->next);_cur->next = _pre;return temp;}void reorderList(ListNode* head) {ListNode* _dm_odd = new ListNode();ListNode* _dm_even = new ListNode();ListNode* _cur_odd = _dm_odd,*_cur_even = _dm_even;ListNode* _f = head;bool odd = false;while(_f){if(!odd){_cur_odd->next = _f;_cur_odd = _cur_odd->next;}else{_cur_even->next = _f;_cur_even = _cur_even->next;}_f = _f->next;odd = !odd;}_cur_odd->next = nullptr;_cur_even->next = nullptr;_dm_even->next = reverse(nullptr,_dm_even->next);ListNode* _dm_now = new ListNode();_f = _dm_now;_cur_odd = _dm_odd->next;_cur_even = _dm_even->next;while(_cur_odd||_cur_even){if(!_cur_odd){_f->next = _cur_even;break;}else if(!_cur_even){_f->next = _cur_odd;break;}else{if(_cur_even->val<_cur_odd->val){_f->next = _cur_even;_f = _f->next;_cur_even = _cur_even->next;}else{_f->next = _cur_odd;_f = _f->next;_cur_odd = _cur_odd->next;}}}for(_f = _dm_now->next;_f;_f = _f->next)cout<<_f->val<<" ->";cout<<endl;}
};

143. 重排链表

  • 找中点、翻转右半段、合并
class Solution {public:ListNode* reverse(ListNode* _pre,ListNode* _cur) {if(!_cur) return _pre;ListNode* temp = reverse(_cur,_cur->next);_cur->next = _pre;return temp;}void reorderList(ListNode* head) {ListNode* _f = head;ListNode* _s = head;while(_f&&_f->next){_f = _f->next->next;_s = _s->next;}ListNode* _temp_head = _s->next;_s->next = nullptr;_temp_head  = reverse(nullptr,_temp_head);_s = head;while(_temp_head){ListNode* _n_s = _s->next;ListNode* _n_t = _temp_head->next;_s->next = _temp_head;_temp_head->next = _n_s;_s = _n_s;_temp_head = _n_t;}}
};

23. 合并K个升序链表

  • 每次取最小的排、大小为k 的优先队列、两两合并
class Solution {public:ListNode* mergeKLists(vector<ListNode*>& lists) {ListNode* dummy_head = new ListNode();ListNode* cur = dummy_head;while(1){bool has = false;int temp = INT_MAX;for(int i=0;i<lists.size();i++){if(lists[i]){has = true;temp = min(temp,lists[i]->val);}}if(!has) break;for(int i=0;i<lists.size();i++){if(lists[i]&&lists[i]->val == temp){cur->next = lists[i];cur = cur->next;lists[i] = lists[i]->next;}}}return dummy_head->next;}
};
1.5 、链表删除连续重复I / II、

83. 删除排序链表中的重复元素

class Solution {public:ListNode* deleteDuplicates(ListNode* head) {ListNode*  dummy_head = new ListNode(-100,head);ListNode* pre = dummy_head;ListNode* cur = pre->next;while(cur){while(cur&&pre->val == cur->val)cur = cur->next;pre->next = cur;pre = cur;if(cur) cur = cur->next;}return dummy_head->next;}
};

82. 删除排序链表中的重复元素 II

class Solution {public:ListNode* deleteDuplicates(ListNode* head) {ListNode* dummy_head = new ListNode(-101,head);ListNode* _pre = dummy_head;ListNode* cur = head;while(cur&&cur->next){ListNode* _next = cur->next;if(cur->val!=_next->val){_pre = cur;cur = _next;}else{while(_next&&cur->val ==_next->val)_next = _next->next;_pre->next = _next;cur = _next;}}return dummy_head->next;}
};
1.6 、链表归并排序、冒泡排序、插入排序;

148. 排序链表 //归并

class Solution {public:pair<ListNode*,ListNode*> _sort(ListNode* l1,ListNode* l2,ListNode* l2_end){ListNode _dm; ListNode* cur = &_dm;ListNode* l1_end = l2;while(l1!=l1_end||l2!=l2_end){if(l1==l1_end){cur->next = l2;l2 = l2->next;cur = cur->next;}else if(l2_end==l2){cur->next = l1;l1 = l1->next;cur = cur->next;}else{if(l1->val<= l2->val){cur->next = l1;l1 = l1->next;cur = cur->next;}else{cur->next = l2;l2 = l2->next;cur = cur->next;}}}return {_dm.next,cur};}ListNode* sortList(ListNode* head) {ListNode* dummy_head = new ListNode(1,head);ListNode* cur = dummy_head,*_pre = cur;int len = 0;while(cur){cur = cur->next;len++;}ListNode* _b1,* _b2,* _e2;for(int i=1;i<=len;i +=i) // length{_pre = dummy_head;while(_pre&&_pre->next){cur = _pre->next;_b1 = cur;for(int j=0;j<i&&cur;j++)cur = cur->next;_b2 = cur;for(int j=0;j<i&&cur;j++)cur = cur->next;_e2 = cur; auto [_begin,_end] = _sort(_b1,_b2,_e2);//merge_pre->next = _begin;_pre = _end;_pre->next = cur;}}return dummy_head->next;}
};

链表冒泡排序 // 向后将最大的冒泡

class Solution {public:ListNode* SortList(ListNode* head) {ListNode* _end = nullptr;ListNode* cur,*pre;ListNode* dm = new ListNode(-1,head);while(1){cur = dm->next;pre = dm;if(cur == _end) break;while(cur->next!=_end){if(cur->next->val>= cur->val){pre = cur;cur = cur->next;}else{ListNode* temp = cur->next; pre->next = cur->next;cur->next = cur->next->next;temp->next = cur;pre = temp;}}_end = cur;}return dm->next;}
};

147. 对链表进行插入排序 //向前插入到合适的位置

class Solution {public:ListNode* insertionSortList(ListNode* head) {if (head == nullptr) {return head;}ListNode* dummyHead = new ListNode(0,head);ListNode* lastSorted = head;ListNode* curr = head->next;while (curr) {if (lastSorted->val <= curr->val) {lastSorted = lastSorted->next;} else {ListNode *prev = dummyHead;while (prev->next->val <= curr->val) {prev = prev->next;}lastSorted->next = curr->next;curr->next = prev->next;prev->next = curr;}curr = lastSorted->next;}return dummyHead->next;}
};
1.7、二叉树与链表转换 - 二叉树展开为单链表、双链表;有序链表转成二叉树;深拷贝带随机指针的链表;

114. 二叉树展开为链表 //*****

  • 前序遍历的途中直接更改;还可以,存下来再改;或者将右边节点给前驱节点,然后再改,每个节点都这样
  • 先序遍历,先把当前节点的前序右节点链接到当前节点上, 然后把当前节点左右树暂存;以当前节点作为前序节点遍历左数;然后以当前节点或者左子树的尾部作为前序节点遍历右子树;
class Solution {public:TreeNode* dfs(TreeNode* pre,TreeNode* cur){//返回的是:以当前节点为根的树,前序遍历展开后的尾部;if(!cur) return nullptr;pre->right = cur;pre->left = nullptr;if(!cur->left&&!cur->right) return cur;TreeNode* _r = cur->right;TreeNode* _l = cur->left;TreeNode* _end1 = dfs(cur,_l);//先去左边,然后返回左边展开后的尾部TreeNode* _end2;if(_end1)//左边有树,就返回左边遍历完的尾部,用来接上右边的树头;_end2 = dfs(_end1,_r);else//左边没树了,用当前节点当作尾部接上右边的树头_end2 = dfs(cur,_r);return _end2 ==nullptr? _end1:_end2;//右边有树,尾部就在右边,否则在左边树中}void flatten(TreeNode* root) {TreeNode* dm = new TreeNode();dfs(dm,root);}
};

剑指 Offer 36. 二叉搜索树与双向链表

  • 中序遍历 存pre来做;
class Solution {public:Node* _new; Node* _p;void dfs(Node* cur){if(!cur) return ;dfs(cur->left);if(!_new)_new = cur;if(_p){_p->right = cur;cur->left = _p;}_p = cur;dfs(cur->right);}Node* treeToDoublyList(Node* root) {if(!root) return root;_new = nullptr;dfs(root);_p->right = _new;_new->left = _p;return _new;}
};

109. 有序链表转换二叉搜索树 // 还可不用数组,快慢指针一次一次在list里找;

class Solution {public:TreeNode* dfs(vector<int> &list_n,int _l,int _r){if(_l>_r) return nullptr;int mid = (_r+_l)/2;return new TreeNode(list_n[mid],dfs(list_n,_l,mid-1),dfs(list_n,mid+1,_r));}TreeNode* sortedListToBST(ListNode* head) {vector<int> list_n;while(head){list_n.push_back(head->val);head = head->next;}return dfs(list_n,0,list_n.size()-1);}
};

138. 复制带随机指针的链表

  • 还可以创建新节点接在后面,然后ramdom同样接在后面。最后再断开
class Solution {public:unordered_map<Node*,Node*> _hash;    Node* copyRandomList(Node* head) {for(Node*cur = head;cur;cur = cur->next)_hash[cur] = new Node(cur->val);for(Node*cur = head;cur;cur = cur->next){if(cur->next)_hash[cur]->next = _hash[cur->next];_hash[cur]->random = _hash[cur->random];}return _hash[head];}
};
1.8、链表随机节点、删除链表连续和为0的连续节点、旋转链表、指定位置合并链表

382. 链表随机节点 // 数据流中数据,被取到的概率一致;(蓄水池抽样算法)

  • 每次只保留一个数,当遇到第 i 个数时,以 1/i的概率保留它,(i-1)/i的概率保留原来的数。
  • 举例说明: 1 - 10
  • 遇到1,概率为1,保留第一个数。
  • 遇到2,概率为1/2,这个时候,1和2各1/2的概率被保留
  • 遇到3,3被保留的概率为1/3,(之前剩下的数假设1被保留),2/3的概率 1 被保留,(此时1被保留的总概率为 2/3 * 1/2 = 1/3)
  • 遇到4,4被保留的概率为1/4,(之前剩下的数假设1被保留),3/4的概率 1 被保留,(此时1被保留的总概率为 3/4 * 2/3 * 1/2 = 1/4)
class Solution {public:Solution(ListNode* head) {this->head = head;}/** Returns a random node's value. */int getRandom() {ListNode* phead = this->head;int val = phead->val;int count = 1;while (phead){if (rand() % count++ == 0)val = phead->val;phead = phead->next;}return val;}ListNode* head;
};

1171. 从链表中删去总和值为零的连续节点 //前缀和,真的恨//***

class Solution {public:ListNode* removeZeroSumSublists(ListNode* head) {ListNode dm(0,head);unordered_map<int ,ListNode* >_hash;int sum = 0,sum_2 = 0;for(ListNode*  cur = &dm;cur;cur =cur->next){sum+=cur->val;_hash[sum] = cur;}for(ListNode*  cur = &dm;cur;cur =cur->next){sum_2+=cur->val;cur->next = _hash[sum_2]->next;}return dm.next;}
};

61. 旋转链表

class Solution {public:ListNode* rotateRight(ListNode* head, int k) {if(!head||!head->next||k==0) return head;int len=1;ListNode* cur = head;for(;cur&&cur->next;cur = cur->next)len++;cur->next = head;k = k%(len);cur = head;for(int i=1;i<len-k;i++)cur = cur->next;ListNode* _new_head = cur->next;cur->next = nullptr;return _new_head;}
};

1669. 合并两个链表

class Solution {public:ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {ListNode* dm = new ListNode(-1,list1);ListNode* cur = dm;for(int i=0;i<a;i++)cur = cur->next;ListNode* node1 = cur;for(int i=0;i<b-a+2;i++)cur = cur->next; node1->next = list2;for(;node1->next;node1 = node1->next);node1->next = cur;return dm->next;}
};

四、二叉树

1.1、二叉树的染色法迭代遍历、普通迭代;二叉树右视图、锯齿形层序遍历;
   vector<int> postorderTraversal(TreeNode* root) {vector<int> result;stack<pair<TreeNode*,bool>> stck_tree;stck_tree.push(make_pair(root,false));while(!stck_tree.empty()){auto [cur,passed] = stck_tree.top();stck_tree.pop();if(cur == nullptr) continue;if(passed ==false){stck_tree.push(make_pair(cur->right,false));//前序遍历stck_tree.push(make_pair(cur->left,false));stck_tree.push(make_pair(cur,true));stck_tree.push(make_pair(cur->right,false));//中序遍历stck_tree.push(make_pair(cur,true));stck_tree.push(make_pair(cur->left,false));stck_tree.push(make_pair(cur,true));        //后序遍历stck_tree.push(make_pair(cur->right,false));stck_tree.push(make_pair(cur->left,false));}elseresult.push_back(cur->val);}return result;}
  • 前序
   void preorderTraversal(TreeNode* root) {if(root == NULL)return ;stack<TreeNode*> stack;stack.push(root);while(cur != NULL || !stack.empty()){while(cur != NULL){cout<<cur->val<<endl;//前序遍历对其处理stack.push(cur);cur = cur->left;}cur = stack.top();stack.pop();cur = cur->right;}}
  • 中序
 void inorderTraversal(TreeNode* root) {  if( root == NULL ) return ;stack<TreeNode*> stack;TreeNode* cur = root;while(cur != NULL || !stack.empty()){while(cur != NULL){                stack.push(cur);cur = cur->left;}cur = stack.top();stack.pop();cout<<cur->key<<endl; //中序遍历处理cur = cur->right;}}
  • 后序
 // Using a pre pointer to record the last visted nodevoid postorderTraversal(TreeNode* root) {if(root == NULL)return ;stack<TreeNode*> stack;TreeNode* pre = NULL;TreeNode* cur = root;while(cur != NULL || !stack.empty()){while(cur != NULL){stack.push(cur);cur = cur->left;}cur = stack.top();stack.pop();if(cur->right == NULL || pre == cur->right){cout<<cur->val<<endl; //只有没有右孩子、或者右孩子操作过了,才操作当前节点pre = cur;cur = NULL;}else{//否则去右孩子继续遍历stack.push(cur);cur = cur->right;}}}

199. 二叉树的右视图

  • 层序
class Solution {public:vector<int> rightSideView(TreeNode* root) {if(!root) return {};vector<int> res;queue<TreeNode*> _que;_que.push(root);while(!_que.empty()){TreeNode* temp  = nullptr;for(int i = _que.size();i>0;i--){temp = _que.front();_que.pop();if(temp->left) _que.push(temp->left);if(temp->right) _que.push(temp->right);}if(temp) res.push_back(temp->val);}return res;}
};
  • dfs
class Solution {public:vector<int> res;    void dfs(TreeNode* root,int deep){if(!root) return;if(deep == res.size())res.push_back(root->val);dfs(root->right,deep+1); dfs(root->left,deep+1); }vector<int> rightSideView(TreeNode* root) {dfs(root,0);return res;}
};

103. 二叉树的锯齿形层序遍历

class Solution {public:vector<vector<int>> zigzagLevelOrder(TreeNode* root) {vector<vector<int>> res;if (!root) return res;queue<TreeNode*> _que;_que.push(root);bool odd = false;while (!_que.empty()){vector<int> each_level;for (int i = _que.size(); i > 0; i--){TreeNode* temp = _que.front();_que.pop();each_level.push_back(temp->val);if (temp->left) _que.push(temp->left);if (temp->right) _que.push(temp->right);}if (odd) res.push_back({ each_level.rbegin(), each_level.rend() });else  res.push_back(each_level);odd = !odd;}return res;}
};

429. N 叉树的层序遍历

class Solution {public:vector<vector<int>> levelOrder(Node* root) {if(!root) return {};vector<vector<int>> res;queue<Node* >_que;_que.push(root);while(!_que.empty()){vector<int> _vec;for(int i=_que.size();i>0;i--){Node* temp = _que.front();_que.pop();_vec.push_back(temp->val);for(int j=0;j<temp->children.size();j++)if(temp->children[j]) _que.push(temp->children[j]);}res.push_back(_vec);}return res;}
};
1.2、树的属性 - 对称二叉树、平衡二叉树、 二叉树的直径、二叉树最大宽度、二叉树的最小深度、二叉树的镜像

101. 对称二叉树 同剑指 Offer 28. 对称的二叉树

class Solution {public:bool dfs(TreeNode* _l,TreeNode* _r){if(!_l&&!_r) return true;else if(!_l||!_r) return false;if(_l->val!=_r->val) return false;return dfs(_l->left,_r->right)&&dfs(_l->right,_r->left);}bool isSymmetric(TreeNode* root) {return dfs(root,root);}
};

110. 平衡二叉树 同 剑指 Offer 55 - II. 平衡二叉树

  • 这里是最简便的写法;还可以中间提前剪枝退出;这是至底向上的递归思路;就是整个树之遍历了一次 O(n)复杂度;
  • 还可以自顶向下的递归,每次算两边的高度;O(n^2)复杂度;
class Solution {public:pair<int ,bool> dfs(TreeNode* root){if(!root) return{0,true};auto [a1,b1] = dfs(root->left);auto [a2,b2] = dfs(root->right);return {max(a1,a2)+1,b1&&b2&&(abs(a1-a2)<=1)};}bool isBalanced(TreeNode* root) {return dfs(root).second;}
};

543. 二叉树的直径

class Solution {public:int res = 1;int dfs(TreeNode* root){if(!root) return 0;int _l = dfs(root->left);int _r = dfs(root->right);res = max(res,_l+_r+1);return max(_l,_r)+1;}int diameterOfBinaryTree(TreeNode* root) {dfs(root);return res-1;}
};

662. 二叉树最大宽度

  • 当前索引* 2就是左子树索引;按照每层的最大索引与最小索引之差来算;
  • 为了避免溢出,每次的索引减去当前层最小索引;
class Solution {public:int widthOfBinaryTree(TreeNode* root) {int res = 0;deque<pair<TreeNode*, int> > _deq;_deq.push_back({ root,1 });while (!_deq.empty()){res = max(res, _deq.back().second - _deq.front().second + 1);int base =  _deq.front().second;for (int i = _deq.size(); i > 0; i--){auto[temp, num] = _deq.front();_deq.pop_front();num-=base;if (temp->left) _deq.push_back({ temp->left,(2 * num)});if (temp->right) _deq.push_back({ temp->right,(2 * num +1)});}}return res;}
};

111. 二叉树的最小深度

class Solution {public:int minDepth(TreeNode* root) {if(!root) return 0;if(!root->left&&!root->right) return 1;int temp = INT_MAX;if(root->left)  temp = min(temp,minDepth(root->left));if(root->right) temp = min(temp,minDepth(root->right));return temp+1;}
};

226. 翻转二叉树 同 剑指 Offer 27. 二叉树的镜像

class Solution {public:TreeNode* invertTree(TreeNode* root) {if(!root) return root;TreeNode* _l = root->left;root->left = invertTree(root->right);root->right = invertTree(_l);return root;}
};
1.3、 二叉树的最近公共祖先、判断是否是完全二叉树

236. 二叉树的最近公共祖先

  • 还可以从根记录路径,然后正向推;
  • 如果是二叉搜索树就更简单了;
class Solution {public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(!root||root ==q||root == p) return root;TreeNode* _l = lowestCommonAncestor(root->left,p,q);TreeNode* _r = lowestCommonAncestor(root->right,p,q);if(_l&&_r) return root;else if(_l) return _l;else if(_r) return _r;else return nullptr;}
};

958. 二叉树的完全性检验

  • bfs,有空子树了以后,再有子树,就不是完全的;
  • dfs,像二叉堆一样做;根据节点实际个数,和按照二叉堆算出来的对比即可;
class Solution {public:bool isCompleteTree(TreeNode* root) {if(!root) return true;bool _end = false;queue<TreeNode* > _que;_que.push(root);while(!_que.empty()){TreeNode* cur = _que.front();_que.pop();if(cur->left) _que.push(cur->left);if(cur->right) _que.push(cur->right);if(cur->right&&!cur->left) return false;if(_end&&(cur->left||cur->right)) return false;if(!cur->left||!cur->right) _end = true;}return true;}
};
1.4、 寻找重复的子树 、另一棵树的子树 、树的子结构

652. 寻找重复的子树

class Solution {public:vector<TreeNode*> res;unordered_map<string, int>_hash;string dfs(TreeNode* root){if (!root) return ",#";string temp = "," + to_string(root->val)+dfs(root->left)+dfs(root->right);if (_hash.find(temp) != _hash.end()&&_hash[temp]==1)res.push_back(root);_hash[temp]++;return temp;}vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {dfs(root);return res;}
};

572. 另一棵树的子树

  • 子结构是包括即可,子树得从这点到后面都得相同
class Solution {public:bool matching(TreeNode* root, TreeNode* subRoot){if(!root&&!subRoot) return true;else if(!root||!subRoot) return false;if(root->val!=subRoot->val ) return false;return matching(root->left,subRoot->left)&&matching(root->right,subRoot->right);}bool isSubtree(TreeNode* root, TreeNode* subRoot) {if(matching(root,subRoot)) return true;else if(!root&&!subRoot) return true;else if(!root||!subRoot) return false;else return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);}
};

剑指 Offer 26. 树的子结构

  • 子结构是包括即可,子树得从这点到后面都得相同
class Solution {public:bool matching(TreeNode* root, TreeNode* subRoot){if(!subRoot) return true; // 与匹配子树的不同else if(!root&&subRoot) return false; // 与匹配子树的不同if(root->val!=subRoot->val ) return false;return matching(root->left,subRoot->left)&&matching(root->right,subRoot->right);}bool isSubStructure(TreeNode* root, TreeNode* subRoot) {if(!subRoot) return false;if(matching(root,subRoot)) return true;else if(!root&&!subRoot) return true;else if(!root||!subRoot) return false;else return isSubStructure(root->left,subRoot)||isSubStructure(root->right,subRoot);}
};
1.3、二叉树的所有路径 、最大路径和 、路径总和

257. 二叉树的所有路径

class Solution {public:vector<string> binaryTreePaths(TreeNode* root) {if(!root) return{};string temp_str = to_string(root->val);if(!root->left&&!root->right)return {temp_str};vector<string> res;vector<string>temp1 = binaryTreePaths(root->left);for(auto& it:temp1)res.push_back(temp_str+"->"+it);vector<string>temp2 = binaryTreePaths(root->right);for(auto& it:temp2)res.push_back(temp_str+"->"+it);return res;}
};

124. 二叉树中的最大路径和 //***

  • 有点像树形dp;
class Solution {public:int res;int  dfs(TreeNode* root) // 全部 单线{if(!root) return 0;int _l1 = max(dfs(root->left),0);int _l2 = max(dfs(root->right),0);res = max(res,root ->val+_l1+_l2);return root ->val+(max(_l1,_l2));}int maxPathSum(TreeNode* root) {res = root->val;return max(dfs(root),res);}
};

113. 路径总和 II 剑指 Offer 34. 二叉树中和为某一值的路径

class Solution {public:vector<vector<int>> res;vector<int> temp;void dfs(TreeNode* root, int target){if(!root) return;target-=root->val;temp.push_back(root->val);if(target == 0&&!root->left&&!root->right){res.push_back(temp);temp.pop_back();return;}dfs(root->left,target);dfs(root->right,target);temp.pop_back();}vector<vector<int>> pathSum(TreeNode* root, int target) {dfs(root,target);return res;}
};

路径总和 III

class Solution {public:int dfs(TreeNode* root, int targetSum){if(!root) return 0;targetSum-=root->val;int res = 0;if(targetSum ==0) res++;res+=dfs(root->left,targetSum);res+=dfs(root->right,targetSum);return res;}int pathSum(TreeNode* root, int targetSum) {int res = dfs(root,targetSum);if(root){res +=pathSum(root->right,targetSum);res +=pathSum(root->left,targetSum);}return res;}
};
1.5、二叉树的改造 - 序列化二叉树

297. 二叉树的序列化与反序列化 同 剑指 Offer 37. 序列化二叉树

  • 如果序列化反序列化二叉搜索树:只需要前序或者后续,因为中序是排序一下就知道的;转换为用后续/ 前序、中序构造二叉搜索树;
class Codec {public:// Encodes a tree to a single string.string serialize(TreeNode* root) {string res = ""; deque<TreeNode*> _que;_que.push_back(root);while(!_que.empty()){for(int i =_que.size();i>0;i--){auto temp = _que.front();_que.pop_front();if(temp){_que.push_back(temp->left);_que.push_back(temp->right);res += to_string(temp->val) +',';}elseres += "null,";}}return res;}// Decodes your encoded data to tree.TreeNode* deserialize(string data) {if(data == "") return nullptr;vector<string> _data;int index1 = 0,index2 = 0;while(index2<data.size()){if(data[index2] == ','){_data.push_back(data.substr(index1,index2-index1));index1 = index2+1;}index2++;}vector<TreeNode*> trees(_data.size(),nullptr);for(int i=0;i<trees.size();i++)if(_data[i] !="null" )trees[i] = new TreeNode(stoi(_data[i]));int offset  = 0;for(int i=0;i<trees.size();i++){if(trees[i] == nullptr) {offset++;continue;}if(2*(i-offset)+1 < trees.size()) trees[i]->left = trees[2*(i-offset)+1];if(2*(i-offset)+2 < trees.size())trees[i]->right = trees[2*(i-offset)+2];}return trees[0];}
};
1.6、二叉树的构造- 给定两个序列来构造二叉树

105. 从前序与中序遍历序列构造二叉树 同 剑指 Offer 07. 重建二叉树 //***

  • 每次在中序遍历中找到根节点,然后对两边分治;
class Solution {public:unordered_map<int,int> _hash;TreeNode* dfs(vector<int>& preorder, vector<int>& inorder,int in_l,int in_r,int pr_l,int pr_r){if(in_l>in_r) return nullptr;int index = _hash[preorder[pr_l]];// 找到当前中序遍历中的根节点int  pr_mid =  pr_l +  index - in_l;//根据中序遍历中根节点两边的子节点个数,将前序遍历分开左右子树TreeNode* _n = new TreeNode(inorder[index]);_n->left = dfs(preorder,inorder,in_l,index-1,pr_l+1,pr_mid);_n->right = dfs(preorder,inorder,index+1,in_r,pr_mid+1,pr_r);return _n;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {for(int i=0;i<inorder.size();i++)_hash[inorder[i]] = i;return dfs(preorder,inorder,0,inorder.size()-1,0,inorder.size()-1);}
};

106. 从中序与后序遍历序列构造二叉树

class Solution {public:unordered_map<int,int> _hash;TreeNode* dfs(vector<int>& inorder, vector<int>& postorder,int p_l,int p_r,int i_l,int i_r){if(p_l>p_r) return nullptr;TreeNode* root = new TreeNode(postorder[p_r]);int mid = _hash[postorder[p_r]];int nums_left = mid-i_l;int nums_right = i_r-mid;root->left = dfs(inorder,postorder,p_l,p_l+nums_left-1,i_l,mid-1);root->right = dfs(inorder,postorder,p_r-nums_right,p_r-1,mid+1,i_r);return root;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {for(int i=0;i<inorder.size();i++)_hash[inorder[i]] = i;return dfs(inorder,postorder,0,inorder.size()-1,0,inorder.size()-1);}
};

889. 根据前序和后序遍历构造二叉树

class Solution {public:int pre_index;unordered_map<int,int> _hash;TreeNode* dfs(vector<int>& preorder, vector<int>& postorder,int _l,int _r){if(_l>_r) return nullptr;TreeNode*  root = new TreeNode(preorder[pre_index]);pre_index++;if(_l == _r)  return root;if(pre_index == preorder.size() )return root;int mid = _hash[preorder[pre_index]];root->left = dfs(preorder,postorder,_l,mid);root->right = dfs(preorder,postorder,mid+1,_r-1);return root;}TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {pre_index = 0;for(int i=0;i<postorder.size();i++)_hash[postorder[i]] = i;return dfs(preorder,postorder,0,postorder.size()-1);}
};

1.7、二叉搜索树的构造-

98. 验证二叉搜索树

class Solution {public:TreeNode* _pre;bool isValidBST(TreeNode* root) {if(!root) return true;if(!isValidBST(root->left)) return false;if(_pre&& _pre->val>=root->val) return false;_pre = root;if(!isValidBST(root->right)) return false;return true;}
};

701. 二叉搜索树中的插入操作

class Solution {public:TreeNode* insertIntoBST(TreeNode* root, int val) {if(!root) return new TreeNode(val);else if(val>root->val) root->right =  insertIntoBST(root->right,val);else  root->left =   insertIntoBST(root->left,val);return root;}
};

108. 将有序数组转换为二叉搜索树

class Solution {public:TreeNode* dfs(vector<int>& nums,int _l,int _r){if(_l >_r) return nullptr; int mid = (_l+_r)/2;return new TreeNode(nums[mid],dfs(nums,_l,mid-1),dfs(nums,mid+1,_r));}TreeNode* sortedArrayToBST(vector<int>& nums) {return dfs(nums,0,nums.size()-1);}
};

剑指 Offer 33. 二叉搜索树的后序遍历序列判断数组是不是后序遍历

  • 其实就是,后续遍历中最后是根,中间的会被根分成小于根的一部分和大于根的一部分,只要每段都严格分开了,就没问题;
class Solution {public:bool dfs(vector<int>& postorder,int _l,int _r){if(_l>=_r) return true;int i=_r-1;for(;i>=0;i--)if(postorder[i]<postorder[_r]) break;for(int j=i;j>=0;j--)if(postorder[j]>postorder[_r]) return false;return dfs(postorder,i+1,_r-1)&&dfs(postorder,_l,i);}bool verifyPostorder(vector<int>& postorder) {return dfs(postorder,0,postorder.size()-1);}
};

96. 不同的二叉搜索树 数组能表示多少种二叉搜索树

  • 其实是动规题
class Solution {public:int numTrees(int n) {vector<int>dp (n+1);dp[0] = 1;dp[1] = 1;for(int i=2;i<=n;i++)for(int j=0;j<i;j++)dp[i]+=dp[j]*dp[i-j-1];return dp.back();}
};

230. 二叉搜索树中第K小的元素

class Solution {public:int _k,res;void dfs(TreeNode* root){if(!root||k<=0) return;dfs(root->left);if(--_k ==0) {res = root->val;return;}dfs(root->right);}int kthSmallest(TreeNode* root, int k) {_k=k;dfs(root);return res;}
};

1.8、二叉搜索树 - 恢复二叉搜索树、最大 BST 子树

99. 恢复二叉搜索树

  • 空间复杂度:O(N);只需要缓存下来,或者递归的时候记录,找不是上升的点交换即可;
  • Morris 中序遍历能做到O(1)空间
class Solution {public:TreeNode* _mis1,* _mis2;TreeNode* _pre = nullptr;void dfs(TreeNode* root){if(!root) return ;dfs(root->left);if(_pre&&_pre->val>root->val){   _mis1 = root;if(!_mis2)_mis2 = _pre;}_pre = root;dfs(root->right);}void recoverTree(TreeNode* root) {dfs(root);int temp = _mis1->val;_mis1->val = _mis2->val;_mis2->val = temp;}
};

333. 最大 BST 子树

  • 自低向上,后续;O(n^2)
class Solution {public:int largestBSTSubtree(TreeNode* root) {if (root == NULL)return 0;if (is_valid(root, INT_MIN, INT_MAX)) {return count(root);}return max(largestBSTSubtree(root->left), largestBSTSubtree(root->right));}bool is_valid(TreeNode* root, int min_val, int max_val) {if (root == NULL) return true;if (root->val <= min_val || root->val >= max_val)return false;return is_valid(root->left, min_val, root->val) && is_valid(root->right, root->val, max_val);}int count(TreeNode* root) {if (root == NULL) return 0;return count(root->left) + count(root->right) + 1;}
};
  • 自低向上,后续;0(n)
class Solution {public:struct Min_max {int min_val;//子树中的最大值int max_val;//子数中的最小值int size;//子树的大小bool sign;//子树是否是二叉搜索树Min_max() :min_val(INT_MAX), max_val(INT_MIN), size(0), sign(true) {}};int largestBSTSubtree(TreeNode* root) {Min_max m = helper(root);return m.size;}Min_max helper(TreeNode* root) {if (root == NULL) return Min_max();Min_max m_val;Min_max _l = helper(root->left);Min_max _r = helper(root->right);if (!_l.sign || !_r.sign|| _l.max_val >= root->val || _r.min_val <= root->val) {m_val.size = max(_l.size, _r.size);m_val.sign = false;return m_val;}m_val.sign = true;m_val.size = _l.size + _r.size + 1;
// 最小的只能是自己或者左子树最小的,最大的只能是自己或者右子树最大的m_val.min_val = root->left != NULL ? _l.min_val : root->val;m_val.max_val = root->right != NULL ? _r.max_val : root->val;return m_val;}
};

1.9、二叉树 -

863. 二叉树中所有距离为 K 的结点

  • 记录父节点,然后从tar开始爆搜即可;
class Solution {public:unordered_map<TreeNode* ,TreeNode* > _dad;vector<int> res;unordered_set<TreeNode*> _usd;void dfs(TreeNode* pre,TreeNode* root){if(!root)return ;_dad[root] = pre;dfs(root,root->left);dfs(root,root->right);}void find_path(TreeNode* root,int k){if(!root||k<0||_usd.find(root)!=_usd.end()) return;if(k ==0) {res.push_back(root->val);return;}_usd.insert(root);find_path(root->left,k-1);find_path(root->right,k-1);if(_dad.find(root)!=_dad.end())find_path(_dad[root],k-1);_usd.erase(root);}vector<int> distanceK(TreeNode* root, TreeNode* target, int k) {dfs(root,root->left);dfs(root,root->right);find_path(target,k);return res;}
};

1367. 二叉树中的列表

class Solution {public:bool dfs(ListNode* head, TreeNode* root) {if(!head) return true;if(!root) return false;if(head->val!=root->val) return false;return dfs(head->next,root->left)||dfs(head->next,root->right);}bool isSubPath(ListNode* head, TreeNode* root) {if(!root) return false;return dfs(head,root)||isSubPath(head,root->left)||isSubPath(head,root->right);}
};

331. 验证二叉树的前序序列化

  • 数字后面能跟俩点,对坑位贡献+2,但是自身占一个位。null只是占个位;位置空了就说明不能构成树
class Solution {public:bool isValidSerialization(string preorder) {int cnt = 1;if(preorder.size() == 0) return true;if(preorder[0] == '#'&& preorder.size()>1) return false;for(int i=0,j = 0;i<preorder.size();i++){if(cnt<=0) return false;if(preorder[i] == ','){if(i -1 == j && preorder[j] == '#')cnt--;elsecnt++;j = i+1;}if(i == preorder.size()-1){if(i == j && preorder[j] == '#')cnt--;elsecnt++;}}return cnt ==0;}
};

222. 完全二叉树的节点个数 // 二分法O(log^2 n)

class Solution {public:bool find(TreeNode* root,int n,int height){int bits = 1<<(height-1);while(bits){if(n&bits) root = root->right;else root = root->left;bits >>= 1;}return root != nullptr;}int countNodes(TreeNode* root) {if(!root) return 0;int height = 0;for(TreeNode* temp = root;temp;temp = temp->left,height++);int _l  = 1<<(height-1),_r  = (1<<height)-1;while(_l<_r){int mid = _l+(_r-_l+1)/2;if(find(root,mid,height-1)) _l = mid;else _r = mid-1;}return _l;}
};

1339. 分裂二叉树的最大乘积

  • 先求所有的和,然后后序遍历对每一个边都当作分开来求最大值;
class Solution {public:long res,sum;int dfs(TreeNode* root){if (!root) return 0;return  dfs(root->left)+ dfs(root->right) + root->val;}int cal(TreeNode* root){if (!root)return 0;int _l = cal(root->left);int _r = cal(root->right);res = max(res,max(_l*(sum-_l),_r*(sum-_r)));return  _l + _r + root->val;}int maxProduct(TreeNode* root) {sum = dfs(root);        res = 0;cal(root);return res % int(1e9 + 7);}
};

五、图

1.1 、拓扑排序:最小高度树、课程表 II

310. 最小高度树

  • 直接按照图的每个节点都DFS 会超时
  • 拓扑排序,每次剥掉树外层(入度为1的节点);最后剩下的小于2的节点就是根;
class Solution{public:vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {if (n == 1) return {0};unordered_map<int, unordered_set<int>> _map;for (auto _vec : edges){_map[_vec[0]].insert(_vec[1]);_map[_vec[1]].insert(_vec[0]);}queue<int>_que;for (auto it : _map)if (it.second.size() == 1)_que.push(it.first);while (!_que.empty()){if (_map.size() <= 2) break;for (int i = _que.size(); i > 0; i--){int temp = _que.front();_que.pop();int tar = *(_map[temp].begin());_map[tar].erase(temp);_map.erase(temp);if (_map[tar].size() == 1)_que.push(tar);}}vector<int> res;for (auto it : _map)res.push_back(it.first);return res;}
};

210. 课程表 II

class Solution {public:vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {vector<vector<int>> edges(numCourses);//有向图vector<int> indeg(numCourses); //入度vector<int> result;for (const auto& info: prerequisites) {edges[info[1]].push_back(info[0]);++indeg[info[0]];}queue<int> q;for (int i = 0; i < numCourses; ++i)if (indeg[i] == 0)q.push(i);while (!q.empty()) {int index = q.front();q.pop();result.push_back(index);for (int v: edges[index]) {--indeg[v];if (indeg[v] == 0) q.push(v);}}if (result.size() != numCourses)  return {};return result;}
};

六、查找、二分法、

1.1 、二分 - lower、uper bound

ceil:

  1. 如果数组中存在target,返回target中的最大索引;
  2. 如果不存在target,返回upper(大于这个值的最小元素索引);
  3. 如果target大于所有的值,返回数组个数n(即数组上溢出的索引)

floor:

  1. 如果找到target,返回第一个target 的索引;
  2. 如果没有找到target,返回比target小的最大值的索引;
  3. 如果target小于所有的值,返回-1(即数组下溢出的索引);
  • ceil
template<typename T>
int ceil(T arr[], int n, T target){// 寻找比target大的最小索引值int l = 0, r = n;while( l < r ){// 使用普通的向下取整即可避免死循环int mid = l + (r-l)/2;if( arr[mid] <= target )l = mid + 1;else // arr[mid] > targetr = mid;}assert( l == r );// 如果该索引-1就是target本身, 该索引+1即为返回值if( r - 1 >= 0 && arr[r-1] == target )return r-1;// 否则, 该索引即为返回值return r;
}
  • floor
template<typename T>
int floor(T arr[], int n, T target){// 寻找比target小的最大索引int l = -1, r = n-1;while( l < r ){// 使用向上取整避免死循环int mid = l + (r-l+1)/2;if( arr[mid] >= target )r = mid - 1;elsel = mid;}assert( l == r );// 如果该索引+1就是target本身, 该索引+1即为返回值if( l + 1 < n && arr[l+1] == target )return l + 1;// 否则, 该索引即为返回值return l;
}
1.2 、二分 - 搜索旋转排序数组 I/II、旋转排序数组中的最小值 I/II、

33. 搜索旋转排序数组

class Solution {public:int search(vector<int>& nums, int target) {int n = (int)nums.size();if (!n) {return -1;}if (n == 1) {return nums[0] == target ? 0 : -1;}int l = 0, r = n - 1;while (l <= r) {int mid = (l + r) / 2;if (nums[mid] == target) return mid;if (nums[0] <= nums[mid]) {if (nums[0] <= target && target < nums[mid]) {r = mid - 1;} else {l = mid + 1;}} else {if (nums[mid] < target && target <= nums[n - 1]) {l = mid + 1;} else {r = mid - 1;}}}return -1;}
};

81. 搜索旋转排序数组 II

class Solution {public:bool search(vector<int> &nums, int target) {int n = nums.size();if (n == 0) {return false;}if (n == 1) {return nums[0] == target;}int l = 0, r = n - 1;while (l <= r) {int mid = (l + r) / 2;if (nums[mid] == target) {return true;}if (nums[l] == nums[mid] && nums[mid] == nums[r]) {++l;--r;} else if (nums[l] <= nums[mid]) {if (nums[l] <= target && target < nums[mid]) {r = mid - 1;} else {l = mid + 1;}} else {if (nums[mid] < target && target <= nums[n - 1]) {l = mid + 1;} else {r = mid - 1;}}}return false;}
};

153. 寻找旋转排序数组中的最小值

class Solution {public:int findMin(vector<int>& nums) {int _l = 0,_r = nums.size()-1;while(_l<_r){int mid  = (_l+_r)/2;if(nums[mid]>nums[_r]) _l = mid+1;else if(nums[mid]<=nums[_r])_r = mid;}return nums[_l];}
};

154. 寻找旋转排序数组中的最小值 II

class Solution {public:int findMin(vector<int>& nums) {int _l = 0,_r = nums.size()-1;while(_l<_r){int mid  = (_l+_r)/2;if(nums[mid]>nums[_r]) _l = mid+1;else if(nums[mid]<nums[_r])_r = mid;else _r--;}return nums[_l];}
};
1.3、几数之和

1. 两数之和

  • 暴力扫描;哈希记录,一次扫描

15. 三数之和

  • 排序+双指针;重点在于去重剪枝(外部三元组剪枝、内部两元组剪枝)。
class Solution {public:vector<vector<int>> threeSum(vector<int>& nums) {sort(nums.begin(), nums.end());vector<vector<int>> res;for(int i=0;i<nums.size();i++){if(i>0&&nums[i] == nums[i-1]) continue;// 剪枝int j=i+1,k=nums.size()-1;int temp = -nums[i];while(j<k){if(nums[j]+nums[k] == temp) {res.push_back({-temp,nums[j++],nums[k--]});while(j<k&&nums[j] == nums[j-1]) j++;// 剪枝while(j<k&&nums[k] == nums[k+1]) k--;// 剪枝}else if(nums[j]+nums[k] < temp)j++;else k--;}}return res;}
};

18. 四数之和

  • 三数之和 外嵌四数。重点在于去重。
class Solution {public:vector<vector<int>> threeSum(vector<int>& nums, int target, int begin) {vector<vector<int>> res;for(int i=begin;i<nums.size();i++){if(i>begin&&nums[i] == nums[i-1]) continue;// 剪枝int j=i+1,k=nums.size()-1;int temp = target-nums[i];while(j<k){if(nums[j]+nums[k] == temp) {res.push_back({nums[i],nums[j++],nums[k--]});while(j<k&&nums[j] == nums[j-1]) j++;// 剪枝while(j<k&&nums[k] == nums[k+1]) k--;// 剪枝}else if(nums[j]+nums[k] < temp)j++;else k--;}}return res;}vector<vector<int>> fourSum(vector<int>& nums, int target) {if(nums.size()<4) return {};sort(nums.begin(), nums.end());vector<vector<int>> result;for (int i = 0; i+3 < nums.size(); i++){int new_tar = target - nums[i];if (i > 0 && nums[i] == nums[i - 1]) continue;//不能有重复的四元组int left = i + 1;vector<vector<int>> temp_vec = threeSum(nums, new_tar, left);if (temp_vec.size() > 0){for (auto vec : temp_vec){result.push_back(vec);result.back().push_back(nums[i]);}}}return result;}
};

16. 最接近的三数之和

  • 和三数之和相似,排序+双指针;多了一层更新差值和结果的步骤。

class Solution {public:int threeSumClosest(vector<int>& nums, int target) {int gap = INT_MAX;sort(nums.begin(), nums.end());int result = 0;function<void(int&)>  update = [&](int &temp){if (gap > (abs)(temp - target)){gap = (abs)(temp - target);result = temp;}};for (int i = 0; i < nums.size() - 2; i++){int left = i + 1;int right = nums.size() - 1;if (i > 0 && nums[i - 1] == nums[i]) continue;while (left < right){int temp = nums[i] + nums[left] + nums[right];update(temp);if (temp > target){right--;while (right > left&&nums[right] == nums[right + 1])right--;}else if (temp < target){left++;while (right > left&&nums[left] == nums[left - 1])left++;}elsereturn temp;}}return result;}
};

1.4、寻找两个正序数组的中位数、有序数组中的缺失元素、长度最小的子数组

4. 寻找两个正序数组的中位数

  • 归并
class Solution {public:double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {int len = nums1.size()+nums2.size();int len2 = len/2;int index = 0,_l =0,_r =0;vector<int> can(len2+1);while(index<=len2){if(_l ==nums1.size())can[index] =nums2[_r++];else if(_r ==nums2.size())can[index] =nums1[_l++];else{if(nums1[_l]<nums2[_r])can[index] =nums1[_l++];elsecan[index] =nums2[_r++];}index++;}if(len%2 ==1) return can[len2];else return (can[len2-1]+can[len2] )/2.0;}
};
  • 二分 O(log (m+n))
class Solution {public:int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) {/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较* 这里的 "/" 表示整除* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个* 这样 pivot 本身最大也只能是第 k-1 小的元素* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数*/int m = nums1.size();int n = nums2.size();int index1 = 0, index2 = 0;while (true) {if (index1 == m) return nums2[index2 + k - 1];if (index2 == n) return nums1[index1 + k - 1];if (k == 1) return min(nums1[index1], nums2[index2]);// 正常情况int newIndex1 = min(index1 + k / 2 - 1, m - 1);int newIndex2 = min(index2 + k / 2 - 1, n - 1);int pivot1 = nums1[newIndex1];int pivot2 = nums2[newIndex2];if (pivot1 <= pivot2) {k -= newIndex1 - index1 + 1;index1 = newIndex1 + 1;}else {k -= newIndex2 - index2 + 1;index2 = newIndex2 + 1;}}}double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {int totalLength = nums1.size() + nums2.size();if (totalLength % 2 == 1) {return getKthElement(nums1, nums2, (totalLength + 1) / 2);}else {return (getKthElement(nums1, nums2, totalLength / 2) + getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0;}}
};

1060. 有序数组中的缺失元素(二分查找)

  • nums[r] - nums[0] - mid 为中间缺失的数量(即,当前数减去最小的,是中间应该有的,再减去中间已经有的;就是缺失了的)
class Solution {public:int missingElement(vector<int>& nums, int k) {int l = 0, r = nums.size() - 1, mid, miss;while (l <= r){mid = l + ((r - l) >> 1);miss = nums[mid] - nums[0] - mid;if (miss < k)l = mid + 1;else if (miss > k)r = mid - 1;elsereturn nums[mid] - 1;}return nums[r] + k - (nums[r] - nums[0] - r);}};

209. 长度最小的子数组

  • 前缀和+二分查找
class Solution {public:int minSubArrayLen(int s, vector<int>& nums) {int n = nums.size();if (n == 0)return 0;vector<int> sums(n + 1, 0); for (int i = 1; i <= n; i++) sums[i] = sums[i - 1] + nums[i - 1];int ans = INT_MAX;for (int i = 1; i <= n; i++) {int target = s + sums[i - 1];auto bound = lower_bound(sums.begin(), sums.end(), target);if (bound != sums.end()) {ans = min(ans, static_cast<int>((bound - sums.begin()) - (i - 1)));}}return ans == INT_MAX ? 0 : ans;}
};
  • 滑动窗口
class Solution {public:int minSubArrayLen(int target, vector<int>& nums) {int res = nums.size()+1;int _l=0,_r=0;int sum =0;while(_r<nums.size()){sum+=nums[_r++];while(_l<=_r && sum>=target){res = min(res,_r-_l);sum-=nums[_l++];}}return res == nums.size()+1? 0:res;}
};
1.5 、爱吃香蕉的珂珂、按权重随机选择

875. 爱吃香蕉的珂珂

  • 二分枚举吃的速度即可
class Solution {public:bool check(vector<int>& piles, int h,int tar){int temp =0;for(auto& it:piles)temp += (it+tar-1)/tar;return temp<=h;}int minEatingSpeed(vector<int>& piles, int h) {int _max =0;for(auto& it:piles)_max = max(_max,it);int _l =1,_r = _max;while(_l<_r){int mid = (_l+_r)/2;if(check(piles,h,mid)) _r = mid;else _l = mid+1;}    return _l;}
};

528. 按权重随机选择

class Solution {public:vector<int> arr;int sum;Solution(vector<int>& w) {sum = 0;srand(time(0));for (auto it : w){sum += it;arr.push_back(sum);}}int pickIndex() {int tar = (rand() % sum) + 1;auto low = lower_bound(arr.begin(), arr.end(), tar);return low - arr.begin();}
};

六、滑动窗口

1.1 、最小覆盖子串、无重复字符的最长子串

76. 最小覆盖子串

  • 每次检查hash表是否匹配,匹配了 ,移动左边窗口继续匹配;
class Solution {public:bool _cal(unordered_map<char,int>  &_t_hash,unordered_map<char,int> &_s_hash){for(auto [it,cnt]:_t_hash)if(_s_hash.find(it) == _s_hash.end()||_s_hash[it]<cnt) return false;return true;}string minWindow(string s, string t) {unordered_map<char,int> _t_hash,_s_hash;string res;int _begin = 0,len=0;for(auto it:t) _t_hash[it]++;for(int i=0;i<t.size()-1&&i<s.size();i++) _s_hash[s[i]]++;for(int i=t.size()-1,j=0;i<s.size();i++){_s_hash[s[i]]++;while(_cal(_t_hash,_s_hash)){if(len ==0||len>i-j+1){len=i-j+1;_begin = j;}_s_hash[s[j++]]--;}}return s.substr(_begin,len);}
};
  • 只记录窗口,和需要匹配的长度即可;不用每次都扫描hash表了;快了很多
class Solution {public:string minWindow(string s, string t) {vector<int> _hash(128,0);for(auto it:t) _hash[it]++;int count =t.size();int _l=0,_r=0;int len=s.size()+1,_begin = 0;while(_r<s.size()){if(_hash[s[_r]]>0) count--;_hash[s[_r]]--;if(count ==0){while(_l<_r&&_hash[s[_l]]<0) _hash[s[_l++]]++;if(len>_r-_l+1){len = _r-_l+1;_begin = _l;}_hash[s[_l++]]++;count++;}_r++;}return len ==s.size()+1 ? "": s.substr(_begin,len);}
};

3. 无重复字符的最长子串

class Solution {public:int lengthOfLongestSubstring(string s) {if(s.size()==0) return 0;unordered_set<char> _hash;int i=0,j=0,res = 0;while(i<s.size()){if(_hash.find(s[i]) == _hash.end()) _hash.insert(s[i++]);else  _hash.erase(s[j++]);   res  = max(res,i-j);}return res;}
};
  • hash 记录一个字母最后出现的位置,重复了就直接尾指针移动到记录的位置;(要注意记录的比当前的小,不用去跳)
class Solution {public:int lengthOfLongestSubstring(string s) {vector<int> _memo(128,0);int res =0;for(int i =0,j = 0;i<s.size()&&j<s.size();i++){if(_memo[s[i]]!=0 && j<_memo[s[i]])j=_memo[s[i]];_memo[s[i]] = i+1;res = max(i-j+1,res);}return res;}
};
1.2 、滑动窗口最大值(单调栈)

239. 滑动窗口最大值

  • 优先队列(延迟删除)
class Solution {public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {int n = nums.size();priority_queue<pair<int, int>> q;for (int i = 0; i < k; ++i) {q.emplace(nums[i], i);}vector<int> ans = {q.top().first};for (int i = k; i < n; ++i) {q.emplace(nums[i], i);while (q.top().second <= i - k) {q.pop();}ans.push_back(q.top().first);}return ans;}
};
  • 单调队列,同样延迟删除
class Solution {public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {int n = nums.size();deque<int> q;for (int i = 0; i < k; ++i) {while (!q.empty() && nums[i] >= nums[q.back()]) {q.pop_back();}q.push_back(i);}vector<int> ans = {nums[q.front()]};for (int i = k; i < n; ++i) {while (!q.empty() && nums[i] >= nums[q.back()]) {q.pop_back();}q.push_back(i);while (q.front() <= i - k) {q.pop_front();}ans.push_back(nums[q.front()]);}return ans;}
};

七、位运算、算数

  • X % 2^n = X & (2^n – 1) 位运算& 代替取模;
  • (n&(n-1)) 能去掉二进制中最后一个 1 ;(用于判断是不是2 的幂次、二进制中1的个数)
1.1 、只出现一次的数字 II / III

137. 只出现一次的数字 II // 二进制分类

class Solution {public:int singleNumber(vector<int>& nums) {int ans = 0;for (int i = 0; i < 32; ++i) {int total = 0;for (int num: nums) total += ((num >> i) & 1);if (total % 3) ans |= (1 << i);}return ans;}
};

260. 只出现一次的数字 III // 异或 分组 再异或

class Solution {public:vector<int> singleNumber(vector<int>& nums) {int ret = 0;for (int n : nums)ret ^= n;int div = 1;while ((div & ret) == 0)div <<= 1;int a = 0, b = 0;for (int n : nums)if (div & n)a ^= n;elseb ^= n;return vector<int>{a, b};}
};

1.2 、数值次方(快速幂)、两数相除(整数除法)

50. Pow(x, n) 同 剑指 Offer 16. 数值的整数次方

class Solution {public:const int mis = 1e-8;double myPow(double x, int n) {if(abs(x-0) <=mis) return 0;long bb = n;if(bb<0){x = 1/x;bb = -bb;}unsigned long  b = bb;double res =1.0;while(b){if(b&1 == 1)res*=x;x*=x;b=b>>1;}return res;}
};

29. 两数相除

class Solution {public:int divide(int dividend, int divisor) {if(dividend == 0) return 0;if(divisor == 1) return dividend;if(divisor == -1){if(dividend>INT_MIN) return -dividend;// 只要不是最小的那个整数,都是直接返回相反数就好啦return INT_MAX;// 是最小的那个,那就返回最大的整数啦}long a = dividend;long b = divisor;int sign = 1; if((a>0&&b<0) || (a<0&&b>0)){sign = -1;}a = a>0?a:-a;b = b>0?b:-b;long res = div(a,b);if(sign>0)return res>INT_MAX?INT_MAX:res;return -res;}int div(long a, long b){  // 似乎精髓和难点就在于下面这几句if(a<b) return 0;long count = 1;long tb = b; // 在后面的代码中不更新bwhile((tb+tb)<=a){count = count + count; // 最小解翻倍tb = tb+tb; // 当前测试的值也翻倍}return count + div(a-tb,b);}
};


八、贪心

1.1 、分发糖果、矩形重叠

135. 分发糖果

  • 同时满足左右规则即可
class Solution {public:int candy(vector<int>& ratings) {vector<int>  _left(ratings.size(),0);vector<int>  _right(ratings.size(),0);int res = 0;for(int i=0;i<ratings.size();i++)if(i>0&&ratings[i]>ratings[i-1]) _left[i] = _left[i-1]+1;else _left[i] = 1;for(int i=ratings.size()-1;i>=0;i--){if(i+1<ratings.size()&&ratings[i]>ratings[i+1])_right[i] = _right[i+1]+1;else _right[i] = 1;res+=max(_left[i],_right[i]);}return res;}
};

836. 矩形重叠

  • 逆向思维判断没碰撞:只需要判断面积为0;或者一个矩形在另一个矩形的四个方向
class Solution {public:bool isRectangleOverlap(vector<int>& rec1, vector<int>& rec2) {if(rec1[0] == rec1[2]||rec1[1] == rec1[3]||rec2[0] == rec2[2]||rec2[1] == rec2[3])return false;if(rec1[0] >= rec2[2]||rec1[1] >= rec2[3]// 右/上||rec1[2] <= rec2[0]||rec1[3] <= rec2[1])// 左/xreturn false;return true;}
};

九、栈、队列、优先队列

1.1 、栈实现队列队列实现栈、最小栈、

232. 用栈实现队列

  • 只需要用一个栈缓存
class MyQueue {public:stack<int> _in,_out;MyQueue() {}void push(int x) {_in.push(x);}int pop() {int temp;if(_out.empty())while(!_in.empty()){_out.push(_in.top());_in.pop();}temp = _out.top();_out.pop();return temp;}int peek() {if(_out.empty())while(!_in.empty()){_out.push(_in.top());_in.pop();}return _out.top();}bool empty() {return _in.empty()&&_out.empty();}
};

225. 用队列实现栈

  • 用两个队列,每次弹出都需要全部出来取最后一个;O(n)
  • 用一个队列,每次push 保证是栈形式;O(n)
  • 真要实现O(1) ,不如创建n个队列,每个最多存一个数
class MyStack {public:queue<int> _q;MyStack() {}void push(int x) {int len =_q.size();_q.push(x);for(int i=0;i<len;i++){_q.push(_q.front());_q.pop();}}int pop() {int temp = _q.front();_q.pop();return temp;}int top() {return  _q.front();}bool empty() {return _q.empty();}
};

155. 最小栈 同 剑指 Offer 30. 包含min函数的栈

  • 记得维护一个最小栈即可;
    void push(int x) {norm.push(x);if(_min.empty()||_min.top()>x)  _min.push(x);else _min.push(_min.top());}
1.2 、基本计算器 II

227. 基本计算器 II

class Solution {public:int calculate(string s) {stack<int>  _stk;char cal = '+';int num = 0;for (int i = 0; i <= s.size(); i++){if (s[i] == '+' || s[i] == '-' || s[i] == '*' || s[i] == '/' || i == s.size()){if (cal == '+') _stk.push(num);else if (cal == '-') _stk.push(-num);else if (cal == '*') _stk.top()*= num;else if (cal == '/') _stk.top() /= num;num = 0;if (i == s.size()) break;cal = s[i];}if (s[i] >= '0'&&s[i] <= '9')num = num * 10 + (s[i] - '0');}int res = 0;while (!_stk.empty()){res += _stk.top();_stk.pop();}return res;}
};

1.3 、有效的括号 、第K个最大元素

20. 有效的括号

class Solution {public:bool isValid(string s) {stack<char> my_stack;for(auto & it:s){if(it =='{'||it =='['||it =='(')my_stack.push(it);else{switch(it){case('}'): if(my_stack.empty()||my_stack.top()!='{')  return false;my_stack.pop(); break;case(']'): if(my_stack.empty()||my_stack.top()!='[')  return false;my_stack.pop(); break;case(')'): if(my_stack.empty()||my_stack.top()!='(')  return false;my_stack.pop(); break;default :return false;}}}return !my_stack.empty()? false:true;}
};

215. 数组中的第K个最大元素

class Solution {public:int findKthLargest(vector<int>& nums, int k) {auto fc = [](int a, int b) {return a > b; };priority_queue<int, vector<int>,decltype(fc) > que(fc);//priority_queue<int, vector<int>,greater<int> > que;for (int i = 0; i < nums.size(); i++){if (que.size() < k)que.push(nums[i]);else if (que.top() < nums[i]){que.pop();que.push(nums[i]);}}return que.top();}};
  • 快排 partition
class Solution {public:int partition(vector<int>& nums, int k,int _l,int _r){int temp = nums[_l];int i=_l+1,j=_r;while(i<=j){while(i<=j&&nums[i]>temp)i++;while(i<=j&&nums[j]<temp)j--;if(i>j) break;swap(nums[i],nums[j]);i++;j--;}swap(nums[_l],nums[j]);return j;}void qs(vector<int>& nums, int k,int _l,int _r){if(_l>_r) return;int mid = partition(nums,k,_l,_r);if(mid==k) return;else if(mid>k) qs(nums,k,_l,mid-1);else qs(nums,k,mid+1,_r);}int findKthLargest(vector<int>& nums, int k) {qs(nums,k-1,0,nums.size()-1);return nums[k-1];}
};


1.4 、【单调栈】接雨水、每日温度、柱状图中最大的矩形
  • 判别是否需要使用单调栈,如果需要找到左边或者右边第一个比当前位置的数大或者小,则可以考虑使用单调栈
  • “在一维数组中找第一个满足某种条件的数”的场景就是典型的单调栈应用场景。

42. 接雨水

  • 两个dp 作为从两边来的最大能容纳的水
class Solution {public:int trap(vector<int>& height) {vector<int> _h1(height.size(),0);vector<int> _h2(height.size(),0);for(int i=1,temp_h = height[0];i<height.size()-1;i++){temp_h = max(temp_h,height[i]);_h1[i] = temp_h-height[i];}for(int i=height.size()-2,temp_h = height[height.size()-1];i>0;i--){temp_h = max(temp_h,height[i]);_h2[i] = temp_h-height[i];}int res = 0;for(int i=1;i<height.size()-1;i++)res+=min(_h1[i],_h2[i]);return res;}
};
  • 单调栈实现:从前到后,维护递减栈,遇到更大的数,就说明可能存在与前面的构成凹陷;
class Solution {public:int trap(vector<int>& height) {stack<int> _stk;int res =0;for(int i=0;i<height.size();i++){while(!_stk.empty()&&height[_stk.top()]<height[i]){int index = _stk.top();_stk.pop();if(_stk.empty()) break;int index2 = _stk.top();int _min = min(height[index2],height[i]); // 两边较低的高度res+= (_min - height[index])*(i-index2-1);// 两边较低的高度*宽度}_stk.push(i);}return res;}
};

739. 每日温度

  • 单调栈实现:从后到前,维持严格递减的栈,每个位置都能访问到离他最近的大于他的数;
class Solution {public:vector<int> dailyTemperatures(vector<int>& temperatures) {stack<int > _stk;vector<int>res(temperatures.size(),0);for(int i=temperatures.size()-1;i>=0;i--){       while(!_stk.empty()&&temperatures[i]>=temperatures[_stk.top()])_stk.pop();if(!_stk.empty()&&temperatures[i]<temperatures[_stk.top()]) res[i] = _stk.top()-i;_stk.push(i);}return res;}
};

84. 柱状图中最大的矩形 //****

class Solution {public:int largestRectangleArea(vector<int>& heights) {stack<int > _stk;int res = 0;for(int i=0;i<heights.size();i++){     while(!_stk.empty()&&heights[i]<heights[_stk.top()]){int index  = _stk.top();_stk.pop();int len = i;if(!_stk.empty())  len = i-_stk.top()-1;// 如果是空的,说明这个之前的都比他大;res = max(res,len*heights[index]);}_stk.push(i);}while (!_stk.empty()) {int length = heights[_stk.top()];_stk.pop();int weight = heights.size();if (!_stk.empty()) {weight = heights.size() - _stk.top() - 1;}res = max(res, length * weight);}return res;}
};

七、数组

1.1 、下一个排列、螺旋矩阵

31. 下一个排列

class Solution {public:void nextPermutation(vector<int>& nums) {int i=nums.size()-1;while(i>0){if(nums[i]>nums[i-1]) break;// 找到从后到前第一个不是升序的i-1;i--;}if(i>0){int j=nums.size()-1;while(nums[j]<=nums[i-1])// 找到后面降序中最后一个大于i-1 的数j--;swap(nums[i-1],nums[j]);}reverse(nums.begin()+i,nums.end());}
};

54. 螺旋矩阵 同 剑指 Offer 29. 顺时针打印矩阵

class Solution {public:vector<int> spiralOrder(vector<vector<int>>& matrix) {if(matrix.size() ==0||matrix[0].size() ==0) return {};int len1 =matrix.size(),len2 = matrix.back().size();int len3 =len1*len2;vector<int> res;vector<vector<int>> dir = {{0,1},{1,0},{0,-1},{-1,0}};vector<vector<bool>> usd(len1,vector<bool>(len2,false));int i=0,j =0,dir_inex = 0;while(1){res.push_back(matrix[i][j]);usd[i][j] = true;if(len3 == res.size()) break;int new_i = i+dir[dir_inex][0];int new_j = j+dir[dir_inex][1];while(new_i<0||new_j<0||new_i>=len1||new_j>=len2||usd[new_i][new_j]){dir_inex= (dir_inex+1)%4;new_i = i+dir[dir_inex][0];new_j = j+dir[dir_inex][1];}i = new_i;j = new_j;}return res;}
};
1.2、缺失的第一个正数、合并区间

41. 缺失的第一个正数

  • 普通排个序、选后面的,做法没法实现O(N) O(1);
  1. 打标记:还可以用哈希映射,用来标记哪些位置有,再遍历的时候能看出来哪些位置缺失了;
  2. 置换:该在某个位置的数交换在位置上去,然后再遍历看位置上哪些地方不是改在的地方;就输出这个位置即可;
class Solution {public:int firstMissingPositive(vector<int>& nums) {int len=nums.size();for(int i=0;i<len;i++)while(nums[i]>0&&nums[i]<=len&&nums[nums[i]-1]!=nums[i])swap(nums[i],nums[nums[i]-1]);for(int i=0;i<len;i++)if(nums[i]!=i+1) return i+1;return len+1;}
};

56. 合并区间

  • 排序完,根据压入的最近的,更新其即可
class Solution {public:vector<vector<int>> merge(vector<vector<int>>& intervals) {sort(intervals.begin(),intervals.end());vector<vector<int>> res;for(int i=0;i<intervals.size();i++){int _l = intervals[i][0],_r = intervals[i][1];if(res.empty()||res.back()[1]<_l)res.push_back(intervals[i]);elseres.back()[1] = max(res.back()[1],_r);}return res;}
};



十、递归和回溯

  • for循环横向遍历;递归纵向遍历;回溯不断调整结果集;

  • 去重或者求和剑指;都需要先排序

  • 子集问题是收集所有的节点,排列、组合与分割问题为收集树的叶子节点

回溯 解决问题
组合问题: N个数⾥⾯按⼀定规则找出k个数的集合
排列问题: N个数按⼀定规则全排列,有⼏种排列⽅式
切割问题: ⼀个字符串按⼀定规则有⼏种切割⽅式
⼦集问题: ⼀个N个数的集合⾥有多少符合条件的⼦集
棋盘问题: N皇后,解数独等等

1、组合、分割 、子集

1.1 、组合 - 组合、组合求和
  • 组合问题时间复杂度O(n*2^n);空间复杂度O(n)

  • 组合总和Ⅳ 是动规题,得dfs+记忆化才能过;否则时间超了

39. 组合总和 //数组中元素可以重复

class Solution {public:vector<vector<int>> res;vector<int> temp;void dfs(vector<int>& candidates, int target,int index){if(target<0) return;if(target ==0){res.push_back(temp);return;}for(int i=index ;i<candidates.size();i++){temp.push_back(candidates[i]);dfs(candidates,target-candidates[i],i);temp.pop_back();}}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {dfs(candidates,target,0);return res;}
};

40. 组合总和 II //给的数组中有重复数组

  • 去重可以一维bool标记用过的,上一层相同的没用,这层也不能用,因为会是上面一层用了的实现的子集,会造成重复;
  • 或者直接 i > index来去重;直接i>0去重的话,会把同层和同枝的都去掉,我们只需要去掉同层相等的;
  • 关于i > index 后面子集二讲的更清楚;
class Solution {public:vector<vector<int>> res;vector<int> temp;vector<bool> _hash;void dfs(vector<int>& candidates, int target, int index){if (target < 0) return;if (target == 0){res.push_back(temp);return;}for (int i = index; i < candidates.size(); i++){if (i > index && candidates[i] == candidates[i - 1] ) continue;temp.push_back(candidates[i]);dfs(candidates, target - candidates[i], i + 1);temp.pop_back();}}vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {sort(candidates.begin(), candidates.end());_hash = vector<bool>(candidates.size(), false);dfs(candidates, target, 0);return res;}
};

216. 组合总和 III 只要求组合中不存在重复数字;

class Solution {public:vector<vector<int>> res;vector<int> temp;void dfs(int k ,int n,int index ){if(n<0) return;if(k ==0){if(n ==0)res.push_back(temp);return ;}for(int i=index;i<=9;i++){temp.push_back(i);dfs(k-1,n-i,i+1);temp.pop_back();}}vector<vector<int>> combinationSum3(int k, int n) {dfs(k,n,1);return res;}
};

377. 组合总和 Ⅳ

  • 这题其实是排列题;且需要动规思想做,得dfs+记忆化才能过;否则时间超了
class Solution {public:vector<int> memo;int dfs(vector<int>& nums, int target,int index){if(target ==0) return 1;if(target<0) return 0;if(memo[target] != -1) return memo[target];int temp = 0;for(int i =0;i<nums.size();i++)temp+=dfs(nums,target-nums[i],index);memo[target] = temp;return temp;}int combinationSum4(vector<int>& nums, int target) {memo = vector<int>(target+1,-1);return dfs(nums,target,0);}
};
  • 完全背包问题;dp来做;但是如果要求结果,还是得回溯,动规求不得实际结果;
class Solution {public:int combinationSum4(vector<int>& nums, int target) {vector<long>dp(target+1,0);dp[0] = 1;for(int i=1;i<=target;i++)for(int j=0;j<nums.size();j++)if(i>=nums[j]&& dp[i] <INT_MAX-dp[i-nums[j]])dp[i] +=dp[i-nums[j]];return dp.back();}
};
1.2 、组合 - 二进制手表、号码的字母组合

401. 二进制手表

  • dfs 试探+回溯
class Solution {public:vector<string> res;void cal(int l_num,int r_num){if(l_num>11||r_num>59) return;string temp = to_string(l_num);temp+=':';if(r_num<10) temp+='0';temp+=to_string(r_num);res.push_back(temp);}void dfs(int turnedOn,int index,int l_num,int r_num){if(turnedOn ==0){cal(l_num,r_num);return;}for(int i=index;i<10;i++)if(i<6)dfs(turnedOn-1,i+1,l_num,r_num+pow(2,i));elsedfs(turnedOn-1,i+1,l_num+pow(2,i-6),r_num);}vector<string> readBinaryWatch(int turnedOn) {dfs(turnedOn,0,0,0);return res;}
};

17. 电话号码的字母组合

class Solution {public:vector<vector<char>> map;vector<string> res;string temp;void dfs(string& digits,int index){if(index==digits.size()) {if(temp!="")res.push_back(temp);return;}vector<char> & _mm = map[digits[index]-'2'];for(int i=0;i<_mm.size();i++){temp.push_back(_mm[i]);dfs(digits,index+1);temp.pop_back();}}vector<string> letterCombinations(string digits) {map = { {'a','b','c'},{'d','e','f'},{'g','h','i'},{'j','k','l'},{'m','n','o'},{'p','q','r','s'},{'t','u','v'},{'w','x','y','z'} };dfs(digits, 0);return res;}
};
1.3 、分割 - 分割回文串、复原 IP

这类问题也是组合问题的分支;

131. 分割回文串

  • 回溯即可,可以在判断回文地方加上记忆化;
  • 难点在于:模拟切割位置,分割成子串后:问题抽象为组合问题;
class Solution {public:vector<vector<string>> res;vector<string> res_temp;bool _cal(string& s2){string s1(s2.rbegin(),s2.rend());return s1==s2;}void dfs(string &s,int index){if(index ==s.size()){res.push_back(res_temp);return;}for(int len=1;len<=s.size()-index;len++){string temp = s.substr(index,len);if(_cal(temp)){res_temp.push_back(temp);dfs(s,index+len);res_temp.pop_back(); //回溯}}}vector<vector<string>> partition(string s) {dfs(s,0);return res;}
};

93. 复原 IP 地址

  • 向后走的时候选择可能的选择;然后提前剪枝
class Solution {public:vector<string> res;void dfs(string& s,int index,int cnt,string temp){if(cnt ==4&&index ==s.size()){res.push_back(temp.substr(0,temp.size()-1));return ;}if(index>=s.size()||cnt>=4||s.size()-index>(4-cnt)*3) return;if(s[index] =='0')dfs(s,index+1,cnt+1,temp+"0.");elsefor(int i=1;i<=3;i++){string temp_str = s.substr(index,i);int nums = stoi(temp_str);if(nums>0&&nums<=255)dfs(s,index+i,cnt+1,temp+ temp_str+".");}}vector<string> restoreIpAddresses(string s) {dfs(s,0,0,"");return res;}
};

140. 单词拆分 II 回溯搜索

class Solution {public:vector<string> res;unordered_set<string> _hash;void dfs(string &s, int l, int r,string _s){if (r >= s.size())return;string temp = s.substr(l, r - l + 1);if (_hash.find(temp) != _hash.end()){if (r == s.size() - 1)res.push_back(_s+temp);dfs(s, r + 1, r + 1,_s+temp+" ");}dfs(s, l, r + 1,_s);}vector<string> wordBreak(string s, vector<string>& wordDict) {_hash = unordered_set<string>(wordDict.begin(), wordDict.end());dfs(s, 0, 0,"");return res;}
};
1.4 、子集 - 子集、递增子序列;- 组合问题同层去重

子集问题时间复杂度O(n*2^n);空间复杂度O(n);子集问题是组合问题的所有节点;

78. 子集 不涉及去重问题;

class Solution {public:vector<vector<int>>  res;vector<int> candidate;void dfs(vector<int>& nums,int index){res.push_back(candidate);for(int i=index;i<nums.size();i++){candidate.push_back(nums[i]);dfs(nums,i+1);candidate.pop_back();}}vector<vector<int>> subsets(vector<int>& nums) {dfs(nums,0);return res;}
};

90. 子集 II //组合问题的同层去重

  • 树的同层去重 ;需要排序;去重也可以用哈希,像递增子序列一样;
  • 同层去重,只需要判断当前节点和之前节点相同,并且当前不是这个递归的第一个元素,就不递归这个元素;即同层不能选取相同元素;
  • 保证分支中每层没有重复的; i>index 十分重要;
  • 这个分支的当前层中除了index 位置,因为nums[i] == nums[i-1]已经保证了没有重复的;但是第一个有可能因为index 之前的重复的也被舍去;
  • 所以与上 i>index 加强限制,防止一些需要的分支被剪掉;也即只剪掉分支中同层重复;
class Solution {public:vector<vector<int>> res;vector<int> candidate;void dfs(vector<int>& nums,int index){res.push_back(candidate);for(int i=index;i<nums.size();i++){if(i>index&& nums[i] == nums[i-1]) continue;candidate.push_back(nums[i]);dfs(nums,i+1);candidate.pop_back();}}vector<vector<int>> subsetsWithDup(vector<int>& nums) {sort(nums.begin(),nums.end());dfs(nums,0);return res;}
};

491. 递增子序列 像子集 II;同层去重,不能排序,所以用哈希去重

  • 一个树枝中,同层上使⽤过的元素就不能再用了;
  • 且树中保存的值大于等于2 的节点都要保存;
class Solution {public:vector<vector<int>> res;vector<int> candidate;void dfs(vector<int>& nums,int index){if(candidate.size()>1)res.push_back(candidate);unordered_set<int> _hash;for(int i=index;i<nums.size();i++){if(_hash.find(nums[i]) != _hash.end()) continue;if(candidate.empty()||nums[i]>=candidate.back()){_hash.insert(nums[i]);candidate.push_back(nums[i]);dfs(nums,i+1);candidate.pop_back();}}}vector<vector<int>> findSubsequences(vector<int>& nums) {dfs(nums,0);return res;}
};

2、排列 - 排列问题 - 同层去重

排列问题问题时间复杂度O(n!);空间复杂度O(n)

1.1 、全排列

46. 全排列 //无重复,只需要同枝去重;

class Solution {public:vector<vector<int>> res;vector<int> candidate;unordered_set<int> _hash;void dfs(vector<int>& nums){if(candidate.size() ==nums.size()){res.push_back(candidate);return;}for(int i=0;i<nums.size();i++){if(_hash.find(nums[i])==_hash.end()){candidate.push_back(nums[i]);_hash.insert(nums[i]);dfs(nums);_hash.erase(nums[i]);candidate.pop_back();}}}vector<vector<int>> permute(vector<int>& nums) {dfs(nums);return res;}
};

47. 全排列 II 带重复,同枝去重+同层去重 ; //排列问题的同层去重;

  • 两个哈希一个做同层,一个做同枝
class Solution {public:vector<vector<int>> res;vector<int> candidate;bool _hash[10] = {0};void dfs(vector<int>& nums){if(candidate.size() ==nums.size()){res.push_back(candidate);return;}bool _hash2[22] = {0};for(int i=0;i<nums.size();i++){if(!_hash[i]&&!_hash2[nums[i]+10]){candidate.push_back(nums[i]);_hash[i] = true;_hash2[nums[i]+10] = true;dfs(nums);_hash[i] = false;candidate.pop_back();}}}vector<vector<int>> permuteUnique(vector<int>& nums) {dfs(nums);return res;}
};
  • 排序+同枝去重+同层去重
class Solution {public:vector<vector<int>> res;vector<int> candidate;bool _hash[10] = {0};void dfs(vector<int>& nums){if(candidate.size() ==nums.size()){res.push_back(candidate);return;}for(int i=0;i<nums.size();i++){if(_hash[i]||(i>0&&nums[i]==nums[i-1]&&!_hash[i-1])) continue;candidate.push_back(nums[i]);_hash[i] = true;dfs(nums);_hash[i] = false;candidate.pop_back();}}vector<vector<int>> permuteUnique(vector<int>& nums) {sort(nums.begin(),nums.end());dfs(nums);return res;}
};
1.2 、安排行程 // 欧拉图;

dfs+删边

332. 重新安排行程

  • 注意的是,有可能A-B的有好几张票;也就是同样的票有好几个;这样就必须记数量;
class Solution {public:int cnt_tickets;int cnt =1;vector<bool> _used;// unordered_map<string,vector<pair<string,int>>> m_hash;//base -  destination - indexunordered_map<string,map<string,int>> m_hash;vector<string> temp;bool dfs(string from){if(cnt ==cnt_tickets+1) return true;for(auto &[_to,_cnt]:m_hash[from]){if(_cnt<=0) continue;_cnt--;temp[cnt] = _to;cnt++;if(dfs(_to)) return true;cnt--;_cnt++;}return false;}vector<string> findItinerary(vector<vector<string>>& tickets) {cnt_tickets = tickets.size();_used = vector<bool>(cnt_tickets,false);temp = vector<string>(cnt_tickets+1);for(int i=0;i<cnt_tickets;i++)m_hash[tickets[i][0]][tickets[i][1]]++;temp[0] = "JFK";dfs("JFK");return  temp;}
};

3、flood fill ;填充;

1.1、岛屿数量、岛屿的最大面积


200. 岛屿数量

  • 最好别用沉岛的方式,别改原数组;
class Solution {vector<vector<bool>> _used;bool dfs(vector<vector<char>>& grid,int x,int y){if(x<0||y<0||x>=grid.size()||y>=grid.back().size()) return 0;if(_used[x][y]) return 0;if(grid[x][y] =='0')return 0;_used[x][y] = true;dfs(grid,x+1,y);dfs(grid,x,y+1);dfs(grid,x,y-1);dfs(grid,x-1,y);return true;}
public:int numIslands(vector<vector<char>>& grid) {_used = vector<vector<bool>> (grid.size(),vector<bool>(grid.back().size(),false));int res = 0;for(int i=0;i<grid.size();i++)for(int j=0;j<grid.back().size();j++)if(dfs(grid,i,j))res++;return res;}
};

695. 岛屿的最大面积

  • 回溯的对周围fill起来;
  • used 不需要回溯的返回false; 因为二维有可能绕一圈走老路;
class Solution {vector<vector<bool>> _used;int dfs(vector<vector<int>>& grid,int x,int y){if(x<0||y<0||x>=grid.size()||y>=grid.back().size()) return 0;if(_used[x][y]) return 0;if(grid[x][y] ==0 )return 0;_used[x][y] = true;int temp =  1+ dfs(grid,x+1,y) + dfs(grid,x,y+1)+dfs(grid,x,y-1) + dfs(grid,x-1,y);return temp;}public:int maxAreaOfIsland(vector<vector<int>>& grid) {_used = vector<vector<bool>> (grid.size(),vector<bool>(grid.back().size(),false));int _max = 0;for(int i=0;i<grid.size();i++)for(int j=0;j<grid.back().size();j++)_max = max(_max,dfs(grid,i,j));return _max;}
};
1.2、被围绕的区域、太平洋大西洋水流问题

  • 都是需要反向从外到内的flood fill;

130. 被围绕的区域

class Solution {public:vector<vector<bool>> _used;int len,len2;void dfs(vector<vector<char>>& board,int x,int y){if(x<0||y<0||x>=len||y>=len2) return;if(_used[x][y]) return;if(board[x][y] == 'X') return;_used[x][y] = true;dfs(board,x+1,y);dfs(board,x,y+1);dfs(board,x,y-1);dfs(board,x-1,y);}void solve(vector<vector<char>>& board) {len = board.size();len2 = board.back().size();_used = vector<vector<bool>>(len,vector<bool>(len2,false));for(int i=0;i<len;i++){dfs(board,i,0);dfs(board,i,len2-1);}for(int i=0;i<len2;i++){dfs(board,0,i);dfs(board,len-1,i);}for(int i=0;i<len;i++)for(int j=0;j<len2;j++)if(board[i][j] =='O'&&!_used[i][j])board[i][j] = 'X';}
};

417. 太平洋大西洋水流问题

class Solution {public:int len,len2;void dfs(vector<vector<int>>& heights,int x,int y,vector<vector<bool>>& used){if(used[x][y]) return;used[x][y] =true;if(x>0&&heights[x-1][y]>=heights[x][y])dfs(heights,x-1,y,used);if(y>0&&heights[x][y-1]>=heights[x][y])dfs(heights,x,y-1,used);if(x<len-1&&heights[x+1][y]>=heights[x][y])dfs(heights,x+1,y,used);if(y<len2-1&&heights[x][y+1]>=heights[x][y])dfs(heights,x,y+1,used);}vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {len = heights.size();if(len<1) return {};len2 = heights.back().size();vector<vector<bool>> used1 = vector<vector<bool>>(len,vector<bool>(len2,false));vector<vector<bool>> used2 = vector<vector<bool>>(len,vector<bool>(len2,false));for(int i=0;i<len;i++){dfs(heights,i,0,used1);dfs(heights,i,len2-1,used2);}for(int i=0;i<len2;i++){dfs(heights,0,i,used1);dfs(heights,len-1,i,used2);}vector<vector<int>> res;for(int i=0;i<len;i++)for(int j=0;j<len2;j++)if(used1[i][j]&&used2[i][j])res.push_back({i,j});return res;}
};

4、搜索

1.1、单词搜索

79. 单词搜索

  • 每个位置都回溯;先污染再整治代码比较好写;
class Solution {public:vector<vector<bool>> _used;bool dfs(vector<vector<char>>& board, string& word,int x,int y,int index){if(x<0||y<0||x>=board.size()||y>=board.back().size()) return false;if(board[x][y]!=word[index]) return false;if(_used[x][y]) return false;if(index == word.size()-1) return true;_used[x][y] = true;bool _temp = dfs(board,word,x+1,y,index+1)||dfs(board,word,x,y+1,index+1)||dfs(board,word,x-1,y,index+1)||dfs(board,word,x,y-1,index+1);_used[x][y] = false;return _temp;}bool exist(vector<vector<char>>& board, string word) {_used = vector<vector<bool>>(board.size(),vector<bool>(board.back().size(),false));for(int i=0;i<board.size();i++)for(int j=0;j<board.back().size();j++)if(dfs(board,word,i,j,0))return true;return false;}
};

212. 单词搜索 II

  • 相较单词搜索,只需在其能找到的时候,把要找的这个单词压入即可;
  • 针对测试用例,需要与处理一下单词中不存在的char,排除一些不可能搜得到的;

class Solution {public:vector<vector<bool>> _used;vector<string> res;string temp;bool dfs(vector<vector<char>>& board, string& word,int x,int y,int index){if(x<0||y<0||x>=board.size()||y>=board.back().size()) return false;if(board[x][y]!=word[index]) return false;if(_used[x][y]) return false;if(index == word.size()-1) return true;_used[x][y] = true;bool _temp = dfs(board,word,x+1,y,index+1)||dfs(board,word,x,y+1,index+1)||dfs(board,word,x-1,y,index+1)||dfs(board,word,x,y-1,index+1);_used[x][y] = false;return _temp;}vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {_used = vector<vector<bool>>(board.size(),vector<bool>(board.back().size(),false));unordered_set<char> _hash_t;vector<bool> _has = vector<bool>(words.size(),false);for(auto vec:board)for(auto it:vec)_hash_t.insert(it);for(int k=0;k<words.size();k++)for(auto it:words[k])if(_hash_t.find(it)==_hash_t.end())_has[k] = true;for(int k=0;k<words.size();k++){if(_has[k])continue;bool _b = false;for(int i=0;i<board.size();i++){for(int j=0;j<board.back().size();j++)if(dfs(board,words[k],i,j,0)){res.push_back(words[k]);_b = true;break;}if(_b)break;}}return res;}
};
1.2、八皇后、解数独;


面试题 08.12. 八皇后

  • 难点在于剪枝的判断; 即每行、每列、每对角线的_hash记录;
  • 递归遍历树枝,for循环遍历树枝的每层;即递归遍历棋盘深度,for循环遍历棋盘宽度;
class Solution {public:vector<vector<string>> res;vector<string> candidate;// vector<bool> _hash_row;// -vector<bool> _hash_col;// |vector<bool> _hash_l;// /vector<bool> _hash_r;// \void dfs(const int n,int index){if(index ==n){res.push_back(candidate);return ;}for(int i=0;i<n;i++){if(_hash_col[i]||_hash_l[i+index]||_hash_r[i-index+n-1])continue;_hash_col[i] = true;_hash_l[i+index] = true;_hash_r[i-index+n-1] = true;candidate[index][i] = 'Q';dfs(n,index+1);candidate[index][i] = '.';_hash_col[i] = false;_hash_l[i+index] = false;_hash_r[i-index+n-1] = false;}}vector<vector<string>> solveNQueens(int n) {candidate =vector<string>(n,string(n,'.'));// _hash_row = vector<bool>(n,false);_hash_col = vector<bool>(n,false);_hash_l = vector<bool>(2*n-1,false);_hash_r = vector<bool>(2*n-1,false);dfs(n,0);return res;}
};

37. 解数独

  • 回溯的尝试每个地方的数字而已;
  • for循环控制每个空位填的数字,也就是递归树的层,递归控制深度;
class Solution {int _hash_row[9] = { 0 };int _hash_col[9] = { 0 };int _hash_block[9] = { 0 };int cnt = 0;int _which(int x, int y){if (x < 3){if (y < 3)return 0;else if (y < 6)return 3;elsereturn 6;}else if (x < 6){if (y < 3)return 1;else if (y < 6)return 4;elsereturn 7;}else{if (y < 3)return 2;else if (y < 6)return 5;elsereturn 8;}}bool dfs(vector<vector<char>>& board, int x, int y, int m){if (_hash_row[x] & (1 << m)) return false;if (_hash_col[y] & (1 << m)) return false;if (_hash_block[_which(x, y)] & (1 << m)) return false;// cout << x << " : " << y << " " << m << " cnt: " << cnt << endl;board[x][y] = m + '0';cnt--;_hash_row[x] |= (1 << m);_hash_col[y] |= (1 << m);_hash_block[_which(x, y)] |= (1 << m);if (cnt == 0) return true;bool _b = false;for (int i = x; i < 9; i++){for (int j = 0; j < 9; j++)if (board[i][j] == '.'){for (int k = 1; k < 10; k++)if (dfs(board, i, j, k))return true;_b = true;break;}if (_b) break;}board[x][y] = '.';cnt++;_hash_row[x] &= ~(1 << m);_hash_col[y] &= ~(1 << m);_hash_block[_which(x, y)] &= ~(1 << m);return false;}public:void solveSudoku(vector<vector<char>>& board) {for (int i = 0; i < 9; i++)for (int j = 0; j < 9; j++){if (board[i][j] == '.'){cnt++;continue;}int temp = board[i][j] - '0';_hash_row[i] |= (1 << temp);_hash_col[j] |= (1 << temp);_hash_block[_which(i, j)] |= (1 << temp);}bool _b = false;for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++)if (board[i][j] == '.'){for (int k = 1; k < 10; k++)if (dfs(board, i, j, k))break;break;}if (_b) break;}}
};

十一、动态规划

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

1、基础DP

1.1、爬楼梯及其深化、不同路径 I 、 II、圆环走回原点问题、约瑟夫环

70. 爬楼梯

class Solution {public:int climbStairs(int n) {if(n<3) return n;int pre = 1;int cur = 2;for(int i=3;i<=n;i++){int temp = pre+cur;pre = cur;cur = temp;}return cur;}
};
  • 如果有说,一次可以爬1~m个阶梯,那么能有多少种爬法;(完全背包) //排列问题;
class Solution {public:int climbStairs(int n,int m) {if(n<3) return n;vector<int>dp(n+1,0);dp[1] = 1;dp[2] = 2;for(int i=3;i<=n;i++)for(int j=1;j<=m;j++)if(i>j) dp[i] +=dp[i-j];return dp.back();}
};

746. 使用最小花费爬楼梯

class Solution {public:int minCostClimbingStairs(vector<int>& cost) {vector<int> dp(cost);for(int i=2;i<cost.size();i++)dp[i] += min(dp[i-1],dp[i-2]);return  min(dp.back(),dp[dp.size()-2]);}
};

62. 不同路径 / 63. 不同路径 II

class Solution {public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m,vector<int>(n,1));for(int i=1;i<m;i++)for(int j=1;j<n;j++)dp[i][j] = dp[i-1][j]+dp[i][j-1];return dp.back().back();}
};
class Solution {public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m=obstacleGrid.size(),n=obstacleGrid.back().size();vector<vector<int>> dp(m,vector<int>(n,0));dp[0][0]= obstacleGrid[0][0] == 1 ? 0:1;for(int i=0;i<m;i++)for(int j=0;j<n;j++)if(i==0&&j==0) continue;else if(i==0){if(obstacleGrid[i][j]!=1)dp[i][j] = dp[i][j-1];}else if(j==0&&obstacleGrid[i][j]!=1){if(obstacleGrid[i][j]!=1)dp[i][j] = dp[i-1][j];}else if(obstacleGrid[i][j]!=1)dp[i][j] = dp[i-1][j]+dp[i][j-1];return dp.back().back();}
};

圆环回原点问题

  • 走n步到0的方案数=走n-1步到1的方案数+走n-1步到9的方案数。
  • 因此,若设dp[i][j]为从0点出发走i步到j点的方案数
int backToOrigin(int len, int n) {vector<vector<int>> dp(n + 1, vector<int>(len, 0));//走n步,到len点的方案数;dp[0][0] = 1;for (int i = 1; i <= n; i++)for (int j = 0; j < len; j++)dp[i][j] = dp[i - 1][(j - 1 + len) % len] + dp[i - 1][(j + 1) % len];return    dp[n][0];
}
void  main()
{cout<<backToOrigin(9, 2);
}

剑指 Offer 62. 圆圈中最后剩下的数字
*

class Solution {public:int lastRemaining(int n, int m) {vector<int> dp(n+1,0);for(int i=1;i<=n;i++){dp[i] = (dp[i-1]+m)%i;//cout<<dp[i]<<endl;}return dp.back();}
};
1.2、剪绳子、不同的二叉搜索树

343. 整数拆分 / 剑指offer 剪绳子

  • 还可以每次贪心的拆 3
class Solution {public:int integerBreak(int n) {if(n<4) return n-1;vector<int> dp(n+1,0);dp[2] = 1;dp[3] = 2;for(int i=4;i<n+1;i++)for(int j=2;j<i ;j++)dp[i]=max(dp[i],max(j*dp[i-j],j*(i-j)));return dp.back();}
};

96. 不同的二叉搜索树

class Solution {public:int numTrees(int n) {vector<int> dp(n+1,0);dp[0] = 1;dp[1] = 1;for(int i=2;i<n+1;i++)for(int j=0;j<i;j++)dp[i]+= dp[j]*dp[i-j-1];return dp.back();}
};
1.3、打家劫舍 I / II / III

198. 打家劫舍

class Solution {public:int rob(vector<int>& nums) {if(nums.size()<2) return nums.back();vector<int> dp(nums.size());dp[0] = nums[0];dp[1] = max(nums[0],nums[1]);for(int i=2;i<nums.size();i++)dp[i] = max(dp[i-1],dp[i-2]+nums[i]);return dp.back();}
};

213. 打家劫舍 II

  • 变成考虑打劫,两种情况,一种考虑打前n-1家,另一种考虑打后n-1家;
class Solution {public:int rob(vector<int>& nums) {if(nums.size()==1) return nums.back();if(nums.size()==2) return max(nums[0],nums[1]);vector<int> dp(nums.size());dp[0] = nums[0];dp[1] = max(nums[0],nums[1]);for(int i=2;i<nums.size()-1;i++)dp[i] = max(dp[i-1],dp[i-2]+nums[i]);int res1 = dp[nums.size()-2];dp[1] = nums[1];dp[2] = max(nums[2],nums[1]);for(int i=3;i<nums.size();i++)dp[i] = max(dp[i-1],dp[i-2]+nums[i]);return max(res1,dp.back());}
};

337. 打家劫舍 III // ***

  • 记忆化dfs
class Solution {public:unordered_map<TreeNode*,pair<int,int>> _hash;int dfs(TreeNode* root,bool _rob){if(!root) return 0;if(_hash.find(root)!=_hash.end()) {if(!_rob&&_hash[root].first!=-1)return _hash[root].first;else if(_rob&&_hash[root].second!=-1)return _hash[root].second;}int temp =0;_hash[root] = {-1,-1};if(_rob){temp = dfs(root->left,false)+dfs(root->right,false);_hash[root].second = temp;}else{temp =  max(root->val+dfs(root->left,true)+dfs(root->right,true),dfs(root->left,false)+dfs(root->right,false));_hash[root].first = temp;}return temp;}int rob(TreeNode* root) {return dfs(root,false);}
};
  • 树形hash dp //两个dp版本,比我自己做的一个dp的代码少
class Solution {public:unordered_map <TreeNode*, int> f, g;void dfs(TreeNode* node) {if (!node) {return;}dfs(node->left);dfs(node->right);f[node] = node->val + g[node->left] + g[node->right];g[node] = max(f[node->left], g[node->left]) + max(f[node->right], g[node->right]);}int rob(TreeNode* root) {dfs(root);return max(f[root], g[root]);}
};
class Solution {public:unordered_map<TreeNode*,int> _hash;void dfs(TreeNode* root){if(!root) return ;dfs(root->left);dfs(root->right);int temp =0;if(root->left&&root->right){int _new = root->val;if(root->right->left)_new+= _hash[root->right->left];if(root->right->right)_new+= _hash[root->right->right];if(root->left->left)_new+= _hash[root->left->left];if(root->left->right)_new+= _hash[root->left->right];_hash[root] = max(_new,_hash[root->right]+_hash[root->left]);}else if(root->right){int _new = root->val;if(root->right->left)  _new+= _hash[root->right->left];if(root->right->right)  _new+= _hash[root->right->right];_hash[root] = max(_new,_hash[root->right]);}else if(root->left){int _new = root->val;if(root->left->left) _new+= _hash[root->left->left]; if(root->left->right) _new+= _hash[root->left->right];_hash[root] = max(_new,_hash[root->left]);}else_hash[root] =root->val;}int rob(TreeNode* root) {dfs(root);return _hash[root];}
};
  • 树形dp 压缩空间
class Solution {public:pair<int, int> try_rob(TreeNode* root)//first 偷了当前节点,second 没偷{if (!root) return {0,0};pair<int, int> temp_left = try_rob(root->left);pair<int, int> temp_right = try_rob(root->right);int temp1 = root->val;temp1 += temp_left.second + temp_right.second;int temp2 = max(temp_left.first,temp_left.second)+max(temp_right.first,temp_right.second);return  make_pair(temp1, temp2);}int rob(TreeNode* root) {pair<int, int> temp = try_rob(root);return max(temp.first, temp.second);}
};
1.4、三角形最小路径和、摆动序列 、解码方法

120. 三角形最小路径和

class Solution {public:int minimumTotal(vector<vector<int>>& triangle) {vector<int> temp(triangle.back());for(int i=triangle.size()-2;i>=0;i--)for(int j=0;j<triangle[i].size();j++)temp[j] = min(temp[j],temp[j+1])+triangle[i][j];return temp[0];}
};

376. 摆动序列

class Solution {public:int wiggleMaxLength(vector<int>& nums) {vector<pair<int,int>> dp(nums.size(),{1,1});for(int i=1;i<nums.size();i++){if(nums[i]>nums[i-1])dp[i] = {dp[i-1].second+1,dp[i-1].second};else if(nums[i]<nums[i-1])dp[i] = {dp[i-1].first,dp[i-1].first+1};else dp[i]  =dp[i-1];}return max(dp.back().second,dp.back().first);}
};

91. 解码方法

class Solution {public:int numDecodings(string s) {if (s.size() == 0 || s[0] == '0')return 0;vector<int> dp(s.size() + 1, 0);dp[0] = 1;for (int i = 1; i <= s.size(); i++){if (s[i - 1] != '0')dp[i] += dp[i - 1];if (i > 1 && s[i - 2]!='0'&&(s[i - 2] - '0') * 10 + s[i - 1] - '0' <= 26)dp[i] += dp[i - 2];}return dp.back();}
};
1.5、最大正方形、最长有效括号、分割数组的最大值

221. 最大正方形

class Solution {public:int maximalSquare(vector<vector<char>>& matrix) {int len1 = matrix.size(),len2 = matrix[0].size();vector<vector<int>> dp(len1,vector<int>(len2,0));int res=0;for(int i=0;i<len1;i++)for(int j=0;j<len2;j++){if(matrix[i][j] == '1')if(i ==0||j ==0)dp[i][j] = 1;elsedp[i][j]  =min(min(dp[i][j-1],dp[i-1][j]),dp[i-1][j-1])+1;res = max(res,dp[i][j]);}return res*res;}
};

32. 最长有效括号

  • 和最近的(匹配的话,就是2 ;否则跳过前面匹配好了的,看能不能匹配,能的话,就按照前面匹配的 +2;
class Solution {public:int longestValidParentheses(string s) {int len = s.size(),res =0;vector<int> dp(len,0);for(int i=1;i<len;i++){if(s[i] ==')'){if(s[i-1] =='('){dp[i] = 2;if(i>1) dp[i]+=dp[i-2];}else if(i - dp[i-1] >0 && s[i - dp[i-1]-1] =='('){   //s[i-1] ==')'dp[i] = dp[i-1]+2;if(i - dp[i-1]>=2) dp[i] += dp[i - dp[i-1]-2];}}res = max(res,dp[i]);}return res;}
};
  • 栈来实现的方法
class Solution {public:int longestValidParentheses(string s) {int maxans = 0;stack<int> stk;stk.push(-1);for (int i = 0; i < s.length(); i++) {if (s[i] == '(') {stk.push(i);} else {stk.pop();if (stk.empty()) {stk.push(i);} else {maxans = max(maxans, i - stk.top());}}}return maxans;}
};

410. 分割数组的最大值 //***

  • dp 表示将数组的前 i 个数分割为 j 段所能得到的最大连续子数组和的最小值;
  • 主要求的就是前面分成 j-1 端后,后面的和前面的相比最大值就是后选值
class Solution {public:int splitArray(vector<int>& nums, int m) {int len =nums.size();vector<int>sum(len+1,0);vector<vector<int>> dp(len+1,vector<int>(m+1,1e9));for(int i=1;i<=len;i++)sum[i] = sum[i-1] + nums[i-1];for(int i=1;i<=len;i++)dp[i][1] = sum[i];for(int i=2;i<=len;i++)for(int j=2;j<=min(i,m);j++)for(int k= 1;k<i;k++)dp[i][j]  = min(dp[i][j],max(dp[k][j-1],sum[i]-sum[k]));return dp.back().back();}
};

2、背包问题

  • 0 - 1 背包问题的暴力解,同样重要,即枚举每个物品取与不取;通过回溯来做;时间复杂度O(2^n);
  • 0-1 背包问题:抽象为,当前物品放与不放;
  1. 放的话最大价值就是这个物品价值value[i] + 这个物品之前且去掉这个物品的容量最大价值 dp[i-1][j-weight[i]]
  2. 不放的话,就是这个容量j 在这个物品之前 i-1 的最大价值 DP[i-1][j]
  • 得到dp 依赖于左上,和正上方数据;
  • 0-1 背包问题:遍历顺序;

经典0 - 1 背包问题的遍历顺序反过来是没事的;

  • 0-1 背包问题,滚动数组: 空间压缩到一维DP;
  • 可以根据递推公式得到,i只依赖于上一层,j则不同,即可将i压缩;
  • 此时的初始化问题;根据递推公式,初始化为0即可;
  • 0-1 背包问题,滚动数组:遍历顺序
  • 内层遍历因为要取上一行的数据,所以需要从后往前遍历;想象一下因为二维dp遍历的时候取的是上一行的数据。
  • 遍历顺序不能反过来,必须内层遍历背包容量,外层遍历物品;因为考虑 内层遍历容量的时候需要倒叙遍历;
  • 完全背包 : 遍历顺序
  • 对于完全背包,空间压缩以后,内层循环得正序,因为完全背包,应该取前面更新过的当前值用过的情况;

一套框架解决「背包问题」

零钱兑换II和爬楼梯问题到底有什么不同?

1.1、0 - 1 背包 - 分割等和子集、最后一块石头的重量、目标和、一和零;

416. 分割等和子集

  • 二维dp:dp[i][j]为容量为j的包能否被0~i个物品装满
class Solution {public:bool canPartition(vector<int>& nums) {int sum = 0;for(auto it:nums)sum+=it;if(sum%2 ==1) return false;sum/=2;vector<vector<bool>> dp(nums.size(),vector<bool>(sum+1,false));for(int i=0;i<nums.size();i++)dp[i][0] = true;if(nums[0] <=sum) dp[0][nums[0]] = true;for(int i=1;i<nums.size();i++)for(int j=1;j<sum+1;j++){if(j>=nums[i])dp[i][j] = dp[i-1][j]||dp[i-1][j-nums[i]];elsedp[i][j] = dp[i-1][j];}return dp.back().back();}
};
  • 一维dp:dp[j]为容量为j的包能否被装满
class Solution {public:bool canPartition(vector<int>& nums) {int sum = 0;for(auto it:nums)sum+=it;if(sum%2 ==1) return false;sum/=2;vector<bool> dp(sum+1,false);dp[0] = true;if(nums[0] <=sum) dp[nums[0]] = true;for(int i=1;i<nums.size();i++){for(int j=sum;j>nums[i];j--)dp[j] = dp[j]||dp[j-nums[i]];if(dp.back() ==true) return true;}return dp.back();}
};

1049. 最后一块石头的重量 II // ***

class Solution {public:int lastStoneWeightII(vector<int>& stones) {int sum = 0;for(auto it:stones) sum+=it;int target = sum/2;vector<vector<int>> dp(stones.size(),vector<int>(target+1,0));for(int j=1;j<=target;j++)if(j>=stones[0]) dp[0][j] = stones[0];for(int i=1;i<stones.size();i++)for(int j=1;j<=target;j++)if(j>=stones[i])dp[i][j] = max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]);elsedp[i][j] = dp[i-1][j];return sum-dp.back().back()-dp.back().back();}
};
class Solution {public:int lastStoneWeightII(vector<int>& stones) {int sum = 0;for(auto it:stones) sum+=it;int target = sum/2;vector<int>dp(target+1,0);for(int i=1;i<stones.size();i++)for(int j=target;j>=stones[i];j--)dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);return  sum-dp.back()-dp.back();}
};

494. 目标和 // ***

  • 我本来的思路就是一直加;
  • 标准思路:nums 中求 (target-sum )/2 ` 的组合;

因为 (sum−neg)−neg=sum−2⋅neg=targetneg = (target-sum )/2

class Solution {public:int findTargetSumWays(vector<int>& nums, int target) {int sum =0;for(int it:nums)sum+=it;if(target>sum||target<-sum) return 0;int bound = 2*sum;vector<vector<int>> dp(nums.size(),vector<int>(bound+1,0));dp[0][nums[0]+sum]++;dp[0][-nums[0]+sum]++;for(int i=1;i<nums.size();i++)for(int j=0;j<bound+1;j++){if(j+nums[i]<=bound)dp[i][j] +=dp[i-1][j+nums[i]];if(j-nums[i]>=0)dp[i][j] +=dp[i-1][j-nums[i]]; }return dp.back()[target+sum];}
};
  • dp[i][j] 表示在数组 nums 的前 i 个数中选取元素,使得这些元素之和等于 j 的方案数
class Solution {public:int findTargetSumWays(vector<int>& nums, int target) {int sum =0;for(int it:nums)sum+=it;if(target>sum||target<-sum||(sum-target)%2==1) return 0;int bound =( sum-target)/2;vector<vector<int>> dp(nums.size()+1,vector<int>(bound+1,0));dp[0][0] =1;for(int i=1;i<=nums.size();i++)for(int j=0;j<bound+1;j++){dp[i][j] =dp[i-1][j];if(j>=nums[i-1])dp[i][j] +=dp[i-1][j-nums[i-1]];}return dp.back().back();}
};

474. 一和零 // ***

  • 本质为两个维度的 0 - 1背包;
  • 三维dp
class Solution {public:int findMaxForm(vector<string>& strs, int m, int n) {vector<pair<int,int>>nums(strs.size());for(int i=0;i<strs.size();i++)for(auto it:strs[i])if(it =='0')nums[i].first++;elsenums[i].second++;vector<vector<vector<int>>>dp (strs.size(),vector<vector<int>>(m+1,vector<int>(n+1,0)));for(int j=0;j<=m;j++)for(int k=0;k<=n;k++)if(j>=nums[0].first&&k>=nums[0].second)dp[0][j][k] = 1;for(int i=1;i<nums.size();i++)for(int j=0;j<=m;j++)for(int k=0;k<=n;k++)if(j>=nums[i].first&&k>=nums[i].second)dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-nums[i].first][k-nums[i].second]+1);else   dp[i][j][k] = dp[i-1][j][k];return dp.back().back().back();}
};
  • 二维dp
class Solution {public:int findMaxForm(vector<string>& strs, int m, int n) {vector<pair<int,int>>nums(strs.size());for(int i=0;i<strs.size();i++)for(auto it:strs[i])if(it =='0')nums[i].first++;elsenums[i].second++;vector<vector<int>>dp =vector<vector<int>>(m+1,vector<int>(n+1,0));for(int j=0;j<=m;j++)for(int k=0;k<=n;k++)if(j>=nums[0].first&&k>=nums[0].second)dp[j][k] = 1;for(int i=1;i<nums.size();i++)for(int j=m;j>=0;j--)for(int k=n;k>=0;k--)if(j>=nums[i].first&&k>=nums[i].second)dp[j][k] = max(dp[j][k],dp[j-nums[i].first][k-nums[i].second]+1);else   dp[j][k] = dp[j][k];return dp.back().back();}
};
1.2、完全背包
  • 这个链接有讲完全背包中求排列和组合的问题;其实状态压缩以后,循环顺序决定了取的是排列还是组合;其中爬楼梯是排列问题;1 2与 2 1 是两个解;而零钱兑换是组合问题 1 2 和2 1 是一个解;
  • 零钱兑换II和爬楼梯问题到底有什么不同?
  • 总结:
  • 二维dp的组合数问题和排列数问题 都可以交换嵌套的循环,因为子问题不会变化;
  • 一维的dp组合数问题和排列数问题 不可以交换嵌套的循环,因为会改变子问题;
  • 一维的dp组合数问题,交换嵌套的循环,子问题会变成排列数问题;
  • 一维的dp排列数问题,交换嵌套的循环,子问题会变成组合数问题;
  • 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
  • 如果求排列数就是外层for遍历背包,内层for循环遍历物品。
  • 组合问题
  • 组合问题:其实是在每一次for coin in coins循环中就把coin的可使用次数规定好了。不允许在后面的硬币层次使用之前的硬币。 这就像排列中2,2,1; 2,1,2是两种情况;
  • 但是组合问题规定好了一种书写顺序,比如大的写在前面那就只有2,2,1这一种情况了。
  • 排列问题:
  • 对每种tar,每个元素都试一下;就做成了排列;
  • 使得dp[J]每次被使用的时候都确定是,对容量J的排列结果;
  • 解决方案:
  1. 据题目所求可以确定是组合数问题还是排列数问题,就可以决定dp表的横纵坐标分别表示什么了,之后无论用二维dp还是一维dp,状态的横纵坐标都是不变的
  2. 二维一维其实是看你的dp数组是一维还是二维决定的
  3. 如果写成了一维,那么两层循环的顺序不能改变----------一维的循环顺序(只能一行一行求解)为什么不能改变呢?因为如果改变循环顺序的话,变成了一列一列求,当求到第一列最后一行时,第一列前几行的数据已经被覆盖,当求第二列时,得到的数据是错的 ;
  4. 如果写成了二维,那么两层循环的熟悉怒可以改变--------循环的顺序改变其实改变的的是一列一列求还是一行一行求,所以顺序是可以改变的(因为二维数组可以保存每一列和每一行的数据,但是一维的dp只能保存一行一行的保存数据)
1.2.1、组合问题 - 零钱兑换 II ; 排列问题 - 组合总和Ⅳ

518. 零钱兑换 II // 组合问题;只需要加之前更新过的值;

  • 二维记忆化搜索能过;
  • 二维DP / 一维DP 才是正解
class Solution {public:int change(int amount, vector<int>& coins) {vector<vector<int>> dp(coins.size(),vector<int>(amount+1,0));for(int i=0;i<coins.size();i++)dp[i][0] = 1;for(int j=1;j<=amount;j++)if(j>=coins[0])  dp[0][j] = dp[0][j-coins[0]];for(int i=1;i<coins.size();i++)for(int j=1;j<=amount;j++)if(j>=coins[i])dp[i][j] = dp[i-1][j]+dp[i][j-coins[i]];elsedp[i][j] = dp[i-1][j];return dp.back().back();}
};
  • 一维DP
class Solution {public:int change(int amount, vector<int>& coins) {vector<int> dp(amount+1,0);dp[0] = 1;for(int i=0;i<coins.size();i++)for(int j=1;j<=amount;j++)if(j>=coins[i])dp[j] += dp[j-coins[i]];return dp.back();}
};

377. 组合总和 Ⅳ /// 排列问题 ;对每种tar,每个元素都试一下;就做成了排列

  • 正常背包定义为 取i 块物品,tar = j 的时候的排列数;这个要取
  • 排列问题,外循环tar,内循环nums;才能反复取到同样nums[i];
class Solution {public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp (target+1,0);dp[0] =1 ;for(int j=1;j<=target;j++)for(int i=0;i<nums.size();i++)if(j>=nums[i]&&dp[j]<INT_MAX-dp[j-nums[i]])dp[j]+=dp[j-nums[i]];return dp.back();}
};
1.2.2、排列问题/组合问题 - 零钱兑换、完全平方数、单词拆分

322. 零钱兑换 //完全背包 // 求填满最少的个数// 排列组合都可

class Solution {public:int coinChange(vector<int>& coins, int amount) {vector<vector<int>> dp(coins.size(),vector<int>(amount+1,INT_MAX));for(int i=0 ;i<coins.size();i++)dp[i][0] = 0;for(int j=1;j<amount+1;j++)if(coins[0]<=j&&dp[0][j-coins[0]]!= INT_MAX) dp[0][j] = 1+dp[0][j-coins[0]];for(int i=1 ;i<coins.size();i++)for(int j=1;j<amount+1;j++){if(dp[i-1][j] !=-1)dp[i][j] = dp[i-1][j];if(coins[i]<=j&&dp[i][j-coins[i]]!=INT_MAX)dp[i][j] = min(dp[i][j],dp[i][j-coins[i]]+1);}return dp.back().back()==INT_MAX? -1:dp.back().back();}
};
  • 简化一维 // 发现循环顺序无所谓;因为求最小个数,排列或者组合都可;
class Solution {public:int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount+1,INT_MAX);dp[0] = 0;for(int j=1;j<amount+1;j++)if(coins[0]<=j&&dp[j-coins[0]]!= INT_MAX) dp[j] = 1+dp[j-coins[0]];for(int i=1 ;i<coins.size();i++)for(int j=1;j<amount+1;j++)if(coins[i]<=j&&dp[j-coins[i]]!=INT_MAX)dp[j] = min(dp[j],dp[j-coins[i]]+1);        return dp.back()==INT_MAX? -1:dp.back();}
};

279. 完全平方数 //完全背包 // 求填满最少的个数// 排列组合都可 //***

  • 和零钱兑换 几乎一样,都是找能填满的最少个数;只是物品是各个平方数;
  • 外层背包,内层物品:求的是排列问题;
class Solution {public:int numSquares(int n) {vector<int> dp(n+1,INT_MAX);dp[0] = 0;for(int i=1;i<=n;i++)for(int j=1;j*j<=i;j++)dp[i] = min(dp[i],dp[i-j*j]+1);return dp.back();}
};
  • 外层物品,内层背包:求的是组合问题;

class Solution {public:int numSquares(int n) {vector<int> dp(n+1,INT_MAX);dp[0] = 0;for(int j=1;j*j<=n;j++)for(int i=1;i<=n;i++)if(i>=j*j)dp[i] = min(dp[i],dp[i-j*j]+1);return dp.back();}
};

139. 单词拆分 //完全背包 // 求是否能组成 // 排列组合都可

  • 子串就是物品,背包是整个字符串,看能不能在DICT 中找到字串拼满背包;
  • 外循环背包,内循环物品;(如果写外层物品,得先处理所有字串,较麻烦)
class Solution {public:bool wordBreak(string s, vector<string>& wordDict) {vector<bool>dp(s.size()+1);dp[0] = 1;unordered_set<string> _hash(wordDict.begin(),wordDict.end());for(int i=1;i<=s.size();i++)for(int j=i;j>=1;j--)if(dp[j-1]&&_hash.find(s.substr(j-1,i-j+1))!=_hash.end()){dp[i] = true;break;}return dp.back();}
};

一套框架解决「背包问题」

1.3、多重背包
  • 有N种物品和⼀个容量为V 的背包。第i种物品最多有Mi件可⽤,每件耗费的空间是Ci ,价值是Wi 。
  • 求解将哪些物品装⼊背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最⼤。
  • 每件物品最多有Mi件可⽤,把Mi件摊开,其实就是⼀个01背包问题了。

其实就是每个物品有M个而已;展开 求0-1背包问题 即可;

3、股票买卖问题

1.1、买卖股票时机 I / II / III / IV

121. 买卖股票的最佳时机

class Solution {public:int maxProfit(vector<int>& prices) {if(prices.size() ==1 )return 0;int _max = 0, _min = prices[0];for(int i=1;i<prices.size();i++){_max = max(prices[i] - _min,_max);_min = min(prices[i] ,_min);}return _max;}
};
  • dp:考虑今天持有与不持有;当天持有,只能是昨天持有、今天买入;(因为只能买卖一次,今天持有不能是之前卖出后的钱再今天买入)
  • 可以滚动数组,压缩空间优化;
class Solution {public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(),vector<int>(2,0));// not or havedp[0][0] = -prices[0];for(int i=1;i<prices.size();i++){dp[i][0] = max(dp[i-1][0],-prices[i]); // different dp[i][1] = max(dp[i-1][1],dp[i-1][0]+prices[i]);}return dp.back().back();}
};

122. 买卖股票的最佳时机 II // ****

  • dp:考虑今天持有与不持有;当天持有,只能是昨天持有、今天买入(包括昨天卖出赚了钱,今天再买入的情况);
class Solution {public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(),vector<int>(2,0));// not or havedp[0][0] = -prices[0];for(int i=1;i<prices.size();i++){dp[i][0] = max(dp[i-1][0],dp[i-1][1]-prices[i]); // different dp[i][1] = max(dp[i-1][1],dp[i-1][0]+prices[i]);}return dp.back().back();}
};
  • 贪心;
class Solution {public:int maxProfit(vector<int>& prices) {if(prices.size() ==1 )return 0;int res = 0;for(int i=1;i<prices.size();i++)res+= max(0,prices[i]-prices[i-1]);return res;}
};

123. 买卖股票的最佳时机 III // ****

  • dp;可压缩空间,只依赖上一行;dp[0]、[1] 代表了一次买卖的收获,[2]、[3 ] 包含了第一次的收获;并加上了第二次的收获;
class Solution {public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(), vector<int>(4, 0));dp[0][0] = -prices[0];//buy1 sell1  buy2 sell2             //[3,3,5,0,0,3,1,4]dp[0][2] = -prices[0];                                       //-3 0 -3 0for (int i = 1; i < prices.size(); i++) {                  //-3 0 -3 0dp[i][0] = max(dp[i - 1][0], -prices[i]);               //-3 2 -3 2dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);    // 0 2  2 2dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] - prices[i]);     // 0 2  2 2dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] + prices[i]);    // 0 3  2 5}                                                            // 0 3  2 5// 0 4  2 6return dp.back()[3];}
};

188. 买卖股票的最佳时机 IV

class Solution {public:int maxProfit(int k, vector<int>& prices) {vector<vector<int>> dp(prices.size(), vector<int>(2*k, 0));if(prices.size()<2||k==0) return 0;k*=2;for(int i=0;i*2<k;i++)  //buy1 sell1  buy2 sell2       dp[0][i*2] = -prices[0];for (int i = 1; i < prices.size(); i++) {    dp[i][0] = max(dp[i - 1][0], -prices[i]);for(int j=1;j<k;j++)if(j%2==0)dp[i][j] = max(dp[i - 1][j], dp[i - 1][j-1]- prices[i]);else   dp[i][j] = max(dp[i - 1][j], dp[i - 1][j-1] + prices[i]); }                                                                               return dp.back().back();}
};
1.2、买卖股票时机带冷冻期、手续费

309. 最佳买卖股票时机含冷冻期

class Solution {public:int maxProfit(vector<int>& prices) {if(prices.size() == 0) return 0;vector<vector<int>> dp(prices.size(),vector<int>(3,0));dp[0][0] = -prices[0];for(int i=1;i<prices.size();i++){//持股 // 冷冻期 // 非冷冻期dp[i][0] = max( dp[i-1][0], dp[i-1][2]-prices[i]);dp[i][1] = dp[i-1][0]+prices[i];dp[i][2] = max( dp[i-1][2], dp[i-1][1]);}return max(dp.back()[1],dp.back()[2]);}
};

714. 买卖股票的最佳时机含手续费

class Solution {public:int maxProfit(vector<int>& prices, int fee) {vector<pair<int,int>> dp(prices.size());dp[0] = {-prices[0]-fee,0};for(int i=1;i<prices.size();i++)dp[i] = {max(dp[i-1].first,dp[i-1].second -prices[i]-fee),max(dp[i-1].second,dp[i-1].first+prices[i])};return dp.back().second;}
};

5、子序列问题

1.1、子序列(不连续)- 最长递增子序列、套娃问题;最长公共子序列;

300. 最长递增子序列

  • 记忆化dfs
class Solution {public:vector<int> memo;int dfs(vector<int>& nums,int index){if(memo[index]!= -1) return memo[index];int temp = 0;for(int i=index+1;i<nums.size();i++)if(nums[i]>nums[index])temp = max(temp ,dfs(nums,i)+1);memo[index] = temp;return temp;}int lengthOfLIS(vector<int>& nums) {int temp = 0;memo = vector<int>(nums.size(),-1);for(int i=0;i<nums.size();i++)temp = max(temp ,dfs(nums,i)+1);return temp;}
};
  • dp
  • 还可优化到O(NlogN) ;用贪心加二分(通过替换 ceil)
class Solution {public:int lengthOfLIS(vector<int>& nums) {vector<int> dp(nums.size(),1);int _max = 1;for(int i=1;i<nums.size();i++){for(int j=i-1;j>=0;j--)if(nums[j]<nums[i])dp[i] = max(dp[j]+1,dp[i]);_max = max(_max,dp[i]);}return  _max;}
};

354. 俄罗斯套娃信封问题

class Solution {public:int maxEnvelopes(vector<vector<int>>& envelopes) {sort(envelopes.begin(),envelopes.end());vector<int> dp(envelopes.size(),1);int res = 1;for(int i=1;i<envelopes.size();i++){for(int j=0;j<i;j++)if(envelopes[i][0]>envelopes[j][0]&&envelopes[i][1]>envelopes[j][1] )dp[i] = max(dp[j]+1,dp[i]);res = max(res,dp[i]);}return res;}
};

1143. 最长公共子序列

class Solution {public:int longestCommonSubsequence(string text1, string text2) {vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));int res =0;for(int i=1;i<=text1.size();i++)for(int j=1;j<=text2.size();j++)if(text1[i-1] == text2[j-1])dp[i][j] = dp[i-1][j-1]+1;elsedp[i][j] = max(dp[i-1][j],dp[i][j-1]);return dp.back().back();}
};

1035. 不相交的线

class Solution {public:int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));int res = 0;for(int i=1;i<=nums1.size();i++)for(int j=1;j<=nums2.size();j++)if(nums1[i-1] ==nums2[j-1])dp[i][j] = dp[i-1][j-1]+1;elsedp[i][j] = max(dp[i][j-1],dp[i-1][j]);return dp.back().back();}
};
1.2、子数组(连续)- 最长连续递增序列、最长重复子数组、 最大子序和;

674. 最长连续递增序列

  • 双指针、dp
class Solution {public:int findLengthOfLCIS(vector<int>& nums) {int res = 1;for(int j=1,i=0;j<nums.size();j++){if(nums[j]<=nums[j-1])i = j;res = max(res,j-i+1);}return res;}
};
class Solution {public:int findLengthOfLCIS(vector<int>& nums) {int res = 1;for(int j=1,temp = 1;j<nums.size();j++){if(nums[j]<=nums[j-1])temp = 1;elsetemp++;res = max(res,temp);}return res;}
};

718. 最长重复子数组

  • 二维dp,可以压缩空间;
class Solution {public:int findLength(vector<int>& nums1, vector<int>& nums2) {vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));int res = 0;for(int i=1;i<=nums1.size();i++)for(int j=1;j<=nums2.size();j++)if(nums1[i-1] == nums2[j-1]){dp[i][j] = dp[i-1][j-1]+1;res = max(res,dp[i][j]);}return res;}
};

53. 最大子序和

class Solution {public:int maxSubArray(vector<int>& nums) {vector<int >dp(nums.size());dp[0] =nums[0];int res = dp[0];for(int i=1;i<nums.size();i++){dp[i] = max(dp[i-1]+nums[i],nums[i]);res = max(res,dp[i]);}return res;}
};

152. 乘积最大子数组

  • 因为负数乘负数也能得到大正数,所以不仅要存上一个节点最大值,还要求最小值;
class Solution {public:int maxProduct(vector<int>& nums) {vector<int> dp1(nums.size(),0);vector<int> dp2(nums.size(),0);int res= nums[0];dp1[0] = nums[0];dp2[0] = nums[0];for(int i=1;i<nums.size();i++){dp1[i] = max(nums[i],max(dp1[i-1]*nums[i],dp2[i-1]*nums[i]));dp2[i] = min(nums[i],min(dp1[i-1]*nums[i],dp2[i-1]*nums[i]));res = max(res,dp1[i]);}return res;}
};

1186. 删除一次得到子数组最大和

  • dp1 为以当前节点为结尾的最大子数组和; dp2 以当前节点为结尾,缺一个的最大子数组和
class Solution {public:int maximumSum(vector<int>& arr) {vector<int> dp1(arr);vector<int> dp2(arr);int res = arr[0];for(int i=1;i<arr.size();i++){dp1[i] = max(arr[i],arr[i]+dp1[i-1]);dp2[i] = max(dp1[i-1],arr[i]+dp2[i-1]);res = max(res,max( dp1[i], dp2[i]));}return res;}
};

1191. K 次串联后最大子数组之和

  • K>2后分了两种情况;一种是sum>0 ,那么取前两段的最大值;加K-2次sum即可;
class Solution {public:int kConcatenationMaxSum(vector<int>& arr, int k) {long long   pre = arr[0];long long  res =0,sum = 0;int len =arr.size();res = max(res,pre);for(int m=0;m<arr.size();m++) sum +=arr[m];for(int m=1;m<min(2,k)*len;m++){pre = max((long long )arr[m%len],pre+(long long )arr[m%len]);res = max(res,pre);//cout<<res<<" "<<pre<<endl;}if(sum>0&&k>2){res += sum*(k-2);return  res %(int(1e9 + 7));}return res%(int(1e9 + 7));}
};

*


1.3、编辑距离 - 判断子序列、不同的子序列、两个字符串的删除操作、编辑距离

392. 判断子序列

  • 双指针;有点像dp,相等就匹配;
class Solution {public:bool isSubsequence(string s, string t) {int j=0,i=0;for(;i<t.size()&&j<s.size();i++)if(s[j] == t[i])j++;if(j == s.size()) return true;return false;}
};
  • 动规,预处理;预处理t串,标记每个位置的地址,如果没有,就直接return,否则直接去对应坐标;有点像KMP;
class Solution {public:bool isSubsequence(string s, string t) {int len = t.size();vector<vector<int>> dp(len+1,vector<int>(26));for(int i=0;i<26;i++)dp[len][i] = len;for(int i=len-1;i>=0;i--)for(int j=0;j<26;j++)if(t[i] == j+'a') dp[i][j] = i;else dp[i][j] = dp[i+1][j];for(int i=0,j=0;i<s.size();i++)if(dp[j][s[i]-'a'] == len) return false;else  j=dp[j][s[i]-'a']+1;return true;}
};

115. 不同的子序列

  • dp[i][j]表示以i-1结尾的s苏烈中出现以 j-1为结尾的t的个数;
class Solution {public:int numDistinct(string s, string t) {unordered_set<char> _set_s,_set_t;for(auto _c:s)_set_s.insert(_c);for(auto _c:t)_set_t.insert(_c);for(auto it:_set_t)if(_set_s.find(it)==_set_s.end()) return 0;vector<vector<long>> dp(t.size()+1,vector<long>(s.size()+1,0));for(int j=0;j<=s.size();j++) dp[0][j] = 1;for(int i=1;i<=t.size();i++)for(int j=1;j<=s.size();j++){dp[i][j] = dp[i][j-1];if(t[i-1] == s[j-1])dp[i][j] += dp[i-1][j-1];}return dp.back().back();}
};
  • 回溯,要记忆化和预处理;
class Solution {public:string temp;vector<vector<int>> memo;int dfs(string& s, string& t,int index){if(temp.size() == t.size()) {if(temp==t) return 1; else return 0; }if(temp.size()>t.size()||index>s.size()) return 0;if(memo[index][temp.size()]!= -1) return memo[index][temp.size()];int res =0;for(int i=index;i<s.size();i++)if(temp.size()<t.size())if(s[i] == t[temp.size()]){temp.push_back(s[i]);res += dfs(s,t,i+1);temp.pop_back();}memo[index][temp.size()] = res;return res;}int numDistinct(string s, string t) {unordered_set<char> _set_s,_set_t;for(auto _c:s)_set_s.insert(_c);for(auto _c:t)_set_t.insert(_c);for(auto it:_set_t)if(_set_s.find(it)==_set_s.end()) return 0;memo = vector<vector<int>>(s.size()+1,vector<int>(t.size()+1,-1));temp ="";return dfs(s,t,0);}
};

583. 两个字符串的删除操作 //***

  • dp[i][j] :以i-1为结尾的字符串word1,和以 j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
class Solution {public:int minDistance(string word1, string word2) {vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));for(int i=1;i<=word1.size();i++) dp[i][0] = i;for(int j=1;j<=word2.size();j++) dp[0][j] = j;for(int i=1;i<=word1.size();i++)for(int j=1;j<=word2.size();j++)if(word1[i-1] == word2[j-1])dp[i][j] = dp[i-1][j-1];elsedp[i][j] = min(min(dp[i-1][j],dp[i][j-1])+1,dp[i-1][j-1]+2);return dp.back().back();}
};

72. 编辑距离

  • dp[i][j] 表示以下标 i-1为结尾的字符串word1,和以下标 j-1为结尾的字符串word2,最近编辑距离。
  • 三个方向最小的+1 ,对应的时增、删、改;
class Solution {public:int minDistance(string word1, string word2) {vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));for(int i=1;i<=word1.size();i++) dp[i][0] = i;for(int j=1;j<=word2.size();j++) dp[0][j] = j;for(int i=1;i<=word1.size();i++)for(int j=1;j<=word2.size();j++)if(word1[i-1] == word2[j-1])dp[i][j] = dp[i-1][j-1];elsedp[i][j] = min(min(dp[i-1][j],dp[i][j-1])+1,dp[i-1][j-1]+1);return dp.back().back();}
};
1.4、回文子串 - 回文子串、最长回文子串、最长回文子序列、最小插入构成回文串

  • 回文串的题:涉及到dp[i][j] = d[i+1][j-1] ;所以是从左下角开始做起来的

647. 回文子串 // 动规***

  • dp[i][j] i~j 是否是回文串
class Solution {public:int countSubstrings(string s) {vector<vector<bool>> dp(s.size()+1,vector<bool>(s.size()+1,false));int res = 0;for(int i=s.size();i>0;i--)for(int j=i;j<=s.size();j++)if(s[i-1] == s[j-1]&&(j-i<=1||i<s.size()&&j>1&&dp[i+1][j-1])){dp[i][j] = true;res++;}return res;}
};

5. 最长回文子串

  • 根据题目-回文子串-改一改即可
class Solution {public:string longestPalindrome(string s) {vector<vector<bool>> dp(s.size()+1,vector<bool>(s.size()+1,false));int len=0,_begin = 0;for(int i=s.size();i>0;i--)for(int j=i;j<=s.size();j++)if(s[i-1] == s[j-1]&&(j-i<=1||i<s.size()&&j>1&&dp[i+1][j-1])){dp[i][j] = true;if((j-i+1)>len){len =j-i+1;_begin = i-1;}}return s.substr(_begin,len);}
};

516. 最长回文子序列 //***

  • dp[i][j] s [i~j ]中能匹配的最常回文子序列
class Solution {public:int longestPalindromeSubseq(string s) {vector<vector<int>> dp(s.size()+1,vector<int>(s.size()+1,0));int res = 0;for(int i=s.size();i>0;i--){for(int j=i;j<=s.size();j++){if(i == j)dp[i][j] = 1;else if(i+1<=s.size()&&j-1>0){if(s[i-1] ==s[j-1])dp[i][j] = dp[i+1][j-1] + 2;elsedp[i][j] = max(dp[i+1][j],dp[i][j-1]);}res = max(res,dp[i][j]);}}return res;}
};

1312. 让字符串成为回文串的最少插入次数 //***

  • dp[i][j] s 和t 中最长公共子序列
  • 也可以求最长回文子序列,然后整体长度减去,即可
class Solution {public:int minInsertions(string s) {int n = s.size();string t(s.rbegin(), s.rend());vector<vector<int>> dp(n + 1, vector<int>(n + 1));for (int i = 1; i <= n; ++i) {for (int j = 1; j <= n; ++j) {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);if (s[i - 1] == t[j - 1]) {dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);}}}return n - dp[n][n];}
};

【学习笔记】【leetcode分门别类整理】【C++】相关推荐

  1. 大数据 -- kafka学习笔记:知识点整理(部分转载)

    一 为什么需要消息系统 1.解耦 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束. 2.冗余 消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险.许多 ...

  2. Altium Designer入门学习笔记和快捷键整理

    Altium Designer入门学习笔记和快捷键整理 一.常用快捷键整理: 以下均为英文输入法: Ctrl + 要设的功能 = 生成快捷键 点击S,切换选择,如区域内选择等 原理图界面:框选后,PC ...

  3. 嵌入式linux学习笔记--TCP通讯整理

    嵌入式linux学习笔记–TCP通讯整理 之前的项目中使用到了比较多的tcp 通讯相关的知识,一直也没有进行整理,今天准备拿出时间好好的整理一下TCP通讯的整个过程.预计会整理linux和window ...

  4. 《软件测试》学习笔记(自整理)

    <软件测试>学习笔记(自整理) A crash is when your competitor's program dies. When your program dies, it is ...

  5. Manacher算法学习笔记 | LeetCode#5

    Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...

  6. 秋枫学习笔记-原创文章整理

    点击蓝字关注,提升学习效率 先祝大家圣诞快乐,感谢大家一直以来的支持,这里对原创文章进行整理,方便大家挑选感兴趣的内容阅读. 公众号:秋枫学习笔记 知乎:夏未眠,https://www.zhihu.c ...

  7. Linux 学习笔记(自己整理仅供自己复习)

    前言 LINUX操作系统是一种免费使用和自由传播的类UNIX操作系统.其内核由林纳斯·托瓦兹于1991年10月5日首次发布,是一个基于POSIX的多用户.多任务.支持多线程和多CPU的操作系统.它能运 ...

  8. 【机器学习】Logistic回归---学习笔记(重新整理)

    Logistic回归学习笔记 Logistic回归学习线路 预备知识:建议先去B站学习一下信息量,熵,BL散度,交叉熵的概念. Logistic回归的函数模型 损失函数.损失最小化架构 对数损失作为损 ...

  9. R语言dplyr包学习笔记(吐血整理宇宙无敌详细版)

    出处:AI入门学习 dplyr包主要用于数据清洗和整理,主要功能有:行选择.列选择.统计汇总.窗口函数.数据框交集等是非常高效.友好的数据处理包,学清楚了,基本上数据能随意玩弄,对的,随意玩弄,简直大 ...

最新文章

  1. 深度学习: mAP (Mean Average Precision)
  2. 难死金庸的考题(高中难度)
  3. 嵌入式入门要知道的五个小窍门-心得
  4. AsyncTask中各个函数详细的调用过程,初步实现异步任务
  5. 遇到tensorflow has no attribute 问题
  6. MATLAB工具箱介绍
  7. 【ZOJ 4070】Function and Function
  8. OpenCV人工智能图像处理学习笔记 第6章 计算机视觉加强之机器学习下 Hog_SVM小狮子识别
  9. 三菱PLC连接威纶通触摸屏
  10. 吉米多维奇数学分析习题集每日一题--泰勒公式习题1377
  11. 最全常用正则表达式大全
  12. 盘点2017年科技事件 马云最娱乐雷军最傲娇
  13. ad10搜索快捷键_ad快捷键有哪些 ad快捷键汇总
  14. 两种播放m3u8链接的方法
  15. java工作流引擎:jbpm和activiti对比分析
  16. 對Googgle adsense廣告作弊和Googeladsense點擊廣告的生存前景看法
  17. TOM邮箱的全新域名,163vip.com、163vip.net,老朋友的新问候!
  18. JAVA面试题目及推荐书籍
  19. [Err] 1146 [Err] 1682
  20. 804计算机考研,北京邮电大学804信号系统考研经验

热门文章

  1. 算术公理化的发明人—格拉斯曼
  2. java学习笔记第七周(二)
  3. 《这句英语怎么说?》之生活篇
  4. JavaScript计时事件
  5. 直播合辑 | 微软ATP与您相约100场公益演讲
  6. MySQL|drop大表
  7. Qt如何实现实时鼠标绘制图形
  8. 计科1504班学生博客链接
  9. 27Vert.X框架学习笔记
  10. 合合信息新推出反光消除技术,助力手写文字识别更精准