1、输入处理
1.1 不定长数组输入
void main() {
int num;
vector<int> arr;
while (cin >> num) {
arr.push_back(num);
if (getchar() == '\n')
break;
}
}
1.2 多行不定长数组输入
1、大循环:读取每一行串(getline(cin,str)) 终止条件为读取的行为空行str.empty()
2、将读取到的一行串输入字符串流中 istringstream iss(str);
3、小循环:读取字符串流iss的数据并保存到数组中;
#include<iostream>
#include<vector>
#include<string>
#include<sstream>
using namespace std;
int main(){
int num;
vector<vector<int>> arr;
string str;
while (getline(cin,str)&&!str.empty()) {//读取一行串同时该串非空
istringstream iss(str);//将读出的字符串再输入到流中
vector<int> temp;
while (iss >> num) temp.push_back(num);
arr.push_back(temp);
}
return 0;
}
1.3 将字符串写入到字符流中 再从流中获取数组
#include<iostream>
#include<sstream>
#include<vector>
#include<string>
using namespace std;
int main() {
string src = "12 13 14 15",tmp="";
vector<string> arr;
istringstream iss(src);
while (iss >> tmp) {
arr.push_back(tmp);
}
for (auto s : arr)cout << s << endl;
}
1.4 输入一行串以某一字符分割数字 获取数组
1、让两个指针pos1:指向每个子串的起始数字位;pos2:指向分割字符位
2、最后一个数字在循环体外进行获取调用substr(pos1)
/*
*str:输入的源串
*ch:以字符ch进行分割
*/
vector<string> splite(const string &str,const char &ch) {
string::size_type pos1=0, pos2=str.find(ch);//find返回目标字符的第一个位置
vector<string> res;
while (pos2 != string::npos) {//npos表示某一个字符的结束位置
string tmp = str.substr(pos1, pos2-pos1);//pos1表示所截取串的起始位置 pos1-pos2截取长度
res.push_back(tmp);
pos1 = pos2 + 1;
pos2 = str.find(ch, pos1);//从pos1位置开始寻找,找到目标字符的第一个位置
}
if (pos1 != string::npos) res.push_back(str.substr(pos1));//最后一个数字字符
return res;
}
1.5 m*n矩阵输入
#include<iostream>
#include<vector>
using namespace std;
void main()
{
int m, n;
cin >> m;
cin >> n;
int num;
vector<vector<int>> matrix(m, vector<int>(n, 0));
for (int i = 0;i < m;i++) {
vector<int> tempArr(n, 0);
for (int j = 0;j < n;j++) {
cin >> num;
tempArr[j]=num;
}
matrix[i] = tempArr;
}
}
1.6 有关链表输入输出问题:
输入两行串 数字以空格间隔,若某一行为空,则对应的链表即为空
#include<iostream>
#include<vector>
#include<string>
#include<sstream>
using namespace std;
//链表输出
void printList(ListNode* head) {
if (!head)return;
ListNode* cur = head;
while (cur) {
cout << cur->val << " ";
cur = cur->next;
}
cout << endl;
}
//链表输入
int main(){
ListNode* head1=nullptr;//存放第一行数据的链表
ListNode* head2= nullptr;//存放第二行数据的链表
ListNode* cur = nullptr;
string str1,str2;//str1获取到的第一行串 str2获取到的第二行串
getline(cin, str1);//获取第一行串
getline(cin, str2);//获取第二行串
if (str1.empty()) {//空串 链表则为空
head1 = nullptr;
}
else {
istringstream iss(str1); //读入字符流中
int num,flag=0; //flag用于标记读取的第一个数据 作用头节点
while (iss >> num) {
if (flag == 0) {
head1 = new ListNode(num);
cur = head1;
flag = 1;
}
else {
cur->next = new ListNode(num);
cur = cur->next;
}
}
}
if (str2.empty()) {
head2 = nullptr;
}
else {
istringstream iss(str2);
int num, flag = 0;
while (iss >> num) {
if (flag == 0) {
head2 = new ListNode(num);
cur = head2;
flag = 1;
}
else {
cur->next = new ListNode(num);
cur = cur->next;
}
}
}
}
1.7 有关二叉树的输入创建问题
说明:输入一行串 各子串以空格隔开 子串为“NULL”对应节点为空 以层序遍历的方式创建树
以某一节点说明 若该节点对应子串i 那么它的左孩子对应2i+1,右孩子对应2i+2
关键点要注意:createTree的形参root必须是TreeNode* 的引用
void createTreee(TreeNode* &root, int index,const vector<string> &strs) {
if (strs[index].compare("NULL") == 0) {
root = nullptr;
return;
}
int n = strs.size();
int val = stoi(strs[index]);
root = new TreeNode(val);
root->left = nullptr;
root->right = nullptr;
if (2 * index+1 < n)createTreee(root->left, 2 * index+1, strs);
if (2 * index+2 < n)createTreee(root->right, 2 * index+2, strs);
}
int main()
{
TreeNode* root = nullptr;
vector<string> strs;
string s;
while (cin >> s) {
strs.push_back(s);
if (getchar() == '\n')
break;
}
createTreee(root, 0, strs);
return 0;
}
1.8 控制数字输出格式
头文件:#include< iomanip >
cout<<setprecision(n)<<num:控制输出n位有效数字
cout<<fixed<<setprecision(n)<<num:控制输出n位小数
#include<cmath>
#include<iomanip>
#include<iostream>
using namespace std;
int main() {
float x = 100.23456;
//输出指定有效位的目标数字
cout<<"输出3位有效数字:" << setprecision(2) << x << endl;
//保持小数点后3位输出
cout <<"保留小数点后3位:" << fixed << setprecision(3) << x << endl;
//科学计数法 xey:x表示有效数字 e表示底数10 y表示指数
cout <<"2e-2=" << 2e-2 << endl;
}
2、字符处理函数
2.1 字符截取
从指定位置开始截取指定长度字符:string substr(string::size_type pos,int length)
从指定位置开始截取一直截取到最后:string substr(string::size_type pos)
string str = "hello world";
cout << str.substr(0, 5) << endl;//输出hello
cout << str.substr(6) << endl;//输出world
2.2 查询子串或字符
从整个字符串起始位置开始查找:size_type find(const string &s)/find(const char& s)
从字符指定位置开始查找:size_type find(size_type pos,const string &s)/find(size_type pos,const char& s)
string str = "hello world";
cout << str.find('o') << endl;//输出4
string::size_type pos = 5;
cout << str.find('o',pos ) << endl;//输出6
2.3 字符串替换
//功能将字符串src中非数字字符,非ch字符都替换成ch字符
string my_replace(string src, const char& ch) {
//const char* tmp = src.c_str();
for (string::size_type i = 0;i < src.size();i++) {
if (src[i] < '0' || src[i]>'9'&&src[i]!=ch) {
src[i] = ch;
}
}
return src;
}
2.4 字符串转数字
int string::stoi(string str):转为int型
long string::stol(string str):转为long型
float string::stof(string str):转为float型
double string::stod(string str):转为double型
string s_int = "120";
string s_long = "1231421";
string s_float = "133.333";
string s_doutble = "12.22222";
cout << stoi(s_int) << endl;
cout << stol(s_long) << endl;
cout << stof(s_float) << endl;
cout << stod(s_doutble) << endl;
2.5 进制字符之间的转换
2.5.1 十进制数转二进制字符
#include <cctype>
#include <iostream>
#include <vector>
#include <string>
#include<algorithm>
using namespace std;
string convert_10_to_2_s(int num) {
string str="";
while (num) {
str += '0' + num % 2;
num /= 2;
}
reverse(str.begin(), str.end());
return str;
}
int main() {
int num = 10;
string str = convert_10_to_2_s(num);
cout << str << endl;
str = "10";
cout << stoi(str, 0, 7) << endl;
}
2.6 正则表达式
^:限定开头的字符
$:限定结尾的字符
\d:匹配数字字符
\D:匹配非数字字符
\s:匹配空格
\S:匹配非空格
\w:匹配一般字符(字母,数字,下划线)
\W:匹配除了字母/数字、下划线外的字符
*:*前的单个字符可以出现任意次(单个字符包括空格)
+:+前的单个字符至少出现一次(单个字符不包括空格)
?:?前的单个字符最多出现一次
{...}:
()分组:(123),这样可以将匹配到的123取出来
{}长度:{4,9},这个表示前一个字符串的长度为4到9
[]范围:[a-z],这个表示匹配所有的小写字母
regex_replace(string src,regex reg,string dest);将src中符合reg规则的子串替换成dest
regex_match可以理解为全匹配,在写正则时,需要与待匹配字符串格式保持一致。
#include<iostream>
#include<regex>
#include<string>
using namespace std;
int main() {
string s = "abc123 34sdf";
regex reg("(\\d+)");
string s2=regex_replace(s, reg, "zzz");
cout << s2 << endl;
if (regex_match(s, reg))cout << "true" << endl;
else cout << "false" << endl;
return 0;
}
3、数据结构实现
3.1 单向链表实现
#include<iostream>
#include<vector>
using namespace std;
//单向链表
struct Node
{
int data;
Node* next;
};
class SingleList
{
public:
Node* head;
int size;
SingleList() {
head = new Node();
head->data = 0;
size = 0;
head->next = NULL;
}
void insert(int pos, int data) {
if (pos <=0)return;
if (pos > size) {
append(data);
return;
}
Node* newNode = new Node();
newNode->data = data;
Node* curNode = head;
for (int i = 0;i < pos-1;i++) {
curNode = curNode->next;
}
newNode->next = curNode->next;
curNode->next = newNode;
size++;
}
void append(int data) {
Node* newNode = new Node();
newNode->data = data;
newNode->next = NULL;
Node* curNode = head;
for (int i = 0;i < size;i++) {
curNode = curNode->next;
}
curNode->next = newNode;
size++;
}
void update(int pos, int data) {
if (pos <= 0)return;
if (pos > size) pos = size;
Node* curNode = head;
for (int i = 0;i < pos;i++) {
curNode = curNode->next;
}
curNode->data = data;
}
void remove(int pos) {
if (pos <= 0)return;
if (pos > size) pos = size;
Node* curNode = head;
for (int i = 0;i < pos-1;i++) {
curNode = curNode->next;
}
Node* delNode = curNode->next;
curNode->next = curNode->next->next;
delete delNode;
size--;
}
void printList() {
Node* curNode = head;
for (int i = 0;i < size;i++) {
curNode = curNode->next;
cout << curNode->data << " ";
}
}
~SingleList() {
for (int i = 0;i < size;i++) {
remove(i);
}
delete head;
}
};
3.2 循环队列实现
4.排序算法
4.1 冒泡排序
从小到大进行排序,现将最大值放在最后面,再将次大值放在倒数第二个,以此类推后面
for (int i = 0;i < 10;i++) {
for (int j = 0;j < 10 - i;j++) {
if (a[j] > a[j + 1]) {
swap(a[j],a[i]);
}
}
}
4.2 插入排序
在数组前面已有部分数字排序好的情况下,通过下一个数字与前面数字逐个对比插入到前面排序中合适的位置(插入排序详细解释),相当于冒泡排序的反版。
//插入排序
for (int i = 1;i <10;i++) {
for (int j = i ;j> 0;j--) {
if (a[j] < a[j - 1]) {
swap(a[j],a[j-1]);
}
}
}
4.3 快速排序
参考链接
核心思想:
1、双指针:左指针、右指针
2、基准:找一个基准,通过一趟排序将元素分为两部分(比基准小的在左边,比基准大的在右边),然后再对基准左右两部分继续以同样的方式进行排序,以此递归下去。
实现步骤:
1、先以第一个元素为基准key,左指针先指向第一个位置,右指针指向最后一个位置;
2、先将右指针逐个往左移(right–),直到找到第一个比基准小的元素,那么进行赋值操作,arr[left]=arr[right];
3、再将左指针逐个往右移(left++),直到找到第一个比基准大的元素,那么进行赋值操作,arr[right]=arr[left];
4、最后直至left==right,此时left或right即为基准值key的真正位置;
5、此时再对基准的左部分和右部分进行如上操作,如此递归最后得到正确排序结果。
#include<iostream>
#include<vector>
using namespace std;
//快速排序
int get_standard(vector<int>& arr, int low, int high) {
int key = arr[low];
while (low < high) {
//右指针一直往左移,直到找到小于key的元素
while(low < high && arr[high]>=key) high--;
//找到小于key的右边元素,同时赋值给左指针
if (arr[high]<key) {
arr[low] = arr[high];
}
//左指针一直往右移,直到找到大于key的元素
while(low < high && arr[low]<=key) low++;
//找到大于key的左边元素,同时赋值给右指针
if (arr[low]>key) {
arr[high] = arr[low];
}
}
//此时low==high,将key值赋值给正确的位置,返回正确位置索引
arr[low] = key;
return low;
}
//获取到standard的正取位置,再对左边部分和右边部分进行递归
void quick_order(vector<int>& arry,int low,int high) {
if (low < high) {
int standard = get_standard(arry, low, high);
quick_order(arry, low, standard-1);
quick_order(arry, standard+1, high);
}
}
void main() {
vector<int> arr{ 3,2,5,6,23,78,11,45 };
quick_order(arr, 0, arr.size() - 1);
for (int i = 0;i < arr.size();i++) {
cout << arr[i] << " ";
}
}
4.4堆排序
参考链接
思想:
1.根据初始数组构造初始堆R[0–n-1]
2.交换R[0]与R[n-1],将R[0]与R[n-2]调整为堆;交换R[0]与R[n-2],将R[0]与R[n-3]调整为堆,如此反复直到交换了R[0]与R[1]为止
void adjustHeap(int arr[],int parent,int length) {
//找到第一个子节点
int child = 2 * parent+1;
while (child < length) {//终止条件:子节点下标要小于数组的长度
if (child + 1 < length && arr[child] < arr[child + 1])//取子节点较大的那个
child++;
if (arr[parent] > arr[child])break;
else {
//交换父节点和子节点,同时继续对孙子节点继续进行调整
swap(arr[parent], arr[child]);
parent = child;
child = 2*parent + 1;
}
}
}
void heapSort(int arr[], int length) {
//构造起始堆,先对最后一个父节点进行调整
for (int i = length / 2 - 1;i >= 0;i--) {
adjustHeap(arr, i, length);
}
for (int i = length - 1;i >= 0;i--) {
swap(arr[0], arr[i]);
adjustHeap(arr, 0, i);
}
}
4.5 归并排序
基本思想:分而治之
1、 将数组进行递归二分为一 对子组进行排序
2、再将子组进行递归合并 合并的同时进行对比合并
参考链接
void merge(vector<int>& arr ,int start, int mid,int end) {
vector<int> tmp(end - start+1, 0);
int left = start;//指向左半边数组元素
int right = mid+ 1;//指向右半边数组元素
int t = 0;
while (left <=mid&&right<=end) {
if (arr[left] < arr[right]) {
tmp[t++] = arr[left++];
}
else {
tmp[t++] = arr[right++];
}
}
//对还未进行比较的元素添加到后面
while (left <= mid) {
tmp[t++] = arr[left++];
}
while (right <= end) {
tmp[t++] = arr[right++];
}
//将排好序的子组放入指定区间
t = 0;
while (start<=end) {
arr[start++]=tmp[t++];
}
}
void orderByMerging(vector<int>& arr,int start, int end) {
if (start >= end) return;
int mid = (start + end) / 2;
orderByMerging(arr,start, mid);
orderByMerging(arr, mid+1, end);
merge(arr, start,mid,end);
}
void order(vector<int>& arr) {
orderByMerging(arr, 0, arr.size() - 1);
}
5、codeTop刷题笔记
判断一个数是否为质数
判断一个数num是否为质数只需要判断2~sqrt(num)之间有没有能被num整除的数,如果有则不是,反之则是。
bool isZhiShu(const int &num){
if(num<=1)return false;
for(int i=2;i*i<=num;i++){
if(num%i==0)return false;
}
return true;
}
5.1 无重复字符的最长子串
题解
基本思想:
滑动窗口 当遇到重复字符时,就将滑动窗口左边元素移除
/*
滑动窗口解法:遇到相同字符,就将窗口左边区域元素全部移除
*/
class Solution {
public:
int findLongest(const string& str) {
int n = str.size();
if (n <= 1) return n;//边界情况
int longest = 0;
string tmp = "";
for (string::size_type i = 0; i < n; i++) {
while (tmp.find(str[i]) != string::npos) tmp.erase(0, 1);//从左边一直往右删直至删掉重复的元素
tmp.push_back(str[i]);
longest = max(longest, (int)tmp.size());
}
return longest;
}
};
5.2 反转链表
题解
思想:双指针 curNode指向当前节点 newHead指向当前节点的下一个节点 同时在移动curNode指针前必须先临时保存curNode->next
ListNode* reverseList(ListNode* head) {
ListNode* newHead=nullptr,*curNode;
curNode = head;
while (curNode != nullptr) {
ListNode* tmp=curNode->next;//暂存后继结点
curNode->next = newHead;
newHead = curNode;
curNode = tmp;
}
return newHead;
}
5.3 LRU 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
设计思想:哈希表 + 双向链表 参考链接
LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。
双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。
#include<iostream>
#include<list>
#include<unordered_map>
using namespace std;
class LRUCache {
public:
LRUCache(int capacity) {
this->capacity = capacity;
}
int get(int key) {
if (_map.find(key)!= _map.end()) {//存在
auto it= *_map[key];
_list.erase(_map[key]);
_list.push_front(it);
_map[key] = _list.begin();
return it.second;
}
return -1;
}
void put(int key, int value) {
if (_map.find(key) != _map.end()) {//存在
_list.erase(_map[key]);
}
else {//不存在
if (capacity == _map.size()) {//容量已满
int tail = _list.back().first;
_map.erase(tail);
_list.pop_back();
}
}
_list.push_front(pair<int, int>(key, value));
_map[key] = _list.begin();
}
private:
list<pair<int, int>> _list;//存有键值对的链表 按访问时间来排序
unordered_map<int, list<pair<int, int>>::iterator> _map;//存有key所对应list中的位置
int capacity;
};
5.4 寻找第k大的元素
思想:堆排序
void adjustHeap(vector<int>& arr, int parent, int len) {
int child = 2 * parent + 1;
while (child < len) {
if (child + 1 <len && arr[child + 1] > arr[child])
child++;
if (arr[parent] < arr[child]) {
swap(arr[parent], arr[child]);
}
parent = child;
child = 2 * parent + 1;
}
}
int findKthLargest(vector<int>& arr,int k) {
for (int i = arr.size() / 2 - 1;i >= 0;i--) {
adjustHeap(arr, i, arr.size());
}
for (int i = arr.size() - 1;i >= arr.size()-k&&i>=0;i--) {
swap(arr[0], arr[i]);
adjustHeap(arr, 0, i);
}
return arr[arr.size() - k];
}
5.5 K 个一组翻转链表
题目链接
关键点:链表反转问题+链表连接问题
链表反转问题:反转left到right所指节点之间的子链表 并返回新的头节点和尾节点 新的头节点就是反转前的尾部 新的尾节点就是反转前的头部 return pair<>(right,left)
链表连接问题:关键点 在头节点前再添加一个pre_head 记录下子链表头节点的前驱pre 记录下子链表的后继nex
pair<ListNode*,ListNode*> reversePart(ListNode* left, ListNode* right) {
ListNode* pre = right->next;//left到right区域的新头节点
ListNode* cur = left;
while (cur != right) {
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pair<ListNode*, ListNode*>(right, left);
}
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* pre_head = new ListNode(0);//链表头节点的前一个节点
ListNode* left = head;//子链表头节点
ListNode* right = pre_head;//子链表尾节点
ListNode* pre = pre_head;//子链表头节点的前一个节点
pre_head->next = head;
while (left) {
for (int i = 0;i < k;i++) {//移动至下一个子链表的头节点处
right = right->next;
if (right == nullptr)
return pre_head->next;
}
ListNode* nex = right->next;//子链表的后一个节点
pair<ListNode*, ListNode*> result=reversePart(left, right);//反转子链表
left = result.first;//子链表的新头结点
right = result.second;//子链表的新尾结点
pre->next = left;//连接子链表的头节点到前一个子链表的尾部
right->next = nex;//连接子链表的尾节点到下一个子链表的头部
pre = right;//下一个链表头结点的前驱节点 即前一个子链表的尾部
left = right->next;//下一个链表的头节点 即上一个子链表尾节点的后继节点
}
return pre_head->next;
}
5.6 三数之和
题解链接
基本思想:
1、先进行排序(从小到大)
2、遍历 指定一个固定位置i 左指针left=i+1 右指针right=n-1
判断arr[i]是否大于零:>0:直接返回结果
判断arr[i]是否跟上一个元素arr[i-1]是否相同,去重
若arr[i]+arr[left]+arr[right]>0:right–;
若arr[i]+arr[left]+arr[right]<0:left++;
若arr[i]+arr[left]+arr[right]==0:添加三员数组 left++,right–,同时要注意去重必须移动到下一个不同值处
vector<vector<int>> threeSum(vector<int>& arr) {
vector<vector<int>> res;
if (arr.size() < 3)//小于三个元素的可以直接返回
return res;
int left = 1;
int right = arr.size() - 1;
sort(arr.begin(), arr.end());//先排序
for (int i = 0;i < arr.size();i++) {//固定某个位置不动 让left从右边第一个位置向右移动 让right从最后一个元素向左移动
if (arr[i] >= 0) return res;
if (i > 0 && arr[i] == arr[i - 1]) continue;//移动到下一个固定位置 与上一个元素必须不同
left = i + 1;//指向固定元素右边第一个位置
right = arr.size() - 1;//指向最后一个元素
while (left < right) {
if (arr[i] + arr[left] + arr[right] == 0) {
vector<int> tmp{ arr[i],arr[left],arr[right] };
res.push_back(tmp);
right--;
left++;
//去重 将左指针和右指针分别与上一个位置进行对比 直到找到与其不同的下一个元素
while (left < right && arr[right] == arr[right+1])right--;
while (left < right && arr[left] == arr[left - 1])left--;
}
else if (arr[i] + arr[left] + arr[right] > 0) {
right--;
}
else {
left++;
}
}
}
return res;
}
5.7 最大子数组和
解题关键:
转化为求以nums[i]结尾的子数组最大和问题
采用动态规划 dp[i]存放以nums[i]结尾的子数组的最大和
对于dp[i+1]:
1、若dp[i]<0:由于nums[i]加上一个负数只能使数值更小,那么直接舍弃以nums[i]结尾的子问题 取dp[i+1]=nums[i+1];
2、若dp[i]>=0:由于nums[i]加上一个非负数只能使数值更大,那么取dp[i+1]=dp[i]+nums[i]
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int maxSum = nums[0];
vector<int> dp(n);//动态规划 dp[i]记录以nums[i]结尾的子组最大和
dp[0] = nums[0];
for (int i = 1;i < n;i++) {
if (dp[i - 1] < 0) {//若dp[i-1]小于零 由于nums[i]加上一个负数只能使值更小 那么dp[i]直接取nums[i]
dp[i] = nums[i];
}
else {//若dp[i-1]大于等于零 nums[i]加上一个非负数会使值更大 那么dp[i]取dp[i-1]+nums[i]
dp[i] = dp[i - 1] + nums[i];
}
maxSum = max(dp[i], maxSum);
}
return maxSum;
}
5.8 合并两个有序链表
题解描述
核心思想:递归
1、判空处理 list1为空则直接返回list2 list2为空则直接返回list1
2、递归调用 将较小头节点的下一个节点与另一个链表头节点进行融合 最终返回较小头节点
//递归调用
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
//1、判空处理 list1为空则直接返回list2 list2为空则直接返回list1
if (list1 == nullptr) {
return list2;
}
else if (list2 == nullptr) {
return list1;
}
//2、递归调用 将较小头节点的下一个节点与另一个链表头节点进行融合 最终返回较小头节点
if (list1->val < list2->val) {
list1->next = mergeTwoLists(list1->next, list2);
return list1;
}
else {
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
}
有关链表输入输出问题:
输入两行串 数字以空格间隔,若某一行为空,则对应的链表即为空
#include<iostream>
#include<vector>
#include<string>
#include<sstream>
using namespace std;
//链表输出
void printList(ListNode* head) {
if (!head)return;
ListNode* cur = head;
while (cur) {
cout << cur->val << " ";
cur = cur->next;
}
cout << endl;
}
//链表输入
int main(){
ListNode* head1=nullptr;//存放第一行数据的链表
ListNode* head2= nullptr;//存放第二行数据的链表
ListNode* cur = nullptr;
string str1,str2;//str1获取到的第一行串 str2获取到的第二行串
getline(cin, str1);//获取第一行串
getline(cin, str2);//获取第二行串
if (str1.empty()) {//空串 链表则为空
head1 = nullptr;
}
else {
istringstream iss(str1); //读入字符流中
int num,flag=0; //flag用于标记读取的第一个数据 作用头节点
while (iss >> num) {
if (flag == 0) {
head1 = new ListNode(num);
cur = head1;
flag = 1;
}
else {
cur->next = new ListNode(num);
cur = cur->next;
}
}
}
if (str2.empty()) {
head2 = nullptr;
}
else {
istringstream iss(str2);
int num, flag = 0;
while (iss >> num) {
if (flag == 0) {
head2 = new ListNode(num);
cur = head2;
flag = 1;
}
else {
cur->next = new ListNode(num);
cur = cur->next;
}
}
}
}
5.9 两数之和
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
vector<int> res;
for (int i = 0;i < n;i++) {
int tar = target - nums[i];
for (int j = i+1;j < n;j++) {
if (tar == nums[j]) {
res.push_back(i);
res.push_back(j);
return res;
}
}
}
return res;
}
5.10 最长回文子串
题解链接
核心思想:中心扩展法
以s[i]或者s[i],s[i+1]为中心的最大回文长度
//以left和right为中心的最大回文长度
int expandaroundcenter(string s, int left, int right) {
int L = left;
int R = right;
while (L >= 0 && R < s.size() && s[L] == s[R]) {
L--;
R++;
}
return R - L - 1;//此时的R和L是最大回文的外层边界 R-L后还要减去一个1
}
string longestPalindrome(string s) {
string maxstr="";
int n = s.size();
int maxLen = 0;//记录最大回文长度
int center=0;//记录最大回文中心
if (n <= 1) return s;
for (int i = 0;i < n;i++) {
int len1=expandaroundcenter(s,i, i);//以中心i向外扩展
int len2=expandaroundcenter(s,i, i+1);//以中心i,i+向外扩展
int len = max(len1, len2);
if (maxLen < len) {
maxLen = len;
center = i;
}
}
int start = center - (maxLen - 1) / 2;
maxstr = s.substr(start, maxLen);
return maxstr;
}
5.11 二叉树的层序遍历
题解链接
核心思想:采用deque依次层放并取出每一层的节点
1、初始化:将根节点作为deque的第一个元素
2、大循环:终止条件为deque空
3、for循环:取出当前容器中存在的所有节点 在取出过程中将节点的非空左右孩子放入deque容器
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (root == nullptr) return res;//判空处理
deque<TreeNode*> dq;
dq.push_back(root);//初始化 将根节点放入
int n = dq.size();
while (!dq.empty()) {
vector<int> tmp;
int n = dq.size();
for (int i = 0;i< n;i++) {//遍历当前队列中所有的节点
auto node = dq.front();
dq.pop_front();
tmp.push_back(node->val);
//遍历过程中 将左右孩子放入队列尾部
if (node->left) {
dq.push_back(node->left);
}
if (node->right) {
dq.push_back(node->right);
}
}
res.push_back(tmp);
}
return res;
}
5.12 搜索旋转排序数组
题解链接
解题核心:二分法
1、left标记区间左边界 right标记区间右边界 mid标记区间中间位
2、判断mid是螺旋数组的左半边还是右半边
若mid在左半边:若target小于nums[mid]并且大于左边界 则移动右边界
若mid在右半边://若target大于nums[mid]并且小于右边界 则移动左边界
int search(vector<int>& nums, int target) {
int n = nums.size(),mid;
int left = 0;//二分边界左边
int right = n - 1;//二分边界右边
while (left<=right) {
mid = (left + right) / 2;
if (nums[mid]==target) {
return mid;
}
//判断mid在螺旋左半边还是右半边
if (nums[mid] >= nums[right]) {//左半边
if (target<nums[mid]&&target>=nums[left]) {//若target小于nums[mid]并且大于左边界 则移动右边界
right = mid - 1;
}
else {
left = mid + 1;
}
}
else {//右半边
if (target > nums[mid]&&target<=nums[right] ) {//若target大于nums[mid]并且小于右边界 则移动左边界
left = mid + 1;
}
else {
right = mid - 1;
}
}
}
return -1;
}
5.13 买卖股票的最佳时机(简单)
题目链接
解题关键: minPrice指向最小价格 maxProfit保存最大利润
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n <= 1)return 0;
int minPrice = prices[0];
int maxProfit = 0;
for (int i = 0;i < n;i++) {
maxProfit = max(maxProfit, prices[i] - minPrice);
minPrice = min(minPrice, prices[i]);
}
return maxProfit;
}
5.14 岛屿数量
题目描述
解题关键:动态规划 dp[i][j]标记网格点grid[i][j]是否被访问过 对于访问过的点直接跳过
int visit(vector<vector<char>>& grid, int i, int j,vector<vector<int>> &dp) {
if (dp[i][j] == 0) {//未访问的点
dp[i][j] = 1;
}
else {
return 0;
}
if (grid[i][j] == '1') {
//判断是否到达边界 一直访问直到访问到边界
if (i - 1 >= 0) visit(grid, i - 1, j, dp);
if (i + 1 <grid.size()) visit(grid, i + 1, j, dp);
if (j - 1 >= 0) visit(grid, i , j-1, dp);
if (j + 1 < grid[0].size()) visit(grid, i , j+1, dp);
return 1;
}
return 0;
}
int numIslands(vector<vector<char>>& grid) {
int m = grid.size();
if (m == 0) return 0;
int n = grid[0].size();
if (n == 0)return 0;
vector<vector<int>> dp(m, vector<int>(n, 0));
int res=0;
for (int i = 0;i < m;i++) {
for (int j = 0;j < n;j++) {
if (dp[i][j]) {//对于访问过的点直接跳过
continue;
}
if (visit(grid, i, j, dp) == 1)
res++;
}
}
return res;
}
5.15 环形链表
题目链接
解题思路:用set存放访问过的节点 对于下一个节点 若set中已经存在 说明存在环形链表
bool hasCycle(ListNode* head) {
ListNode* cur = head;
set<ListNode*> seen;
while (cur) {
if (seen.find(cur) != seen.end()) {
return true;
}
seen.insert(cur);
cur = cur->next;
}
return false;
}
5.16 有效的括号
题目链接
解题思路: 关键 使用stack容器存放左括号 使用map存放<右括号,左括号>对组(省去括号类型判断)
解题步骤:
1、边界处理:字符长度非偶数 绝对无效
2、当前括号为左:直接放入stack顶部
当前括号为右: 先判断stack是否空 空直接无效
再判断stack顶部括号是否匹配 不匹配直接无效
3、最后再对statck进行判空:非空直接无效
bool isValid(string s) {
int n = s.size();
if (n % 2 == 1) return false;
stack<char> sta;
map<char, char> ma;
ma.insert(pair<char,char>(')', '('));
ma.insert(pair<char, char>(']', '['));
ma.insert(pair<char, char>('}', '{'));
for (int i = 0;i < n;i++) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{') {
sta.push(s[i]);
}
else {
if (sta.empty()) return false;
else {
if (ma[s[i]] == sta.top()) {
sta.pop();
}
else return false;
}
}
}
if (!sta.empty()) return false;//最后再进行一个判空处理
return true;
}
5.17 合并两个有序数组
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
nums1.resize(m + n);
if (m == 0) {
for (int i = 0;i < n;i++)
{
nums1[i] = nums2[i];
}
}
if (n == 0) {
return;
}
int i = 0, j = 0;//i,j分别指向nums1和nums2元素
while (i < m) {
if (nums1[i] > nums2[0]) swap(nums1[i], nums2[j]);
i++;
}
for (j = 0;j < n;j++) {
nums1[i + j] = nums2[j];
}
}
官方最优解
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
if (m == 0) {
for(int i=0;i<n;i++) nums1[i] = nums2[i];
return;
}
if (n == 0) return;
int i = m + n - 1;//指向nums1尾端
m--;//指向nums1最大元素
n--;//指向nums2最大元素
while (n >= 0) {
while (m >= 0 && nums1[m] >nums2[n]) {
swap(nums1[m--], nums1[i--]);
}
swap(nums2[n--], nums1[i--]);
}
}
5.18 全排列
解题关键:递归回溯
每次递归都对所有元素进行访问 取一个没有被访问的元素 放入临时数组combine中 同时将该元素下标放入set中标记该元素访问过 继续下一轮递归 该子递归完成后 记得回溯状态
递归终止条件:set容器的size与nums的size相等 将combine放入res中
void backtrack(vector<int>& nums, vector<vector<int>>& res, vector<int>& combine, set<int>& dp) {
int n = nums.size();
if (dp.size() == nums.size()) {//终止条件 set的size与nums的size相等
res.push_back(combine);
return;
}
for (int i = 0;i < n;i++) {
if (dp.count(i) == 0) {//未访问该元素
dp.insert(i);//下标放入set
combine.push_back(nums[i]);//元素放入combine
backtrack(nums, res, combine, dp);
//回溯状态
combine.pop_back();
dp.erase(i);
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> combine;
set<int> dp;
backtrack(nums,res, combine,dp);
return res;
}
5.19 二叉树的锯齿形层序遍历
题目链接
核心思想:设立一个标志位flag 标志当前进行正序还是逆序
注意问题:
1、从头部取节点时 要将左右孩子进行尾差 防止影响头部
2、从尾部取节点时 要将左右孩子进行头插 防止影响头部
3、同时要注意左右孩子的插入顺序也不一样
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>> res;
deque<TreeNode*> deq;
bool flag = true;//标记当前层是按照顺序遍历还是逆序遍历
deq.push_back(root);//初始化
while (!deq.empty()) {
vector<int> tmp;
int n = deq.size();//当前容器元素个数
if (flag) {//正序 从容器尾部取节点 同时将节点的左右孩子进行头插 防止影响尾部
for (int i = 0;i < n;i++) {
auto node = deq.back();
deq.pop_back();
tmp.push_back(node->val);
if (node->left) deq.push_front(node->left);
if (node->right) deq.push_front(node->right);
}
}
else {
for (int i = 0;i < n;i++) {//逆序 从容器头部取节点 同时将节点的左右孩子进行尾差插 防止影响头部
auto node = deq.front();
deq.pop_front();
tmp.push_back(node->val);
if (node->right) deq.push_back(node->right);
if (node->left) deq.push_back(node->left);
}
}
res.push_back(tmp);
flag = !flag;
}
return res;
}
5.20 二叉树的最近公共祖先
题解链接
解题思路:
1、对于父节点 若父节点等于p或q那么直接 那么父节点即为最近公共祖先
2、若父节点不为 p、q那么就去左右子树中寻找
3、若在左子树中没有找到p、q 即返回结果为空的话 那么必然在右子树中
若在右子树中没有找到p、q 即返回结果为空的话 那么必然在左子树中
4、若分别在左右子树中找到了p、q 那么说明父节点依然为最近公共祖先
递归解析:
1.终止条件:
a.当越过叶节点,则直接返回 null ;
b.当 root 等于 p,q ,则直接返回root ;
2.递推工作:
a.开启递归左子节点,返回值记为left ;
b.开启递归右子节点,返回值记为 right ;
3.返回值: 根据 left 和 rightr ,可展开为四种情况;
a.当 leftle和 right同时为空 :说明 root的左 / 右子树中都不包含 p,q ,返回 null ;
b.当 left和 right 同时不为空 :说明 p,q分列在 root的 异侧 (分别在 左 / 右子树),因此 rootr为最近公共祖先,返回 root ;
c.当 left为空 ,right 不为空 :p,q都不在 root的左子树中,直接返回 right 。具体可分为两种情况:
c_1.p,q其中一个在 root 的 右子树 中,此时 right 指向 p(假设为 p );
c_2.p,q 两节点都在 root 的 右子树 中,此时的 right 指向 最近公共祖先节点 ;
d.当 leftleftleft 不为空 , rightrightright 为空 :与情况 3. 同理;
TreeNode* lowestCommonAncestor(TreeNode* node, TreeNode* p, TreeNode* q) {
if (node == nullptr || node == p || node == q)return node;//若找到了 p或q则返回到上一层
TreeNode* left = lowestCommonAncestor(node->left, p, q);//对左孩进行p,q寻找 将找到的p,q返回给left
TreeNode* right = lowestCommonAncestor(node->right, p, q);//对右孩子进行p,q寻找 将找到的p,q返回给right
//注意:如果左树、右树均没有找到 肯定先对左树进行判断为空 然后返回了右树的结果也为空 逐层回溯最后到最远的祖先处 也返回了空
if (left == nullptr) {//若左树没找到 则返回右树结果
return right;
}
else if (right == nullptr) {//若右树没找到 则返回左树结果
return left;
}
else {//左树和右数分别找到了 p,q那么该节点即为p,q的最小公共祖先
return node;
}
}
5.21 螺旋矩阵
题目描述
解题思路:
用left,right,top,bottom分别标记当前的上下左右边界 每次边界访问完便将边界向相应的方向移动
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
int m = matrix.size();
if (m == 0)return res;
int n = matrix[0].size();
int top = 0, bottom = m - 1, left = 0, right = n - 1;
while (top<=bottom&&left<=right) {
for (int i = left;i <= right&&top<=bottom;i++)
{
res.push_back(matrix[top][i]);
}
top++;
for (int i = top;i <= bottom&&left<=right;i++) {
res.push_back(matrix[i][right]);
}
right--;
for (int i = right;i >= left&&top<=bottom;i--) {
res.push_back(matrix[bottom][i]);
}
bottom--;
for (int i = bottom;i >= top&&left<=right;i--) {
res.push_back(matrix[i][left]);
}
left++;
}
return res;
}
5.22 合并k个有序链表
题目链接
解题思想:将合并k个链表转化为合并后2个链表
ListNode* merge2Lists(ListNode* list1, ListNode* list2) {
ListNode* head=new ListNode();
if (!list1)return list2;
if (!list2)return list1;
ListNode* pre=head;
while (list1 && list2) {
if (list1->val < list2->val) {
ListNode* tmp = list1;
pre->next = tmp;
list1 = list1->next;
pre = tmp;
}
else {
ListNode* tmp = list2;
pre->next = tmp;
list2 = list2->next;
pre = tmp;
}
}
if (list1)pre->next = list1;
if (list2)pre->next = list2;
return head->next;
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode* res = nullptr;
for (auto list : lists) {
res = merge2Lists(res, list);
}
return res;
}
5.23 字符串相加
题目链接
解题关键:高位补0 进位add
1、从低位开始相加(并加上进位) 所得结果取个位存入字符 有进位则置add为1
2、移到高位以上以0补齐
3、终止条件:num1和num2均移到了高位以上 同时进位为0
string addStrings(string num1, string num2) {
int i = num1.size()-1;//由低位向高位移动
int j = num2.size()-1;
int add = 0;
string res;
while (i>=0||j>=0||add!=0) {//只要num1和num2还未移动到高位 以及进位不为0 则继续循环
int x = i >= 0 ? num1[i--] - '0' : 0;//当移动到最高位 再往高位移动由0补位
int y = j >= 0 ? num2[j--] - '0' : 0;
int tmp = x + y + add;//得到该位相加的结果
res.push_back('0' + tmp%10);//将结果的个位放入字符数组
add = tmp / 10;//获取进位
}
reverse(res.begin(), res.end());//将字符反转得到结果
return res;
}
5.24 最大递增子序列
题解链接
解题核心:动态规划 dp[i]记录以nums[i]结尾的最大子列长度(必须包含nums[i])
对于dp[i]:他的大小一定是基于dp[j] (0<=j<i)的
找到满足nums[j]<nums[i]的元素,能够使得dp[i]=dp[j]+1最大的情况 即为dp[i]的结果
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;//边界处理 空数组直接返回0
vector<int> dp(n, 1);//用动态规划数组dp[i]表示以nums[i]结尾的最大递归数组长度
int maxLen = 1;
//在dp[j],(0<=j<i)的基础上 若nums[i]>nums[j]那么dp[i]就是在dp[j]的基础上再加一
//逐个进行对比 直到找到满足nums[i]>nums[j]并且使得dp[i]=dp[j]+1最大的情况
for (int i = 1;i < n;i++) {
for (int j = 0;j < i;j++) {
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
maxLen = max(maxLen, dp[i]);
}
return maxLen;
}
5.25 环形链表入口位置
题解链接
解题关键:两个步长不一样的指针 fast (步长2) slow(步长1)
假设链表结构为a+b
1、当两指针相遇时,说明fast比slow多走了nb步 同时fast所走的路程又是slow的两倍 那么slow的路程就是nb 即slow走了n个环的路程:f=nb+s;f=2*s ==> s=nb;
2、此时让fast指向头节点 slow继续往下移动 那么下一次相遇即为入口节点
ListNode* detectCycle(ListNode* head) {
ListNode* fast=head;
ListNode* slow=head;
while (true) {
if (!fast || !fast->next) {
return nullptr;
}
fast = fast->next->next;
slow = slow->next;
if (fast == slow)break;
}
fast = head;
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return fast;
}
5.26 重排链表
题解链接
解题关键:用线性表存放链表节点 i左指针 j右指针 终止条件:左右指针相遇
void reorderList(ListNode* head) {
vector<ListNode*> list;//用于存放链表节点
ListNode* cur = head;
while (cur) {
list.push_back(cur);
cur = cur->next;
}
int n = list.size();
if (n <= 1)return;
int i = 0, j = n - 1;//i指向链表头节点 j指向链表尾节点 两者向内收缩
while (i < j) {//当节点数位为奇数时 的终止条件
list[i]->next = list[j];
i++;
if (i >= j) {//当节点数位偶数时 的终止条件
break;
}
list[j]->next = list[i];
j--;
}
list[i]->next = nullptr;//最后记得将尾节点加上空节点
}
5.27 接雨水
题解链接
解题思路:双指针 left指向左边只能向右移动 right指向左边只能向左移动 leftMax用于作为左边此时最大值 rightMax用于维护右边此时最大值
1、判断当前left、right所指位置是否分别大于leftMax、rightMax 若是则更新
2、判断当前leftMax和rightMax哪个更小一点?小的那个决定当前所能蓄水的量
leftMax<rightMax:那么下标left出所能接水量为leftMax-height[left] left++向右移动
leftMax>=rightMax:那么下标right处所能接水量为rightMax-height[right] right–向左移动
3、终止条件:left和right相遇
int trap(vector<int>& height) {
int res=0;
int n = height.size();
if (n <= 2)return res;
int left = 0, right = n - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if (leftMax< rightMax) {
res += leftMax - height[left++];
}
else {
res += rightMax - height[right--];
}
}
return res;
}
5.28 二叉树中的最大路径和
二叉树 abc,a 是根结点(递归中的 root),bc 是左右子结点(代表其递归后的最优解)。
最大的路径,可能的路径情况:
a
/ \
b c
1、b——a——c;
2、b——a——a的父节点;
3、c——a——c的父节点
对于情况1:不与父节点联系,或者a本身为根节点,此时无法进行递归但是,该路径也有可能为最大路径和
对于情况2、3:递归时计算a+b后者a+c取其中较大的值,也就是递归后返回的最优解
另外结点有可能是负值,最大和肯定就要想办法舍弃负值(max(0,x))。
但是上面 3 种情况,无论哪种,a 作为联络点,都不能够舍弃。
int maxPathSum(TreeNode* node, int& maxVal) {
if (!node)return 0;
int left = maxPathSum(node->left, maxVal);//左边路径返回的最优解
int right = maxPathSum(node->right, maxVal);//右边路径返回的最优解
int res = node->val + max(0, max(left, right));//取左右中的最大值 对应2、3情况
int lmr = node->val + max(0, left) + max(0, right);//对应1情况
maxVal = max(maxVal, lmr);//对于情况1的结果必然不小于 2、3的结果 故用lmr作比较就好
return res;
}
int maxPathSum(TreeNode* root) {
if (!root) return 0;
int maxVal=root->val;
maxPathSum(root, maxVal);
return maxVal;
}
5.29 删除链表的倒数第N个节点
题目链接
解题关键:用线性数组vector<ListNode*> list 存储链表节点
1、若倒数第N个节点正好为头节点 直接返回head->next
2、非头节点:list[n-size-1]->next=list[n-size]->next
ListNode* removeNthFromEnd(ListNode* head, int n) {
if (!head)return head;
ListNode* cur = head;
vector<ListNode*> list;
while (cur) {
list.push_back(cur);
cur = cur->next;
}
int size = list.size();
if (n == size)return head->next;
list[size - n - 1]->next = list[size - n ]->next;
return head;
}
5.30 编辑距离
题解链接
解题核心:动态规划 dp[i][j]表示word1前i个字符转变为word2前j个字符的编辑距离
1、初始化:由于word1、word2存在空字符的情况 那么,
word1前i个字符转变为word2前0个字符 全都是进行删操作 则dp[i][0]=i;
word1前0个字符转变为word2前j个字符 全都是进行增操作 则dp[0][j]=j;
2、对于dp[i][j]:有三种来源 取dp[i][j]=min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1
2.1、在dp[i-1][j+1]的基础上进行改操作
2.2、在dp[i-1][j]的基础上进行删操作
2.3、在dp[i][j-1]的基础上进行增操作
3、特殊情况:word1第i个字符与word2第j个字符相同 即word1[i-1]==word2[j-1]
此时可能不需要进行任何操作直接取dp[i][j]=min(dp[i][j],dp[i-1][j-1]) (第2步的基础上)
int minDistance(string word1, string word2) {
int minDis = 0;
int len1 = word1.size();
int len2 = word2.size();
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));//dp[i][j]表示word1前i个字符转变为word2前j个字符的最短编辑距离
//初始化word1前i个字符转变为word2前0个字符所需的最短编辑距离 全都是进行删操作
for (int i = 0;i < len1 + 1;i++) {
dp[i][0] = i;
}
//初始化word1前0个字符转变为word2前j个字符所需的最短编辑距离 全都是进行增操作
for (int j = 0;j < len2 + 1;j++) {
dp[0][j] = j;
}
for (int i = 1;i < len1 + 1;i++) {
for (int j = 1;j < len2 + 1;j++) {
//对于dp[i][j] 有三种来源
//1、在dp[i-1][j+1]的基础上进行改操作
//2、在dp[i-1][j]的基础上进行删操作
//3、在dp[i][j-1]的基础上进行增操作
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
//有一种特殊情况:当word1[i]==word2[j]时可能不需要进行任何操作直接取dp[i-1][j-1]
if (word1[i-1] == word2[j-1]) {//细节:word1的前i个字符的最后一个字符下标为i-1
dp[i][j] = min(dp[i][j], dp[i - 1][j - 1]);
}
}
}
return dp.back().back();
}
5.31 合并区间
题目链接
解题步骤:
1、将所有子区间进行排序:sort(intervals.begin(),intervals.end())
2、双指针:指向相邻的两个区间 j j+1
若j+1<n&&intervals[j][1]>=intervals[j+1][0],说明两个区间可以进行合并,那么将intervals[j+1]区间更新为融合后的区间;
若不能进行合并或者j+1==n,那么就将intervals[j]放入res区间数组中
3、对于最后一个区间:由于后面没有相邻区间了,直接加入res中,可以在第2步中,多加一个j+1<n的判断,若j+1=n的话直接进入第二条分支
vector<vector<int>> merge(vector<vector<int>>& intervals) {
int n = intervals.size();
if (n <= 1)return intervals;
vector<vector<int>> res;
sort(intervals.begin(), intervals.end());
for (int j = 0;j < n;j++) {
if (j+1<n&&intervals[j][1] >= intervals[j+1][0]) {//区间可以融合且j不是最后一个区间
intervals[j+1][0] = min(intervals[j][0], intervals[j+1][0]);
intervals[j+1][1] = max(intervals[j][1], intervals[j+1][1]);
}
else {//区间不能融合或者j为最后一个区间
res.push_back(intervals[j]);
}
}
return res;
}
5.32 二分法查找
int search(vector<int>& nums, int left, int right, int target) {
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] < target) {
return search(nums, mid + 1, right, target);
}
else {
return search(nums, left, mid - 1, target);
}
}
int search(vector<int>& nums, int target) {
return search(nums,0,nums.size()-1,target);
}
5.33 最长公共子序列
题解链接
解题关键:动态规划 dp[i][j]标记text1前i个字符与text2前j个字符的最大公共子序列
对于text1第i个字符text1[i-1]、text2的j个字符text[j-1]
若text1[i-1]==text2[j-1]:那么dp[i][j]正好是text1前i-1个字符与text2前j-1个字符的最大公共子序列子序列的基础上加1,即dp[i][j]=dp[i-1][j-1]+1;
若text1[i-1]!=text2[j-1]:dp[i][j]=max(dp[i][j-1],dp[i-1][j])
int longestCommonSubsequence(string text1, string text2) {
int len1 = text1.size();
int len2 = text2.size();
if (len1 == 0 || len2 == 0)return 0;
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));//动态规划 dp[i][j]表示text1前i个字符、text2前j个字符的最长公共子序列
int i = 0, j = 0;
for (i = 1;i < len1+1;i++) {
for (j = 1;j < len2+1;j++) {
if (text1[i-1] == text2[j-1]) {//若text1第i个字符与text2第j个字符相等
dp[i][j] = dp[i-1][j-1]+1;//那么dp[i][j]的大小正好为dp[i-1][j-1]+1
}
else {
dp[i ][j ] = max(dp[i][j-1], dp[i-1][j]);//否则取dp[i][j-1]、dp[i-1][j]中较大的那个
}
}
}
return dp.back().back();
}
5.34 用双栈实现队列
题目链接
解题思路:stack1 statck2
1、push操作:
开始时,stack1,stack2均为空,那么将第一个元素放入stack2中
放入超过一个元素时,先判断stack2是否为空,不为空就将元素放入stack1中
2、pop操作:
先判断statck2是否空,若为空那么现将stack1所有的元素转移到statck2中,再对statck2进行pop
若statck2不为空,那么直接从statck2中pop
3、peek操作
与pop操作类似,只是不将statck2栈顶元素弹出,而是直接返回栈顶元素
4、empty操作:
对statck1和stack2进行判空
class MyQueue {
public:
MyQueue() {
stc1 = new stack<int>();
stc2 = new stack<int>();
}
void push(int x) {
if (stc2->empty() && stc1->empty()) {//开始时,stack1,stack2均为空,那么将第一个元素放入stack2中
stc2->push(x);
}
else {//放入超过一个元素时,先判断stack2是否为空,不为空就将元素放入stack1中
stc1->push(x);
}
}
int pop() {
if (stc2->empty()) {//若statck2为空那么现将stack1所有的元素转移到statck2中,再对statck2进行pop
if (stc1->empty()) {
return -1;
}
else {//若statck2不为空,那么直接从statck2中pop
while (!stc1->empty()) {
stc2->push(stc1->top());
stc1->pop();
}
}
}
int top = stc2->top();
stc2->pop();
return top;
}
int peek() {
if (stc2->empty()) {
if (stc1->empty()) {
return -1;
}
else {
while (!stc1->empty()) {
stc2->push(stc1->top());
stc1->pop();
}
}
}
return stc2->top();
}
bool empty() {
return stc1->empty() && stc2->empty();
}
~MyQueue() {
delete stc1;
delete stc2;
}
private:
stack<int>* stc1;
stack<int>* stc2;
};
5.35 寻找两个正序数组的中位数
题解链接
解题关键:将寻找两个正序数组的中位数转化为寻找 两个数组中第k小的元素
1、双指针index1、index2 分别指向nums1、nums2开始寻找的位置
2、比较nums1从index1开始的第k/2-1个元素和nums2从index2开始的第k/2-1个元素的大小:
pivot1=min(index1+k/2,len1)-1;pivot2=min(index2+k/2,len2)-1
2.1 nums1[pivot1]<nums2[pivot2]:
那么舍弃掉nums1[index1…pivot1],让index1指向pivot1+1,即index1=pivot1+1,并减小k-=pivot1-index1-1;
2.2 nums1[pivot1]>nums2[pivot2]:
那么舍弃掉nums2[index2…pivot2],与2.1类似
2.2 nums1[pivot1]==nums2[pivot2]:
同2.1操作即可
3、边界处理问题:
3.1 当nums1中的元素全被丢弃了,即此时index1 = =len1,那么直接返回nums2此时的第k个元素即可;
3.2 nums2中的元素全被丢弃同3.1操作
3.3 当k= = 1时,直接返回此时nums1、nums2中较小的那个
int findKth(vector<int>& nums1,vector<int>& nums2,int k) {
int len1 = nums1.size();
int len2 = nums2.size();
int index1 = 0, index2 = 0;//分别指向数组nums1和nums2当前起始位置
while (true) {
//边界处理
if (index1 >= len1) {
return nums2[index2 + k-1];
}
if (index2 >= len2) {
return nums1[index1 + k-1];
}
if (k == 1) {
return min(nums1[index1], nums2[index2]);
}
int pivot1 = min(index1 + k / 2,len1)-1;//此时nums1的中心位置
int pivot2 = min(index2 + k / 2,len2)-1;//此时nums2的中心位置
//正常情况
if (nums1[pivot1] <= nums2[pivot2]) {//pivot1元素小于等于pivot2元素
int newIndex1 = pivot1+1;//丢弃pivot1之前的所有元素
k = k-(newIndex1- index1);//更新k值
index1 = newIndex1;//重置index1
}
else {
int newIndex2 = pivot2 + 1;
k = k - (newIndex2 - index2);
index2 = newIndex2;
}
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
int half = (m + n)/2;
if ((m + n) % 2 == 0) {
return (findKth(nums1, nums2, half) + findKth(nums1, nums2,half + 1)) / 2.0;
}
else {
return findKth(nums1, nums2, half+1);
}
}
5.36 二叉树的右视图
题目链接
解题思路:采用层序遍历的方式 每次将各层节点缓存入deque中,并读取当前层最后一个节点放入res中
void rightSideView(TreeNode* root, vector<int>& res) {
if (!root) return;
deque<TreeNode*> dq;
dq.push_back(root);//初始化将root节点放入deque
while (!dq.empty()) {
int curSize = dq.size();//当前deque中的元素个数
for (int i = 0;i < curSize;i++) {//取deque的队头 将队的左右节点放入队尾
TreeNode* node = dq.front();
dq.pop_front();
if (node->left)dq.push_back(node->left);
if (node->right)dq.push_back(node->right);
if (i == curSize - 1) res.push_back(node->val);//当取到当前层的队尾元素时 将值放入res
}
}
}
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
rightSideView(root, res);
return res;
}
5.37 删除排序链表中的重复元素 II
题解链接
解题思路:创建hair节点 连接下一个节点为head 单指针cur从hair开始后移
判断cur->next与cur->next->next的元素是否相等:
1、相等:缓存该值x,进入小循环将链表中所有x元素删除,cur指针不动
2、不相等:移动cur指针
ListNode* deleteDuplicates(ListNode* head) {
if (!head || !head->next)return head;
ListNode* hair = new ListNode(0, head);//下一个节点指向头节点
ListNode* cur = hair;
while (cur->next && cur->next->next) {
if (cur->next->val == cur->next->next->val) {
int x = cur->next->val;//缓存相同节点值
while (cur->next && cur->next->val == x) {//删除所有与x值相同的节点
cur->next = cur->next->next;
}
}
else {
cur = cur->next;
}
}
return hair->next;
}
5.38 复原IP地址
题目链接
解题思路:将IP地址字符串的划分 转化为 对某一数字的划分
1、采用递归回溯 将length分为4个数字 且每个数字的大小在1-3之间
2、根据所有可能的数字组合 将字符串进行剪切
3、对各子串进行判定
对于第1步:
前三次划分正常递归回溯 最后一次要进行特判:
length经过前三次的划分后最后剩下的长度len 若满足1<=len<=3,那么:
a、将其加入临时数组tmp;
b、将tmp加入res;
c、记得回溯,再将len弹出。
//采用递归回溯 将num分为4个数字 且每个数字的大小在1-3之间
//num被分的数字
//count标记当前所分次数
//tmp用于临时存放每一种可能的数字组合
//res用于存放所有符合要求的数字组合
void split_to_4(int num,int &count,vector<int>& tmp,vector<vector<int>> &res) {
if (count == 1) {//对于最后一次划分 由于前三次已经划分完 剩下的数字大小如果满足要求 则加入结果
if (num>0&&num<=3) {//若符合要求
tmp.push_back(num);//将最后的数字放入临时数组
res.push_back(tmp);//将该数字组合放入res中
tmp.pop_back();//回溯 记得将数字弹出临时数组
}
return;
}
if (num<=0)return ;
for (int i = 1;i <= 3;i++) {
tmp.push_back(i);
count--;
split_to_4(num - i, count, tmp, res);
count++;
tmp.pop_back();
}
}
vector<string> restoreIpAddresses(string s) {
int n = s.size();
vector<vector<int>> len_arr;
vector<int> tmp;
int count=4;
split_to_4(n, count, tmp, len_arr);
vector<string> res;
for (auto arr : len_arr) {//根据字符串长度所划分的所有子串长度组合
string s1 = s.substr(0, arr[0]);//剪切IP地址的各个子串
string s2 = s.substr(arr[0], arr[1]);
string s3 = s.substr(arr[0]+arr[1], arr[2]);
string s4 = s.substr(arr[0]+arr[1]+arr[2], arr[3]);
if (s1[0]=='0'&&s1.size()!=1||stoi(s1)>255) {//对各个子串进行判定
continue;
}
if (s2[0] == '0' && s2.size() != 1 || stoi(s2) > 255) {
continue;
}
if (s3[0] == '0' && s3.size() != 1 || stoi(s3) > 255) {
continue;
}
if (s4[0] == '0' && s4.size() != 1 || stoi(s4) > 255) {
continue;
}
res.push_back(s1 + "." + s2 + "." + s3 + "." + s4);
}
return res;
}
5.39 排序链表
题目链接
解题思路:分而治之 递归 二分链表 分别排序 再融合
将链表不停地打断成两份 直到链表为空、单节点、双节点为止
//递归:将链表不停地分为两段子链表 再将子链表进行排序融合
//边界条件:对于空节点以及单节点链表直接返回 对于只有两个节点的链表 返回排序后的链表
//链表中点:采用快指针和慢指针的方式寻找
//采用分而治之的方法
//融合排好序的链表
ListNode* merge2List(ListNode* list1, ListNode* list2) {
if (!list1)return list2;
if (!list2)return list1;
ListNode* dummy=new ListNode(0);
ListNode* cur = dummy;
while (list1&&list2) {
if (list1->val < list2->val) {
cur->next = list1;
list1 = list1->next;
cur = cur->next;
}
else {
cur->next = list2;
list2 = list2->next;
cur = cur->next;
}
}
if (list1) cur->next = list1;
if (list2) cur->next = list2;
return dummy->next;
}
//递归:将链表不停地分为两端子链表 再将子链表进行排序融合
//边界条件:对于空节点以及单节点链表直接返回 对于只有两个节点的链表 返回排序后的链表
//链表中点:采用快指针和慢指针的方式寻找
ListNode* sortList(ListNode* head) {
if (!head||!head->next)return head;//对于空节点以及单节点链表直接返回
if (!head->next->next) {//对于只有两个节点的链表 返回排序后的链表
if (head->val < head->next->val)return head;
else {
ListNode* tmp = head->next;
tmp->next = head;
head->next = nullptr;
return tmp;
}
}
ListNode* fast = head;//快指针
ListNode* slow = head;//慢指针
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
ListNode* mid = slow;//中间节点
ListNode* head2 = mid->next;
mid->next = nullptr;
return merge2List(sortList(head), sortList(head2));//递归排序再融合
}
5.40 爬楼梯
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//第n层楼梯 可以是由第n-1层跨一层而来或者由第n-2跨两层而来
//dp[n]=dp[n-1]+dp[n-2]
class Solution {
public:
//int climbStairs(int n) {
// if (n <= 1)return 1;
// vector<int> dp(n + 1, 1);
// for (int i = 2; i <= n; i++) {
// dp[i] = dp[i - 1] + dp[i - 2];
// }
// return dp[n];
//}
int climbStairs(int n) {
if (n <= 1)return 1;
int p = 1;
int q = 1;
int r = 1;
for (int i = 2; i <= n; i++) {
p = q;
q = r;
r = q + q;
}
return r;
}
};
5.41 括号生成
题目链接
解题思路:生成所有的括号序列,判断每一种情况是否为有效括号串,如果是则加入结果
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Solution {
public:
bool isValid(const string& str)
{
int balance = 0;
for (int i = 0; i < str.size(); i++)
{
if (str[i] == '(')balance++;
else balance--;
if (balance < 0)return false;
}
return balance == 0;
}
void generateValid(vector<string> &res,string ¤t ,int n) {
if (current.size() == n ) {
if (isValid(current)) {
res.push_back(current);
}
return;
}
current += '(';
generateValid(res, current, n);
current.pop_back();
current += ')';
generateValid(res, current, n);
current.pop_back();
}
vector<string> generateParenthesis(int n) {
vector<string> res;
string current;
generateValid(res, current, 2*n);
return res;
}
};
5.42 下一个排列
题目链接
解题思路:
1、从后往前找,找到第一个nums[i]<nums[i-1],并记录下此时nums[i]的迭代器it;
2、找出nums[i]~nums[n-1]中大于nums[i-1]的最小元素nums[k],swap(nums[i-1],nums[k]);
3、将从it所指元素到最后一个元素的子区间进行排序。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size();
if (n <= 1)return;
int i;
vector<int>::iterator it;
for (i = n - 1, it = nums.end() - 1; i-1 >= 0&&it!=nums.begin(); i--,it--) {
if (nums[i] <= nums[i - 1])continue;
int j = n - 1;
for (; j >= i; j--) {
if (nums[j] > nums[i - 1]) {
swap(nums[j], nums[i - 1]);
break;
}
}
break;
}
sort(it, nums.end());
}
};
int main()
{
vector<int> nums{1,2,3,4};
vector<int>::iterator it = nums.end() - 1;
cout << *it << endl;
return 0;
}
5.43 字符串转换整数(atoi)
题目链接
解题思路:创建一个状态表,key-当前字符对应的状态,value-下一个可能的状态
每一个时刻都有一个状态s,每当从字符串中读取一个字符c时,都会根据字符c转换到下一个状态s’,建立一个覆盖所有情况的从s与c映射到s’的映射表即可解决问题。
class AutoMaton
{
private:
int getCol(char c) {//判断当前处于的状态
if (isspace(c))return 0;
if (c == '+'||c=='-')return 1;
if (isdigit(c))return 2;
return 3;
}
public:
void get(char c) {//更新当前状态,并根据状态进行相应的操作
state = table[state][getCol(c)];
if (state == "signed") {//若为符号,则更新符号
if (c == '-')sign = -1;
}
if (state == "in_number") {//若为数字,则更新res
res = res * 10 + c - '0';
}
res = sign == -1 ? min(-(long long)INT_MIN, res) : min((long long)INT_MAX, res);
}
int sign = 1;//正负符号
long long res = 0;//数字部分的数值
private:
unordered_map<string, vector<string>> table = {//状态映射表
{"start",{"start","signed","in_number","end"}},
{"signed",{"end","end","in_number","end"}},
{"in_number",{"end","end","in_number","end"}},
{"end",{"end","end","end","end"}}
};
string state = "start";//状态
};
class Solution
{
public:
int myAtoi(string s) {
AutoMaton am;
for (auto c : s) {
am.get(c);
}
return (int)am.sign * am.res;
}
};
5.44 两数相加(链表)
题目链接
解题思路:
当前l1和l2节点为空的用0来补位,当前和节点数值为进位flag+l1节点+l2节点,判断是否有进位;
最后一个节点注意是否有进位。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* pre=new ListNode(-1);
ListNode* cur=pre;
int flag=0;
while(l1||l2){
int num1=0,num2=0;
if(l1){
num1=l1->val;
}
if(l2){
num2=l2->val;
}
int num=num1+num2+flag;
flag=num/10>0?1:0;
num=num%10;
ListNode* node=new ListNode(num);
cur->next=node;
cur=cur->next;
if(l1)l1=l1->next;
if(l2)l2=l2->next;
}
if(flag>0){
ListNode* node=new ListNode(flag);
cur->next=node;
}else{
cur->next=nullptr;
}
return pre->next;
}
};
5.45 滑动窗口最大值
题目链接
解题思路:利用优先队列来解决
对于「最大值」,我们可以想到一种非常合适的数据结构,那就是优先队列(堆),其中的大根堆可以帮助我们实时维护一系列元素中的最大值。
为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素num 在数组中的下标为index。
初始时,先将前k个元素放入堆中,此时堆顶元素为第一个窗口中最大值。将窗口向右移动,将新值存入堆中:
新值大于前一个窗口中最大值的话,那么新值会直接放入堆顶;
新值若小于前一个最大值,那么不停地去除堆顶中不在窗口范围的值,直到堆顶元素属于当前窗口范围,那么将此时的堆顶输出。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> res;
//优先队列 pair中key为nums元素值,value为nums元素下标
priority_queue<pair<int, int>> que;
for (int i = 0; i < k;i++ ) {//先将第一个滑动窗口中所有元素放入堆
que.emplace( nums[i],i);
}
res.push_back(que.top().first);//将堆顶元素放入结果
for (int i = k ; i < nums.size(); i++) {
que.emplace(nums[i],i);//将当前窗口最右端元素放入堆中
while (que.top().second < i - k+1)que.pop();//去除所有不在窗口范围内的堆顶元素
res.push_back(que.top().first);
}
return res;
}
};
5.46 比较版本号
题目链接
解题思路:将两个版本分别放入两个字符流iss1,iss2,然后直接从字符流分别读取各下标处的修订号
1.将version1、version2中所有的‘.’都替换成空格符,方便字符流读取数字;
2.将读取到的修订号分别存入arr1,arr2;
3.按顺序对比arr1和arr2中的元素,元素个数不对齐用0来补,比较过程中:
num1>num2,返回1;num1<num2,返回2;
4.arr1和arr2的迭代器均指向end(),循环终止,返回0。
class Solution {
public:
int compareVersion(string version1, string version2) {
istringstream iss1(version1);//字符流1
istringstream iss2(version2);//字符流2
vector<int> arr1;//存放字符流1中所有的数字
vector<int> arr2;//存放字符流2中所有的数字
int num;
for (int i = 0; i < version1.size();i++) {//将版本号中所有的‘.’都换成空格,方便从字符流中直接读取数字
if (version1[i] == '.') {
version1[i] = ' ';
}
}
for (int i = 0; i < version2.size(); i++) {
if (version2[i] == '.') {
version2[i] = ' ';
}
}
while (iss1 >> num) arr1.push_back(num);
while (iss2 >> num)arr2.push_back(num);
auto it1 = arr1.begin();//指向数组1的迭代器
auto it2 = arr2.begin();
while (it1 != arr1.end() || it2 != arr2.end()) {
int num1 = 0, num2 = 0;
if (it1 != arr1.end()) {
num1 = *it1;
it1++;
}
if (it2 != arr2.end()) {
num2 = *it2;
it2++;
}
if (num1 > num2)return 1;
if (num1 < num2)return -1;
}
return 0;
}
};
5.47.缺失的第一个最小整数
题目链接
解题思路:
对于一个长度为n的数组来说,其中缺失的第一个最小整数一定在1~n范围内,那么可以通过交换元素位置的方式,将数组中所有1 ~n范围内的元素放入到对应的位置。
细节点:在进行swap(nums[i],nums[nums[i]-1])时,应使用while循环,直到nums[i]都处于对应位置或者nums[i]超出1~n范围。
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++) {//调整1~n范围内元素的位置
if (nums[i] > 0 && nums[i] <= nums.size() && nums[i] != nums[nums[i] - 1]) {
swap(nums[i], nums[nums[i] - 1]);
}
}
for (int i = 0; i < nums.size(); i++) {//找出第一个不在对应位置的数
if (nums[i] != i + 1)
return i + 1;
}
return nums.size() + 1;//若所有数都在对应位置,那么缺失的最小整数为n+1
}
};
5.48 最大连通域
题目描述:一个由0和1组成的m行n列矩阵,找出其中最大的1连通域,并返回连通域的大小
解题思路:动态规划,使用dp[i][j]=1来标记matrix[i][j]已经被访问过,递归函数visited,
递归内容:对于访问过的元素直接返回;对于未访问过的元素,先将dp[i][j]置为1,然后判断元素是否为1,若为1则让连通域加1,再继续递归访问上下左右元素,否则,直接返回。
class Solution
{
public:
void visited(vector<vector<int>>& mat, vector<vector<int>> &dp,int &area,int r,int c) {
if (dp[r][c] == 1)return;
dp[r][c] = 1;
if (mat[r][c] == 1)area++;
else return;
if (r + 1 < mat.size())visited(mat, dp, area, r + 1, c);
if (c + 1 < mat[0].size())visited(mat, dp, area, r, c + 1);
if (r - 1 >=0 )visited(mat, dp, area, r - 1, c);
if (c - 1 >=0)visited(mat, dp, area, r, c - 1);
}
int findMaxRegion(vector<vector<int>>& matrix) {
int m = matrix.size();
if (m == 0)return 0;
int n = matrix[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
int maxArea = 0;
for (int i = 0;i < matrix.size();i++) {
for (int j = 0;j < matrix[0].size();j++) {
int area = 0;
visited(matrix, dp, area, i, j);
maxArea = max(maxArea, area);
}
}
return maxArea;
}
};
5.49 判断平衡二叉树
题目链接
解题思路:采用递归方式比较每个节点的左右子树长度
对于当前节点:
终止条件:节点为空,直接返回0
递归体:获取左右子树长度,若左右子树长度均非负,且两长度差值不超过1,则返回max(leftLen,rightLen)+1;
最后不符合条件返回-1。
class Solution {
public:
int abs(int a, int b) {
return a > b ? a - b : b - a;
}
int getTreeLength(TreeNode* root){
if(!root)return 0;
int leftLen=getTreeLength(root->left);
int rightLen=getTreeLength(root->right);
if(leftLen>=0&&rightLen>=0&&abs(leftLen,rightLen)<=1)
return max(leftLen,rightLen)+1;
return -1;
}
bool isBalanced(TreeNode* root) {
if(!root)return true;
return getTreeLength(root)>=0;
}
};
5.50 背包问题
背包重量为N,有m种物品,物品重量分别为weights[i],物品价值为values[i]
1、01背包:每种物品只有一件,问如何装可以使价值最高
方法一:二维数组dp[i][j],表示容量为j时能装入前i个物品的总价值,状态转化方程为
dp[i][j]=j>=weights[i]?max(dp[i-1][j],dp[i-1][j-weights[i]]+value[i]):dp[i-1][j](先遍历物品还是先遍历容量均可)
方法二:一维数组dp[j],表示容量j时能装入的最大总价值,状态转化方程为
dp[j]=j>=weights[i]?max(dp[j],dp[j-weights[i]]+value[i]):dp[j];(必须先遍历物品,再遍历容量,且容量要从大到小进行遍历)
2、完全背包:每种物品数量不限,问如何装可以使价值最高
一维数组dp[j],表示容量j时能装入的最大总价值,状态转化方程为dp[j]=j>=weights[i]?max(dp[j],dp[j-weights[i]]+value[i]):dp[j];(先遍历物品还是容量均可,但容量遍历顺序从小到大)
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
int N, m;
cin >> N >> m;
vector<int> dp(N + 1);
vector<int> weights(m + 1, 0);
vector<int> value(m + 1, 0);
for (int i = 1;i <= m;i++) {
int p, v;
cin >> p >> v;
weights[i] = p;
value[i] = v;
}
//完全背包:
//for (int i = 1;i <= m;i++) {
// for (int j = prices[i];j <= N;j++) {
// dp[j] = max(dp[j], dp[j - weights[i]] + value[i]);
// }
//}
//01背包
for (int i = 1;i <= m;i++) {
for (int j = N;j >= 0;j--) {
dp[j] = j>= weights[i]?max(dp[j], dp[j - weights[i]] + value[i]):dp[j];
}
}
cout << dp[N] << endl;
}