【数据结构与算法刷题笔记】

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 &current ,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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 基础知识扎实:首先要打好计算机基础知识的基础,如数据类型、算法思想、时间复杂度等等。建议通过课程、书籍等途径系统地学习。 2. 刻意练习:对于数据结构与算法来说,刻意练习是很重要的。可以通过刷题、参加比赛等方式来提高自己的水平。 3. 多做笔记:学习过程中,多做笔记可以帮助巩固知识点,方便日后复习回顾。 4. 参加社区:在社区中可以与其他学习者交流,分享自己的学习心得,也可以从其他人的经验中学习到更多的知识。 5. 不断学习:数据结构与算法是一个不断学习的过程,需要不断地学习新的知识点,更新自己的知识储备。 关于如何正常使用力扣,可以参考以下几点: 1. 从简单到复杂:在刷题的过程中,建议从简单的题目开始,逐步提高难度,这样可以让自己逐渐适应力扣的题目难度。 2. 掌握基础知识:在刷题之前,先掌握一些基础的算法和数据结构知识,这样可以更好地理解题目,提高解题效率。 3. 多看题解:在力扣上,每个题目都有很多人提交过自己的解答,可以多看一些高赞的题解,从中学习新的解题思路。 4. 坚持刷题:在力扣上,坚持刷题是非常重要的,可以通过每天刷一定数量的题目来提高自己的水平。 5. 不要过于依赖代码:在刷题的过程中,不要过于依赖他人的代码,要尽可能地自己思考和解决问题,这样可以更好地提高自己的解题能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值