注:~【完成】代表还有一些方法没看,最后再看
1、for(;;)中的;经常写成,
2、true 的拼写错误
3、while 的离开循环条件忘写,陷入死循环
4、return忘写
5、不能使用关键字命名变量
6、链表的遍历用while,数组的遍历用for
7、C++负数不支持移位运算符
1、vector排序的写法:
Arrays.sort(nums);
sort(nums.begin(),nums.end());
2、int与string的互相转换:
int a;
string s;
s = to_string(a);
a = stoi(s);
3、注意(n&(n-1))==0外面的括号不能删
4、return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0
排除 n<=0 的情况,**易错点**,顺序不能写错,因为是按照写的顺序依次判断条件是否成立的
5、for(int num:nums){
num=1;
}//采用这种方式无法改变数组中的值
6、reverse(a.begin(),a.end())//翻转数组或者string
7、temp.resize((int)nums.size(), 0);//重新定义字符串的长度
//存在一个问题,当输入数组只有两个元素时就会出错
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int n=(int)numbers.size();
int mid=target/2;
int i,j,pivot;
vector<int> ret = {0,0};
for(i=0;i<n && numbers[i]<=mid;i++){
if(numbers[i+1]>=mid)
pivot=i;
}
for(i=0;i<=pivot;i++){
int left=numbers[i];
for(j=pivot+1;j<n;j++){
int right=numbers[j];
if(left+right==target){
ret[0]=i+1;
ret[1]=j+1;
}
}
}
return ret;
}
};
//改进后 [12344567]8这个实例会出错,这个是错误代码
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int n=(int)numbers.size();
vector<int> ret = {0,0};
if(n==2){
ret[0]=1;
ret[1]=2;
return ret;
}
int mid=target/2;
int i,j,pivot;
for(i=0;i<n;i++){
if(numbers[i]<=mid && numbers[i+1]>=mid){
pivot=i;
break;
}
}
for(i=0;i<=pivot;i++){
int left=numbers[i];
for(j=pivot+1;j<n;j++){
int right=numbers[j];
if(left+right==target){
ret[0]=i+1;
ret[1]=j+1;
}
}
}
return ret;
}
};
//Leetcode 官方解答
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
for (int i = 0; i < numbers.size(); ++i) {//每次固定一个i,然后通过二分查找法去找另一个数
int low = i + 1, high = numbers.size() - 1;//此处有双指针,low,high,用于限定除了numbers[i]的另一个数的搜索范围
while (low <= high) {
int mid = (high - low) / 2 + low;//这个本质就是求中点的公式,(high+low)/2
if (numbers[mid] == target - numbers[i]) {
return {i + 1, mid + 1};
} else if (numbers[mid] > target - numbers[i]) {
high = mid - 1;//缩小一般的搜索范围
} else {
low = mid + 1;
}
}
}
return {-1, -1};
}
};
一张图告诉你 O(n) 的双指针解法的本质原理
//双指针法
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int left, right;
int n=(int)numbers.size();
vector<int> ret={0,0};
for(left=0,right=n-1;left<right;){
if(numbers[left]+numbers[right]<target)
left++;
else if(numbers[left]+numbers[right]>target)
right--;
else{
ret[0]=left+1;
ret[1]=right+1;
break;
}
}
return ret;
}
};
//Leetcode 官方解答
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int low = 0, high = numbers.size() - 1;
while (low < high) {
int sum = numbers[low] + numbers[high];
if (sum == target) {
return {low + 1, high + 1};//注意这种写法
} else if (sum < target) {
++low;
} else {
--high;
}
}
return {-1, -1};//注意这种写法,表示程序出错,返回错误
}
};
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
unordered_map<int,int> hashtable;
for(int i=0;i<numbers.size();i++){
auto t=hashtable.find(target-numbers[i]);
if(t!=hashtable.end()){
return {++t->second,++i};
}
hashtable[numbers[i]]=i;//注意哈希表的赋值方式
}
return {};
}
};
//注意使用 long
class Solution {
public:
bool judgeSquareSum(int c) {
long r=(int)sqrt(c);
long l=0;
while(l<=r){
long result=l*l+r*r;
if(result==c)
return true;
else if(result>c)
r--;
else
l++;
}
return false;
}
};
class Solution {
public:
bool decide(char c){
return c == 'a' || c == 'o' || c == 'e' || c == 'i' || c == 'u' ||
c == 'A' || c == 'O' || c == 'E' || c == 'I' || c == 'U';
}
string reverseVowels(string s) {
int left,right;
left=0;
right=s.length()-1;
while(left<right){
while(left<right&&!decide(s[left])){left++;}//一开始这里的判断条件中少了left
while(left<right&&!decide(s[right])){right--;}
char temp=s[left];
s[left]=s[right];
s[right]=temp;
left++;
right--;
}
return s;
}
};
//融合了别人的代码
class Solution {
public:
bool decide(char c){
return c == 'a' || c == 'o' || c == 'e' || c == 'i' || c == 'u' ||
c == 'A' ||
c == 'O' || c == 'E' || c == 'I' || c == 'U';
}
string reverseVowels(string s) {
int left,right;
left=0;
right=s.length()-1;
while(left<right){
if(!decide(s[left])){
left++;
continue;
}
else if(!decide(s[right])){
right--;
continue;
}
// while(!decide(s[left])){left++;continue;}
// while(!decide(s[right])){right--;continue;}
else{
char temp=s[left];
s[left]=s[right];
s[right]=temp;
}
left++;
right--;
}
return s;
}
};
class Solution {private: unordered_set<char> ss{'a','e','i','o','u','A','E','I','O','U'};public: string reverseVowels(string s) { int i = 0; int j = s.size() - 1; while (i < j) { while (i < j && ss.find(s[i]) == ss.end()) { ++i; } while (i < j && ss.find(s[j]) == ss.end()) { --j; } if (i < j) { swap(s[i], s[j]); ++i; --j; } } return s; }};
用于求解 Kth Element 问题,也就是第 K 个元素的问题。
可以使用快速排序的 partition() 进行实现。需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
用于求解 TopK Elements 问题,也就是 K 个最小元素的问题。使用最小堆来实现 TopK 问题,最小堆使用大顶堆来实现,大顶堆的堆顶元素为当前堆的最大元素。实现过程:不断地往大顶堆中插入新元素,当堆中元素的数量大于 k 时,移除堆顶元素,也就是当前堆中最大的元素,剩下的元素都为当前添加过的元素中最小的 K 个元素。插入和移除堆顶元素的时间复杂度都为 log2N。
堆也可以用于求解 Kth Element 问题,得到了大小为 K 的最小堆之后,因为使用了大顶堆来实现,因此堆顶元素就是第 K 大的元素。
快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。
可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
215. 数组中的第K个最大元素 中等
347. 前 K 个高频元素 中等
451. 根据字符出现频率排序 中等
荷兰国旗包含三种颜色:红、白、蓝。
有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。
75. 颜色分类 简单
单指针法
class Solution {public: void sortColors(vector<int>& nums) { int n = nums.size(); int ptr = 0; for (int i = 0; i < n; ++i) { if (nums[i] == 0) { swap(nums[i], nums[ptr]); ++ptr; } } for (int i = ptr; i < n; ++i) { if (nums[i] == 1) { swap(nums[i], nums[ptr]); ++ptr; } } }};作者:LeetCode-Solution链接:https://leetcode-cn.com/problems/sort-colors/solution/yan-se-fen-lei-by-leetcode-solution/来源:力扣(LeetCode)著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【patition法】 (注意while和if的顺序不能颠倒)
class Solution {public: void sortColors(vector<int>& nums) { int n=nums.size(); int p0=0,p2=n-1; for(int i=0;i <= p2;i++){ while(nums[i]==2&&i <= p2){//而数组末端的情况我 们是不知道的,所以就需要用while,防止换过来的也是2 swap(nums[i],nums[p2]); p2--; } if (nums[i] == 0) {//因为i是从左边开始遍历的,所以遍历过的地方一定没有0,不存在交换nums[p0]和nums[i]之后nums[i]还是等于0的情况 swap(nums[i], nums[p0]); ++p0; } } }};
保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
455. 分发饼干 简单
排序+贪心
class Solution {public://双指针法 int findContentChildren(vector& g, vector& s) { int ng=g.size(); int ns=s.size(); sort(g.begin(),g.end());//排序 sort(s.begin(),s.end()); int i=0,j=0,c=0; while(i=g[i]){ i++; j++; c++; } else j++; } return c; }};
435. 无重叠区间 中等
452. 用最少数量的箭引爆气球 中等
蓝色荧光笔的字可以这么去理解:如果将该箭移到该箭可以射爆的气球中最左的右边界后不能射爆其他的气球的话,那这支箭在移动前也不能射爆那些气球。
知识点回顾:
lambda表达式
语法形式:[函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}
C++中的lambda表达式详解
class Solution {public: int findMinArrowShots(vector<vector<int>>& points) {// vector>& points 代表每个 vector 容器中都有两个元素,每个两个元素的 vector 容器又被存储到外层 vector 中 if (points.empty()) { return 0; } sort(points.begin(), points.end(), [](const vector& u, const vector& v) ->bool{ return u[1] < v[1];//lambda表达式 }); int pos = points[0][1]; int ans = 1; for (const vector& balloon: points) { if (balloon[0] > pos) { pos = balloon[1]; ++ans; } } return ans; }};
406. 根据身高重建队列 中等
121. 买卖股票的最佳时机 简单
122. 买卖股票的最佳时机 II 简单
605. 种花问题 简单
392. 判断子序列 简单
665. 非递减数列 简单
53. 最大子序和 简单
763. 划分字母区间 简单
正常实现
Input : [1,2,3,4,5]key : 3return the index : 2public int binarySearch(int[] nums, int key) { int l = 0, h = nums.length - 1; while (l <= h) { int m = l + (h - l) / 2; if (nums[m] == key) { return m; } else if (nums[m] > key) { h = m - 1; } else { l = m + 1; } } return -1;}
时间复杂度
二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度为 O(logN)。
m 计算
有两种计算中值 m 的方式:
l + h 可能出现加法溢出,也就是说加法的结果大于整型能够表示的范围。但是 l 和 h 都为正数,因此 h - l 不会出现加法溢出问题。所以,最好使用第二种计算法方法。
未成功查找的返回值
循环退出时如果仍然没有查找到 key,那么表示查找失败。可以有两种返回值:
变种
二分查找可以有很多变种,实现变种要注意边界值的判断。例如在一个有重复元素的数组中查找 key 的最左位置的实现如下:
public int binarySearch(int[] nums, int key) { int l = 0, h = nums.length; while (l < h) { int m = l + (h - l) / 2; if (nums[m] >= key) { h = m; } else { l = m + 1; } } return l;}
该实现和正常实现有以下不同:
在 nums[m] >= key 的情况下,可以推导出最左 key 位于 [l, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解。
在 h 的赋值表达式为 h = m 的情况下,如果循环条件为 l <= h,那么会出现循环无法退出的情况,因此循环条件只能是 l < h。以下演示了循环条件为 l <= h 时循环无法退出的情况:
nums = {0, 1, 2}, key = 1l m h0 1 2 nums[m] >= key0 0 1 nums[m] < key1 1 1 nums[m] >= key1 1 1 nums[m] >= key...
当循环体退出时,不表示没有查找到 key,因此最后返回的结果不应该为 -1。为了验证有没有查找到,需要在调用端判断一下返回位置上的值和 key 是否相等。
求开方的思想就是不断的变更搜索范围,通过不断变更 L 和 R 这两个左右边界进行。
注意事项:这题千万要注意边界条件
69. x 的平方根 简单
【二分查找法】
向上取整公式:
L + ( R − L + 1 ) / 2 ( 其 中 的 R − L + 1 就 是 数 组 的 元 素 的 数 量 ) L+(R-L+1)/2 (其中的R-L+1就是数组的元素的数量) L+(R−L+1)/2(其中的R−L+1就是数组的元素的数量)
向下取整公式:
L + ( R − L ) / 2 L+(R-L)/2 L+(R−L)/2
//力扣官解class Solution {public: int mySqrt(int x) { int l = 0, r = x, ans = -1; while (l <= r) { int mid = l + (r - l) / 2; if ((long long)mid * mid <= x) { ans = mid; l = mid + 1; } else { r = mid - 1; } } return ans; }};
//简化版的二分查找法(自己写的)class Solution {public: int mySqrt(int x) { int l=0; int right = x / 2; if(x==0) return 0; if(x==1) return 1; while(lx/mid){ r=mid-1; } else{ l=mid; } } return l; }};
//简化版的二分查找法(别人写的java版)public class Solution { public int mySqrt(int x) { // 特殊值判断 if (x == 0) { return 0; } if (x == 1) { return 1; } int left = 1; int right = x / 2; // 在区间 [left..right] 查找目标元素 while (left < right) { int mid = left + (right - left + 1) / 2; // 注意:这里为了避免乘法溢出,改用除法 if (mid > x / mid) { // 下一轮搜索区间是 [left..mid - 1] right = mid - 1; } else { // 下一轮搜索区间是 [mid..right] left = mid; } } return left; }}
【牛顿迭代法】
// fabs() 求绝对值//官解牛顿迭代class Solution {public: int mySqrt(int x) { if (x == 0) { return 0; } double C = x, x0 = x; //x0是选定的迭代初始值,C就是x(注意是double) while (true) { //一直执行到 break 为止 double xi = 0.5 * (x0 + C / x0); //新的迭代解 if (fabs(x0 - xi) < 1e-7) { //计算误差 break; } x0 = xi; //更新迭代解 } return int(x0); }};
//精简版牛顿迭代(这个不是很好理解)class Solution {public: int mySqrt(int x) { if (x == 0) return 0; double t=x; while (t*t-x>=1) t=0.5*(t+x/t);//这个判断条件为什么是 1, 没看懂 return int(t); }};
744. 寻找比目标字母大的最小字母 简单
540. 有序数组中的单一元素 中等
278. 第一个错误的版本 简单
153. 寻找旋转排序数组中的最小值 中等
34. 在排序数组中查找元素的第一个和最后一个位置 中等
241. 为运算表达式设计优先级 中等
// substr的用法
- 用途:一种构造string的方法
- 形式:s.substr(pos, n)
- 解释:返回一个string,包含s中从pos开始的n个字符的拷贝(pos的默认值是0,n的默认值是s.size() - pos,即不加参数会默认拷贝整个s)
- 补充:若pos的值超过了string的大小,则substr函数会抛出一个out_of_range异常;若pos+n的值超过了string的大小,则substr会调整n的值,只拷贝到string的末尾
// push_back()函数的用法
函数将一个新的元素加到vector的最后面,位置为当前最后一个元素的下一个元素
官解(加了自己的注释)
class Solution {public: vector<int> diffWaysToCompute(string expression) { // 存储中间值 vector count; for(int i = 0; i < expression.size(); i ++) {//这个for相当于遍历了所有的情况 char c = expression[i]; // 找到运算符 if(c == '+' || c == '-' || c == '*') { // 运算符左边的运算结果 vector left = diffWaysToCompute(expression.substr(0, i));//从0开始的i个元素,刚好是0 ~ i-1 // 运算符右边的运算结果 vector right = diffWaysToCompute(expression.substr(i + 1));//从n+1到string末尾 // 左右两边继续运算 for(int& l : left) { for(int& r : right) { switch(c) { case '+': count.push_back(l + r);//看起来 count 一直有数据 put_back 进去,其实都作为返回值 return 进入 l 或者 r 中了,所以外层的 count 中是没有值的 break; case '-': count.push_back(l - r); break; case '*': count.push_back(l * r); break; } } } } } // count为空说明当前无运算符,只是单独的数字,直接放入count中 if(count.size() == 0) { count.push_back(stoi(expression));//stoi()是C++的字符处理函数,把数字字符串转换成int输出 } return count; }};
别人的解法(用 if 代替switch,看起来简洁)
class Solution {public: vector<int> diffWaysToCompute(string input) { vector<int> vec1, vec2, res; int n = input.size(); int flag = 0; for(int i=0; i<n; i++){ if(input[i] == '+' || input[i] == '-' || input[i] == '*'){ flag = 1; // flag=1说明string是表达式,flag=0说明string是一个数字 vec1 = diffWaysToCompute(string(input, 0, i)); // 从第0个开始,存i个字符 vec2 = diffWaysToCompute(string(input, i+1, n-i-1)); for(int v1:vec1){ for(int v2:vec2){ if(input[i] == '+') res.push_back(v1+v2); if(input[i] == '-') res.push_back(v1-v2); if(input[i] == '*') res.push_back(v1*v2); } } } } if(flag==0) return {std::stoi(input)}; return res; }};
#include #include #include #include class Solution {public: std::vector<int> diffWaysToCompute(std::string input) { int number = 0; if (tryIfNumber(input, &number)) { return {number}; } std::vector<int> result; for (auto i = 0; i < input.size(); ++i) { auto c = input[i]; if (isOperator(c)) { auto leftResult = diffWaysToCompute(input.substr(0, i)); auto rightResult = diffWaysToCompute(input.substr(i+1)); for (const auto &left : leftResult) { for (const auto &right : rightResult) { if (c == '+') { result.emplace_back(left + right); } else if (c == '-') { result.emplace_back(left - right); } else if (c == '*') { result.emplace_back(left * right); } } } } } return result; }private: bool tryIfNumber(const std::string &str, int *number) const { *number = 0; for (const auto &c : str) { if (std::isdigit(c)) { *number = *number * 10 + (c - '0'); } else { *number = 0; return false; } } return true; } bool isOperator(const char &c) const { return c == '+' || c == '-' || c == '*'; }};int main() { Solution sln; std::vector<std::string> inputs{"2-1-1", "2*3-4*5"}; auto print_v = [](const std::vector<int> &computes) { std::cout << "["; for (const auto &c : computes) { std::cout << c << ", "; } std::cout << "]" << std::endl; }; for (auto &input : inputs) { print_v(sln.diffWaysToCompute(input)); }}
95. 不同的二叉搜索树 II 中等
深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。
广度优先搜索一层一层地进行遍历,每层遍历都是以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
第一层:
第二层:
第三层:
每一层遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di <= dj。利用这个结论,可以求解最短路径等 最优解 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径,无权图是指从一个节点到另一个节点的代价都记为 1。
在程序实现 BFS 时需要考虑以下问题:
1091. 二进制矩阵中的最短路径
279. 完全平方数
127. 单词接龙
广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。
而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。
从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 可达性 问题。
在程序实现 DFS 时需要考虑以下问题:
695. 岛屿的最大面积 中等
200. 岛屿数量 中等
547. 省份数量
130. 被围绕的区域
417. 太平洋大西洋水流问题
Backtracking(回溯)属于 DFS。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
17. 电话号码的字母组合
93. 复原 IP 地址
79. 单词搜索
257. 二叉树的所有路径
46. 全排列
47. 全排列 II
77. 组合
39. 组合总和
40. 组合总和 II
216. 组合总和 III
78. 子集
90. 子集 II
131. 分割回文串
37. 解数独
51. N 皇后
递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。
70. 爬楼梯 简单
动态规划(滚动数组)
class Solution {public: int climbStairs(int n) { int p=0; int q=1; int r=p+q; for(int i=2;i<=n;i++){ p=q; q=r; r=p+q; } return r; }};
带记忆的递归
斐波那契数列通项公式法
class Solution {public: int climbStairs(int n) { double sqrt5 = sqrt(5); double fibn = pow((1 + sqrt5) / 2, n + 1) - pow((1 - sqrt5) / 2, n + 1); return (int)round(fibn / sqrt5); }};
198. 打家劫舍 中等
213. 打家劫舍 II 中等
[程序员代码面试指南-P181](https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode 题解 - 动态规划.md#)
64. 最小路径和 中等
62. 不同路径 中等
303. 区域和检索 - 数组不可变 简单
413. 等差数列划分 中等
343. 整数拆分 中等
279. 完全平方数 中等
91. 解码方法 中等
已知一个序列 {S1, S2,…,Sn},取出若干数组成新的序列 {Si1, Si2,…, Sim},其中 i1、i2 … im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 子序列 。
如果在子序列中,当下标 ix > iy 时,Six > Siy,称子序列为原序列的一个 递增子序列 。
定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。对于一个递增子序列 {Si1, Si2,…,Sim},如果 im < n 并且 Sim < Sn,此时 {Si1, Si2,…, Sim, Sn} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 Sn 就构成了以 Sn 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。
因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {Sn} 就构成了递增子序列,需要对前面的求解方程做修改,令 dp[n] 最小为 1,即:
对于一个长度为 N 的序列,最长递增子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,max{ dp[i] | 1 <= i <= N} 即为所求。
300. 最长递增子序列 中等
646. 最长数对链 中等
376. 摆动序列 中等
对于两个子序列 S1 和 S2,找出它们最长的公共子序列。
定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况:
综上,最长公共子序列的状态转移方程为:
对于长度为 N 的序列 S1 和长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。
与最长递增子序列相比,最长公共子序列有以下不同点:
1143. 最长公共子序列 中等
有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。
定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论:
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:
// W 为背包总体积// N 为物品数量// weights 数组存储 N 个物品的重量// values 数组存储 N 个物品的价值public int knapsack(int W, int N, int[] weights, int[] values) { int[][] dp = new int[N + 1][W + 1]; for (int i = 1; i <= N; i++) { int w = weights[i - 1], v = values[i - 1]; for (int j = 1; j <= W; j++) { if (j >= w) { dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + v); } else { dp[i][j] = dp[i - 1][j]; } } } return dp[N][W];}
空间优化
在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅与前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,
因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。
public int knapsack(int W, int N, int[] weights, int[] values) { int[] dp = new int[W + 1]; for (int i = 1; i <= N; i++) { int w = weights[i - 1], v = values[i - 1]; for (int j = W; j >= 1; j--) { if (j >= w) { dp[j] = Math.max(dp[j], dp[j - w] + v); } } } return dp[W];}
无法使用贪心算法的解释
0-1 背包问题无法使用贪心算法来求解,也就是说不能按照先添加性价比最高的物品来达到最优,这是因为这种方式可能造成背包空间的浪费,从而无法达到最优。考虑下面的物品和一个容量为 5 的背包,如果先添加物品 0 再添加物品 1,那么只能存放的价值为 16,浪费了大小为 2 的空间。最优的方式是存放物品 1 和物品 2,价值为 22.
id | w | v | v/w |
---|---|---|---|
0 | 1 | 6 | 6 |
1 | 2 | 10 | 5 |
2 | 3 | 12 | 4 |
变种
416. 分割等和子集 中等
494. 目标和 中等
474. 一和零 中等
322. 零钱兑换 中等
518. 零钱兑换 II 中等
139. 单词拆分 中等
377. 组合总和 Ⅳ 中等
309. 最佳买卖股票时机含冷冻期 中等
714. 买卖股票的最佳时机含手续费 中等
123. 买卖股票的最佳时机 III 困难
188. 买卖股票的最佳时机 IV 困难
583. 两个字符串的删除操作 中等
72. 编辑距离 困难
650. 只有两个键的键盘 中等
每一个数都可以分解成素数的乘积,例如 84 = 22 * 31 * 50 * 71 * 110 * 130 * 170 * …
令 x = 2m0 * 3m1 * 5m2 * 7m3 * 11m4 * …
令 y = 2n0 * 3n1 * 5n2 * 7n3 * 11n4 * …
如果 x 整除 y(y mod x == 0),则对于所有 i,mi <= ni。
x 和 y 的最大公约数为:gcd(x,y) = 2min(m0,n0) * 3min(m1,n1) * 5min(m2,n2) * …
x 和 y 的最小公倍数为:lcm(x,y) = 2max(m0,n0) * 3max(m1,n1) * 5max(m2,n2) * …
204. 计数质数 简单
暴力法:
class Solution {public: int countPrimes(int n) { if(n<2) return 0; int count=0; int j=0; for(int i=2;i<n;i++){ count++;//这步是精髓 for(j=2;j<=sqrt(i);j++){ if(i%j==0){ count--;//这步是精髓 break; } } } return count; }};
埃及筛
注意:
这种没有改进的埃及筛做了很多无用功
每次都从头开始标记会导致很多已经被标记过的元素重新被标记一遍,比如 3个 i,在前面一定在标记质数 3 的时候,作为 3 的倍数被标记过 i 个 3
//没有改进的埃及筛(会超时)class Solution {public: int countPrimes(int n) { vector isPrime(n,1);//可以适当设置大一点,因为最后检查是否质数的时候会跳过n int ans=0; for(int i=2;i
改进后的埃及筛
class Solution {public: int countPrimes(int n) { vector<int> isPrime(n,1);//可以适当设置大一点,因为最后检查是否质数的时候会跳过n int ans=0; for(int i=2;i= n就不需要标记了,说明已经全部标记完了 for (int j = i * i; j < n; j += i) { isPrime[j] = 0; } } } } return ans; }};
线性筛(不在面试范畴)
[编程之美:2.7](https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode 题解 - 数学.md#)
504. 七进制数 简单
取模运算
思路:
class Solution {public: string convertToBase7(int num) { if(num==0) return "0";//边缘情况 string ans; ans.reserve(9); string sign= num<0 ? "-":""; if(num<0) num=abs(num); while(num){//进制转化 ans+= '0' + (num % 7);//注意这个单引号 num /= 7; } ans+=sign;//加上符号 reverse(ans.begin(),ans.end()); return ans; }};
递归
只能用于10以内的进制运算
否则没法用数字表示,就不能使用这种方法
#include class Solution {public: int convert(int num){ if(num < 7 && num > -7) return num; //如果就一位数 return convert(num / 7)*10 + num % 7; } string convertToBase7(int num) { int b = convert(num); //进入递归函数,返回进制转换后的 int 数 string c; //将 int 数转换为 string 返回 c.reserve(9); c=to_string(b); return c; }};
405. 数字转换为十六进制数 简单
移位解法
class Solution {public: string toHex(int num) { if (num == 0){//特殊情况处理 return "0"; } // 0-15映射成字符 string num2char = "0123456789abcdef";//这样就可以用 int 下标运算符去读取正确的表达了 // 转为为非符号数 unsigned int n = num;//因为C++负数不支持移位运算符 string res = ""; //res为空 while (n > 0){ //这种方式就是二进制转16进制的典型方法,取四位算它的值 res = num2char[n&15] + res; //n&15用于去除除了最后四位二进制数前面的数,15=000...0001111 n >>= 4;//删除最后的四位 } return res; }};
168. Excel表列名称 简单
思路
以数字 486 为例示范 10 进制与其他进制的转换过程:
通过**【除留余数法】**将数字 486 由 10 进制转换为 10 进制
10 的 3 次幂大于 486 循环结束
可拆解出 10 进制数的个位、十位、百位…,再反向罗列得到 486
同理,通过【除留余数法】将数字 486 由 10 进制转换为 2 进制
2 的 9 次幂大于 486 循环结束
可拆解出 2 进制数 逻辑上的 个位、十位、百位…,再反向罗列得到 111100110
c++11中四种类型转换
两种不同思路的代码
class Solution {public: string convertToTitle(int columnNumber) { string res=""; while (columnNumber > 0) { int remainer = columnNumber % 26; if(remainer == 0){ remainer = 26; columnNumber -= 1; } res.push_back('A' + remainer - 1); columnNumber /= 26; } reverse(res.begin(),res.end()); return res; }};
//class Solution {public: string convertToTitle(int columnNumber) { string s=""; while(columnNumber>0){ columnNumber-=1; s+='A'+columnNumber%26; columnNumber/=26; } reverse(s.begin(),s.end()); return s; }};
172. 阶乘后的零 简单
思路:
【寻找5元素】
class Solution {public: int trailingZeroes(int n) { int count=0; for(int i=1;i<=n;i++){ int N=i;//小心这里,会改变i的值 while(N%5==0) { count++; N/=5; } } return count; }};
【寻找元素5】更简洁的
class Solution {public: int trailingZeroes(int n) { int count = 0; while (n > 0) { count += n / 5; n = n / 5; } return count; }};
67. 二进制求和 简单
思路:
1、补 0 的思想很重要
class Solution {public: string addBinary(string a, string b) { reverse(a.begin(),a.end()); reverse(b.begin(),b.end()); string sum=""; int carry=0; int min=a.length()<b.length()? a.length():b.length(); int max=a.length()>b.length()? a.length():b.length(); for(int j=min;j<max;j++){//补齐缺少的0 if(a.length()
415. 字符串相加 简单
在上面的基础上改动一点点即可
class Solution {public: string addStrings(string a, string b) { reverse(a.begin(),a.end()); reverse(b.begin(),b.end()); string sum=""; int carry=0; int min=a.length()<b.length()? a.length():b.length(); int max=a.length()>b.length()? a.length():b.length(); for(int j=min;j<max;j++){//补齐缺少的0 if(a.length()'9') { carry=1; sum[i]-=10; } } if(carry==1) sum.push_back('1');//处理最后的进位 reverse(sum.begin(),sum.end()); return sum; }};
462. 最少移动次数使数组元素相等 II 中等
169. 多数元素 简单
排序法
class Solution { public: int majorityElement(vector<int>& nums){ sort(nums.begin(),nums.end()); return nums[nums.size()/2]; }};
哈希表法
会超时
367. 有效的完全平方数 简单
暴力法(会超时)
class Solution {public://暴力法 bool isPerfectSquare(int num) { if(num==0||num==1) return true; for(int i=1;i<=num/2;i++){ if(i==num/i && num%i==0) return true; } return false; }};
二分查找法
class Solution {public://二分查找法 bool isPerfectSquare(int num) { if(num==0||num==1||num==4) return true;//特殊情况判定 int low=1; int high=num/2; int mid=(low+high)/2; while(lownum){ high=mid; } else low=mid; } return false; }};
==数学法:==首项为1 公差为2的等差数列之和刚好为完全平方数,因此可以根据这个原理来解题
class Solution {public://二分查找法 bool isPerfectSquare(int num) { int tmp = 1; while(num > 0) { num -= tmp; tmp += 2; } return num == 0; }};
遇到求根题可以直接想到牛顿迭代法
牛顿迭代法
第一种官解:
class Solution {public: bool isPerfectSquare(int num) { if(num<2) return true; long x0=num/2; while (x0 * x0 > num) { x0 = (x0 + num / x0) / 2; } return (x0 * x0 == num); } };
第二种:double版牛顿迭代法
class Solution {public: bool isPerfectSquare(int num) { if(num<2) return true; double x0=num/2; while(true){ double x1=0.5*(x0+num/x0); if(fabs(x1-x0)<1e-7) break; x0=x1; } return (long long)x0 * (long long)x0==num; } };
326. 3的幂 简单
暴力法
class Solution {public://暴力解法:遍历 bool isPowerOfThree(int n) { if(n==0) return false; while(n%3==0){ n/=3; } if(n==1) return true; return false; }};
238. 除自身以外数组的乘积 中等
暴力解法:会超时
class Solution {public: vector<int> productExceptSelf(vector<int>& nums) { vector<int> output(nums.size(),0); for(int i=0;i<nums.size();i++){ int left=0; int right=i+1; int product=1; for(;left<i;left++){ product*=nums[left]; } for(;right<nums.size();right++){ product*=nums[right]; } output[i]=product; } return output; }};
左右乘积列表
class Solution {public: vector<int> productExceptSelf(vector<int>& nums) { vector<int> L(nums.size(),1);//在初始化是就将L[0]的前缀之积设置为1,后面循环时即可跳过L[0],R[0]同理 for(int i=1;i R(nums.size(),1); for(int j=nums.size()-2;j>=0;j--){ R[j]=R[j+1]*nums[j+1]; } for(int i=0;i
628. 三个数的最大乘积 简单
排序法:
class Solution {public://基于快速排序的算法 int Partition(vector& nums,int low,int high){ int pivot=nums[low]; while(low=pivot) high--; nums[low]=nums[high]; while(low& nums,int low,int high){ if(low& nums) { int product=1; QuickSort(nums,0,nums.size()-1); //sort(nums.begin(),nums.end()); int max1=nums[nums.size()-1] * nums[nums.size()-2] * nums[nums.size()-3]; int max2=nums[0] * nums[1] * nums[nums.size()-1]; return max1>max2?max1:max2; }};
在方法一中,我们实际上只要求出数组中最大的三个数以及最小的两个数,因此我们可以不用排序,用线性扫描直接得出这五个数。
其实链表定义的时候,就是定义一个节点,链表就是很多节点连接起来,所以一个节点也叫链表
1、反转链表的基本方式:
设置 prev 和 curr 两个指针
prev=nullptr;//因为是反转链表,那么原头结点前的元素便是尾结点 nullptrnext=curr->next;//记录 curr->next,因为下一步会断掉原有的 curr->next 这个链接curr->next=prev;//断掉原有的链接,反转指向前一个数//下面两步将这 prev 和 curr 两个指针右移prev=curr;curr=next;
160. 相交链表 简单
==【较聪明的双指针解法】==第一种解法:【双指针法】把两个链表连起来,也就是一个相同长度的链表了
*注意其中对 ListNode A = headA 的理解
class Solution {public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { ListNode *A = headA, *B = headB;/*指向指针的指针,这里对比的是指针headA和指针HeadB是否相等(这个描述是错误的) ListNode *A = headA的正确理解:ListNode *A指的是定义一个指向 ListNode 类型的指针,A的值就等于headA中的值,也就是等于 headA 指向的节点的地址 之所以一开始理解不了是因为搞混了 int *a = b; 和 int *a = &b的区别, 第一种写法的意思就是指 a 是一个指向int类型的指针,a 等于 b 中存储的地址的值,b 本身也是一个指针。 第二种写法的意思是 a 指向 b 所以此处的 A 在后续改变值之前和 headA 完全等同,之所以要重新定义一个 A 是为了保存 headA 中的地址,不然后面需要用时就没有了 */ while (A != B) { A = (A != nullptr ? A->next : headB);//判断是否到达链表的尾部,如果到达尾部之后就继续遍历另一个链表,加了括号就好理解了 B = B != nullptr ? B->next : headA; } return A; }};
==【较笨的双指针解法】==第二种解法(以后可以写,此处未写):
思路:
1、首先分别遍历两个链表,得到每个链表的长度a,b
2、相差的长度让指向长链的指针先走b-a步
3、然后指向两个链表的两个指针同时运动,当两个指针指向的地址相同时就是两个链表相遇的地方
【哈希表法】
//哈希表法class Solution {public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { //存入哈希表 ListNode *ptrA=headA, *ptrB=headB; unordered_map T; while(ptrA!=NULL){ T[ptrA]=ptrA->val; ptrA = ptrA->next; } //搜索哈希表中是否有与ptrA相同的地址 while(ptrB!=NULL){ if(T.count(ptrB)){ return ptrB; } ptrB=ptrB->next; } return {}; }};
206. 反转链表 简单
【双指针法】
class Solution {public: ListNode* reverseList(ListNode* head) { ListNode* curr=head; ListNode* prev=nullptr; while(curr){ ListNode* next=curr->next; curr->next=prev;//这步断了后面的链接,往前面连接 prev=curr; curr=next; } return prev; }};
【递归法】还没看懂
class Solution {public: ListNode* reverseList(ListNode* head) { if (!head || !head->next) { return head; } ListNode* newHead = reverseList(head->next); head->next->next = head; head->next = nullptr; return newHead; }};
21. 合并两个有序链表 简单
【迭代法】
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {}//构造函数 * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ //一开始想的解决方案都是在原来的链表中去插入,比较繁琐,简单的做法应该是:创建一个新链表,用一个指针控制这个链表的增长,另外另个待合并的链表只需要将需要的节点连接到新链表中,他们原来的顺序及指向不重要class Solution {public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { ListNode* ans = new ListNode(-1);//记住这种创建链表的方法 ListNode* prev = ans;//将prev指向第一个节点 while(l1!=nullptr&&l2!=nullptr){//将两个链表合并 if(l1->valval){ prev->next=l1; l1=l1->next; } else{ prev->next=l2; l2=l2->next; } prev=prev->next; } //这里注意看一下为什么最后只剩下一个没有连接上去的节点 prev->next = l1 == nullptr ? l2 : l1; return ans->next; }};
【递归法】
还没看
83. 删除排序链表中的重复元素 简单
哈希表法
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ //哈希表法class Solution {public: ListNode* deleteDuplicates(ListNode* head) { if(head==nullptr) return head; unordered_set table;//哈希表 table.insert(head->val);//将第一个元素存入哈希表 ListNode* temp=head->next;//temp指向 head->next ListNode* prev=head;//prev指向temp的前一个 while(temp!=nullptr){//将元素存入哈希表 if(table.count(temp->val)){//如果该元素存在 prev->next=temp->next;//跳过temp的当前节点 } else prev=prev->next; table.insert(temp->val); temp=temp->next; } return head; }};
还有一种**一次遍历法**,因为重复项是相邻的,因此可以使用这种方法,比较蠢
19. 删除链表的倒数第 N 个结点 中等
【哈希表法】
//哈希表法,记录下第n个数的地址,key值为n,val为地址class Solution {public: ListNode* removeNthFromEnd(ListNode* head, int n) { unordered_map table;//注意此处的 ListNode* ListNode* temp=head; int i=0; while(temp!=nullptr){ table[i++]=temp;//存储第i个数字的地址, i-1 就是最终的节点个数 temp=temp->next; }//这个循环过后,其实i被多加了一次,所以这个链表的下标最大其实只有i-1,也就是有i个结点 if(i==n) return head->next;//防止倒数n+1这个结点不存在的情况 temp=table[i-n+-1]; //找到倒数第n+1个结点 temp->next=temp->next->next; return head; }};
快慢指针法
栈
双指针法
24. 两两交换链表中的节点 中等
三指针法
class Solution {public: ListNode* swapPairs(ListNode* head) { ListNode* dummy=new ListNode(0);//创建头结点 dummy->next=head; ListNode* temp=dummy; ListNode* l=head; if(head==nullptr) return head; ListNode* r=head->next; while(l->next!=nullptr){ temp->next=r; l->next=r->next; r->next=l; temp=l; if(l->next==nullptr) break; r=l->next->next; l=l->next; } return dummy->next; }};
递归
445. 两数相加 II 中等
官解栈
//官解class Solution {public: ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { stack s1, s2;//定义两个栈 while (l1) { s1.push(l1 -> val);//将l1压入栈中 l1 = l1 -> next; } while (l2) { s2.push(l2 -> val);//将l2压入栈中 l2 = l2 -> next; } int carry = 0; //设置进位 ListNode* ans = nullptr;//设置答案链表 while (!s1.empty() or !s2.empty() or carry != 0) { int a = s1.empty() ? 0 : s1.top();//判断是否为空,并记录下栈顶的元素 int b = s2.empty() ? 0 : s2.top(); if (!s1.empty()) s1.pop(); //将栈顶元素弹出 if (!s2.empty()) s2.pop(); int cur = a + b + carry; //计算当前位的值 carry = cur / 10; //进位 cur %= 10; //余数就是cur auto curnode = new ListNode(cur); //创建新结点 curnode -> next = ans; //从尾部开始创建链表,注意这种写法 ans = curnode; } return ans; }};
自己写的栈
思路:
1、将两个待加链表入栈
2、提取栈顶待加元素并将他们出栈
3、计算当前位的进位及余数
4、创建新的 ans 链表并返回,注意这里的创建链表思路
ListNode* ans=nullptr; //从末尾开始创建链表,因为新链表中还没有元素,因此将头结点先指向 nullptr ListNode* newNode=new ListNode(a); //创建 val=a 的新结点//下面两步是精华newNode->next=ans;ans=newNode;
//自己写的class Solution {public: ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { stack s1,s2; while(l1!=nullptr){ s1.push(l1->val); l1=l1->next; } while(l2!=nullptr){ s2.push(l2->val); l2=l2->next; } int curr=0; int carry=0; ListNode* ans=nullptr; while(!s1.empty()|!s2.empty()|carry!=0){ int a=s1.empty()?0:s1.top(); int b=s2.empty()?0:s2.top(); if(!s1.empty()) s1.pop(); if(!s2.empty()) s2.pop(); curr=a+b+carry; carry=curr/10; //curr=(a+b+carry)%10;//注意这里的carry被改变了,所以会错 curr=curr%10; ListNode* currnode=new ListNode(curr); currnode->next=ans; ans=currnode; } return ans; }};
234. 回文链表 简单
栈
class Solution { //栈public: bool isPalindrome(ListNode* head) { ListNode* temp=head; stack s; while(temp!=nullptr){//入栈 s.push(temp->val); temp=temp->next; } temp=head; while(temp!=nullptr){ int a=s.empty()?0:s.top();//a=栈顶元素 if(!s.empty()) s.pop();//出栈 if(temp->val!=a) return false; temp=temp->next; } return true; }};
数组法
复制到数组中然后,通过双指针去确认是否为回文串
递归法(还没看)
快慢指针(代码分为三大部分:1、特殊情况判断2、找出中点并反转链表。3、判断回文子串)
整个流程可以分为以下五个步骤:
执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。
我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。
若链表有奇数个节点,则中间的节点应该看作是前半部分。
步骤二可以使用「206. 反转链表」问题中的解决方法来反转链表的后半部分。
步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。
public boolean isPalindrome(ListNode head) { if(head == null || head.next == null) {//特殊情况 return true; } ListNode slow = head, fast = head; ListNode pre = head, prepre = null; while(fast != null && fast.next != null) {//寻找中点的同时反转一半的链表,fast.next != null用于保证该链表是偶数个数的链表 pre = slow; slow = slow.next; fast = fast.next.next; //反转链表 pre.next = prepre; prepre = pre; } if(fast != null) {//元素个数为奇数的情况 slow = slow.next; } while(pre != null && slow != null) {//判断回文子串 if(pre.val != slow.val) { return false; } pre = pre.next; slow = slow.next; } return true; }
725. 分隔链表 中等
方法一 计算长度
简要说说,就是先遍历一次链表计算出链表长度 nn ,然后根据 n // kn//k 就能计算出分割后每段的长度,其中前 n % kn%k 段长度比其他段长 1 。
class Solution {public: vector<ListNode*> splitListToParts(ListNode* root, int k) { int count=0;//计算有多少个结点 vector table(k,nullptr); ListNode* temp=root; while(temp!=nullptr){ count++; temp=temp->next; } int carry=count%k;//carry为余数, int curr=count/k;//一共有 carry 个长度为 curr+1 的链表,剩余的链表长度都为 curr ListNode* temp1; temp=root; count=0; int count1=0; int i=1; table[0]=root; while(temp!=nullptr){ count++; temp1=temp->next; while(count1<=carry && count==curr+1&&temp->next!=nullptr){//carry个curr+1 table[i++]=temp->next; temp->next=nullptr; count1++; count=0; } while(count1next!=nullptr){//k-carry个curr table[i++]=temp->next; temp->next=nullptr; count1++; count=0; } temp=temp1; } return table; }};
双指针法
328. 奇偶链表 中等
官解
class Solution {public: ListNode* oddEvenList(ListNode* head) { if (head == nullptr) { return head; } ListNode* oddhead=head->next; ListNode* odd=oddhead; ListNode* evenhead=head; ListNode* even=head; while(odd&&odd->next){ even->next=even->next->next; even=even->next; odd->next=odd->next->next; odd=odd->next; } even->next=oddhead; return head; }};
一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。
104. 二叉树的最大深度 简单
110. 平衡二叉树 简单
543. 二叉树的直径 简单
226. 翻转二叉树 简单
617. 合并二叉树 简单
112. 路径55总和 简单
437. 路径总和 III 中等
572. 另一个树的子树 简单
101. 对称5二叉树 简单
111. 二叉树的最小深度 简单
404. 左叶子之和 简单
687. 最长同值路径 中等
337. 打家劫舍 III 中等
671. 二叉树中第二小的节点 简单
使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
637. 二叉树的层平均值 简单
513. 找树左下角的值 中等
1 / \ 2 3 / \ \4 5 6
层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。
前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。
① 前序
void dfs(TreeNode root) { visit(root); dfs(root.left); dfs(root.right);}
② 中序
void dfs(TreeNode root) { dfs(root.left); visit(root); dfs(root.right);}
③ 后序
void dfs(TreeNode root) { dfs(root.left); dfs(root.right); visit(root);}
144. 二叉树的前序遍历 中等
145. 二叉树的后序遍历 简单
94. 二叉树的中序遍历 简单
二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。
二叉查找树中序遍历有序。
669. 修剪二叉搜索树 中等
230. 二叉搜索树中第K小的元素 中等
538. 把二叉搜索树转换为累加树 中等
235. 二叉搜索树的最近公共祖先 简单
236. 二叉树的最近公共祖先 中等
108. 将有序数组转换为二叉搜索树 简单
109. 有序链表转换二叉搜索树 中等
653. 两数之和 IV - 输入 BST 简单
530. 二叉搜索树的最小绝对差 简单
501. 二叉搜索树中的众数 简单
Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。
208. 实现 Trie (前缀树) 中等
677. 键值映射 中等
232. 用栈实现队列 简单
225. 用队列实现栈 简单
155. 最小栈 简单
20. 有效的括号 简单
739. 每日温度 中等
503. 下一个更大元素 II 中等
哈希表使用 O(N) 空间复杂度存储数据,并且以 O(1) 时间复杂度求解问题。
Java 中的 HashMap 主要用于映射关系,从而把两个元素联系起来。HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中 [Leetcdoe : 535. Encode and Decode TinyURL (Medium)
Leetcode,利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源�) / 力扣,利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源�)
1. 两数之和 简单
class Solution {public: vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int, int> hashtable;//无序哈希表 for (int i = 0; i < nums.size(); ++i) { auto it = hashtable.find(target - nums[i]); if (it != hashtable.end()) { return {it->second, i}; } //之所以放在最后是为了只要在找到这两个数时就停止往哈希表里写入数据,节省时间和空间 hashtable[nums[i]] = i;//该哈希表的 key 为数组元素的值,val 为数组元素的下标 } return {}; }};unorder_map hashtable;for(int i = 0;isecond,i}; hashtable[nums[i]]=i; }}
217. 存在重复元素 简单
第一种解法:(采用类似于计数排序的方法进行统计)
class Solution {public://计数 bool containsDuplicate(vector& nums) { int min=INT_MAX; int max=INT_MIN; int k; for(int i=0;inums[i]) min=nums[i]; if(max T(k,0); for(int i=0;i1) {return true;} } return false; }};
第二种解法(哈希表法)
class Solution {public: bool containsDuplicate(vector<int>& nums) { unordered_map<int,int> hashtable;//注意hashtable不需要提前定义他的长度 for(int i=0;i
594. 最长和谐子序列 简单
128. 最长连续序列 困难
第一代(时间过长)
class Solution {public: int longestConsecutive(vector<int>& nums) { if(nums.size()==0){ return 0; } int max=0; unordered_set<int> Hashtable; for(int i=0;i<nums.size();i++){//创建哈希表 Hashtable.insert(nums[i]); } for(int i=0;i
第二代哈希表法:(加入了条件判断,时间缩短)
class Solution {public: int longestConsecutive(vector<int>& nums) { if(nums.size()==0){ return 0; } int max=0; unordered_set<int> Hashtable; for(int i=0;i<nums.size();i++){//创建哈希表 Hashtable.insert(nums[i]); } for(int i=0;i
第三种,别人写的,并查集,还没搞懂(别人的评论:第一种方法不是并查集吧,只是用递归重写了for循环。)
class Solution {public: unordered_map<int,int> a,b; int find(int num){ return a.count(num) ? a[num] = find(a[num]) : num;//判断哈希表中是否存在 x 这个数,存在的话啊 a[x] 就等于 a[x] 的下标,不存在的话 a[x] = x } int longestConsecutive(vector& nums) { for(auto num:nums)//遍历 nums 数组 a[num]=num+1; int count=0; for(auto low:nums){//遍历哈希表 int high = find(low+1); //是否存在后驱元素 count=max(count,high-low); } return count; }};
第四种:学习了力扣官解第二个for使用哈希表遍历,快了几十倍不止
class Solution {public: int longestConsecutive(vector<int>& nums) { unordered_set<int> Hashtable; for(auto num:nums){//创建哈希表 Hashtable.insert(num); } int longest=0; for(auto num:Hashtable){ int c=0; if(Hashtable.count(num-1)) continue;//加入了判断,缩短时间 while(Hashtable.count(num)){ num+=1; c+=1; } if(longest
[编程之美 3.1](https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode 题解 - 字符串.md#)
[编程之美 2.17](https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode 题解 - 字符串.md#)
[程序员代码面试指南](https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode 题解 - 字符串.md#)
242. 有效的字母异位词 简单
用冒泡排序会超时:
class Solution { //快速排序public: void mySort(string &s){ for(int i=0;is[j+1]){ char temp; temp=s[j+1]; s[j+1]=s[j]; s[j]=temp; } } } } bool isAnagram(string s, string t) { sort(s.begin(), s.end()); sort(t.begin(), t.end()); if(s.size()!=t.size()) return false; int ptr1=0,ptr2=0; while(ptr1
排序官解
class Solution {public: bool isAnagram(string s, string t) { if (s.length() != t.length()) { return false; } sort(s.begin(), s.end()); sort(t.begin(), t.end()); return s == t; }};
哈希表法:(哈希表采用的数据结构不一定是 set 或者 map)
//官解class Solution {public: bool isAnagram(string s, string t) { if (s.length() != t.length()) { return false; } vector table(26, 0); for (auto& ch: s) { table[ch - 'a']++; } for (auto& ch: t) { table[ch - 'a']--; if (table[ch - 'a'] < 0) { return false; } } return true; }};
【哈希表法】
class Solution {public: bool isAnagram(string s, string t) { vector<int> table(26,0); if(s.size()!=t.size()){ return false; } for(char sl:s){ table[sl-'a']++; } for(char tl:t){ table[tl-'a']--; if(table[tl-'a']<0) return false; } return true; }};
409. 最长回文串 简单
class Solution {public://哈希表法 int longestPalindrome(string s) { unordered_map table; int ans=0; for(int i=0;i
205. 同构字符串 简单
class Solution {public: bool isIsomorphic(string s, string t) { unordered_map<char,char> table; string ans=""; ans.resize(s.length(),0);//没有这一步,不能成功构造 for(int i=0;i
647. 回文子串 中等
中心拓展法
class Solution {public: int countSubstrings(string s) { int num = 0; int n = s.size(); for(int i=0;i<n;i++)//遍历回文中心点 { for(int j=0;j<=1;j++)//j=0,中心是一个点,j=1,中心是两个点 { int l = i; int r = i+j; while(l>=0 && r
动态规划(未看)
9. 回文数 简单
最全题解
普通解法(转字符串)
class Solution {public: bool isPalindrome(int x) { if(x<0) return false; string temp=to_string(x); string ans=to_string(x); reverse(temp.begin(),temp.end()); return temp==ans?true:false; }};
进阶解法—数学解法
class Solution {public: bool isPalindrome(int x) { if(x<0) return false; int div=1; while(x/div>=10) div*=10;//注意不要少了等号 while(x>0){ int l=x / div; int r=x % 10; if(l!=r) return false; x=(x % div) / 10; div=div / 100; } return true; }};
解法三:进阶解法—巧妙解法
696. 计数二进制子串 简单
283. 移动零 简单.
【自己写的】
class Solution {public: void moveZeroes(vector<int>& nums) { int count=0; for(int i=0;i<nums.size();i++){ if(nums[i]!=0){ nums[count++]=nums[i]; } } for(;count<nums.size();count++){ if(nums[count]!=0) nums[count]=0; } }};
566. 重塑矩阵 简单
485. 最大连续 1 的个数 简单
240. 搜索二维矩阵 II 中等
378. 有序矩阵中第 K 小的元素 中等
645. 错误的集合 简单
287. 寻找重复数 中等
667. 优美的排列 II 中等
697. 数组的度 简单
766. 托普利茨矩阵 简单
565. 数组嵌套 中等
769. 最多能完成排序的块 中等
基本原理
基本原理
0s 表示一串 0,1s 表示一串 1。
x ^ 0s = x x & 0s = 0 x | 0s = xx ^ 1s = ~x x & 1s = x x | 1s = 1sx ^ x = 0 x & x = x x | x = x
利用 x ^ 1s = ~x 的特点,可以将一个数的位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。
1^1^2 = 2
利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 n········um 中与 mask 的 1 部分相对应的位。
01011011 &00111100--------00011000
利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。
01011011 |00111100--------01111111
位与运算技巧
n&(n-1) 去除 n 的位级表示中最低的那一位 1。例如对于二进制表示 01011011,减去 1 得到 01011010,这两个数相与得到 01011010。
01011011 &01011010--------01011010
n&(-n) 得到 n 的位级表示中最低的那一位 1。-n 得到 n 的反码加 1,也就是 -n=~n+1。例如对于二进制表示 10110100,-n 得到 01001100,相与得到 00000100。
10110100 &01001100--------00000100
n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1) 效果一样。
移位运算
>> n 为算术右移,相当于除以 2n,例如 -7 >> 2 = -2。
11111111111111111111111111111001 >> 2--------11111111111111111111111111111110
>>> n 为无符号右移,左边会补上 0。例如 -7 >>> 2 = 1073741822。
11111111111111111111111111111001 >>> 2--------00111111111111111111111111111111
<< n 为算术左移,相当于乘以 2n。-7 << 2 = -28。
11111111111111111111111111111001 << 2--------11111111111111111111111111100100
(x >> i) & 1 得到 xx 的第 i 个二进制位
mask 计算
要获取 111111111,将 0 取反即可,~0。
要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1<<(i-1) 。例如 1<<4 得到只有第 5 位为 1 的 mask :00010000。
要得到 1 到 i 位为 1的 mask,(1<
要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 ~((1<
总结
1、错位异或
2、各位二进制位累加进行余数计算,%3
3、分治
4、一些数学思想:
偶数 n 的二进制位1等于 n/2 偶数的 1 的个数,奇数 1 的个数等于前一个偶数的 1 的个数
4的幂次方 n % 3 == 1
2的幂次方只有一个1
加法等于 ^ + & 运算
461. 汉明距离 简单
class Solution {public: int hammingDistance(int x, int y) { int temp = x^y;//temo中的1就是xy中不同的元素的位置 int count=0; while(temp>0){ count++; temp&=(temp-1); } return count; }};
136. 只出现一次的数字 简单
class Solution {public: int singleNumber(vector<int>& nums) { int ans=nums[0]; for(int i=1;i<nums.size();i++){ ans^=nums[i]; } return ans; }};
268. 丢失的数字 简单
四种方法:
**哈希表法:**存入数字,然后遍历0~n,一一去set中查找是否存在
**数学法:**将0~n累加起来,一边加一边减
**排序法:**排序后一一对比
**位运算法:**将0~n以及数组中出现的所有数字进行异或,最后的值就是缺的值,因为其他数都出现了两次,0和所有非0数异或都为该非0数
260. 只出现一次的数字 III 中等
排序法
class Solution {public://排序法 vector singleNumber(vector& nums) { sort(nums.begin(),nums.end()); int n=nums.size(); int a=0; vector ans(2,0); if(nums[n-2]!=nums[n-1]) ans[a++]=nums[n-1]; for(int i=0;i
位运算法
class Solution {public: vector<int> singleNumber(vector<int>& nums) { long div=0;//用于分组的变量,取0是因为0异或所有数都为它本身 for(int num:nums){ div^=num; } div&=(-div);//取div最低位的1 int a=0,b=0; for(int num:nums){ if(div&num){//num的最后一位为1 a^=num; } else b^=num; } return {a,b}; }};
137. 只出现一次的数字 II
哈希表法
class Solution {public://哈希表法 int singleNumber(vector& nums) { unordered_map freq; for(int num:nums){ ++freq[num];//存入哈希表 } for (auto [num,count] :freq){//注意这种特殊的遍历方式 if(count==1){ return num; } } return 0; }};
位运算法
这个算法的思想就是,出现了三次,那么将每个数的每个对应的二进制位的相加,如果%3有余数,那么就是表示只出现了一次的那个数的那个二进制位那个位置是1,因此这种算法对于出现了 4 5 6 7…10 11次去找那个只出现了一次的数的题目都适用
class Solution {public: int singleNumber(vector<int>& nums) { int ans=0; for(int i=0;i<32;i++){ int total=0; for(int num:nums){ total += (num>>i)&1; } if(total % 3){ ans|=(1<<i); } } return ans; }};
190. 颠倒二进制位 简单
分治法(较难想到)
class Solution {private: const uint32_t M1 = 0x55555555; // 01010101010101010101010101010101 const uint32_t M2 = 0x33333333; // 00110011001100110011001100110011 const uint32_t M4 = 0x0f0f0f0f; // 00001111000011110000111100001111 const uint32_t M8 = 0x00ff00ff; // 00000000111111110000000011111111public: uint32_t reverseBits(uint32_t n) { n = n >> 1 & M1 | (n & M1) << 1; n = n >> 2 & M2 | (n & M2) << 2; n = n >> 4 & M4 | (n & M4) << 4; n = n >> 8 & M8 | (n & M8) << 8; return n >> 16 | n << 16; }};
位运算法
class Solution {public: uint32_t reverseBits(uint32_t n) { int ans=0; int temp=0; for(int i=0;i<32;i++){ temp=(n>>i)&1; //得到从右往左第 i 位的 0 或者 1 ans|=temp<<(32-i-1); //将这个 1 移动到ans的从左往右第 i 位 } return ans; }};
[程序员代码面试指南 :P317](https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode 题解 - 位运算.md#)
231. 2的幂 简单
位运算法
//自己写的class Solution {public: bool isPowerOfTwo(int n) { if(n<0) return false; //如果是负数一定不会是2的幂次方 int total=0; for(int i=0;i<32;i++){ //统计 n 的二进制数中有几个1 if(n>>i&1) total++; } if(total==1) return true; //如果是2的幂次方,那么二进制数一定只有一个 1 else return false; }};
//官解1class Solution {public: bool isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; }};//官解2class Solution {public: bool isPowerOfTwo(int n) { return n > 0 && (n & -n) == n; }};
数学法
class Solution {private: static constexpr int BIG = 1 << 30;public: bool isPowerOfTwo(int n) { return n > 0 && BIG % n == 0; }};
342. 4的幂 简单
傻瓜式求解:迭代除4(别人写的)
class Solution { public boolean isPowerOfFour(int n) { if (n < 1) return false; while (n % 4 == 0) { n /= 4; } return n == 1; }}
取模法
思路:
1、排除 n<=0 的情况,易错点,顺序不能写错,因为是按照写的顺序依次判断条件是否成立的
2、保证该数是2的幂次方,通过保证2进制数只有一个 1, n&(n-1)== 0 (去除了最后一位 1 后就没有 1 了说明这个二进制数只有一个)
3、如果是4的幂次方,对3取模的结果一定是1,(如果 n 是 2 的幂却不是 4的幂,那么它可以表示成 4x×2 的形式,此时它除以 3 的余数一定为 2)。
这段理解非常重要:比如(3+1) (3+1)=9+3+3+1,该数 mod3 一定等于1,即余数为1,那么(3+1) (3+1)* 2的余数也就是前面余数的两倍,也就是2 **
class Solution {public: bool isPowerOfFour(int n) { //注意(n&(n-1))==0外面的括号不能删 return n > 0 && (n & (n - 1)) == 0 && n % 3 == 1;//关键就是这个 n&(n-1) ,可以排除奇数,因为7%3=1 }};
位运算法
思路:
1、排除 n<=0 的情况,易错点,顺序不能写错,因为是按照写的顺序依次判断条件是否成立的
2、保证该数是2的幂次方,通过保证2进制数只有一个 1, n&(n-1)== 0 (去除了最后一位 1 后就没有 1 了说明这个二进制数只有一个)
3、并且这个1出现在偶数位上 0xaaaaaaaa & n==0
class Solution {public: bool isPowerOfFour(int n) { return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0; //n&(n-1)是为了排除奇数的可能,因为 101 & 010 == 0 }};
693. 交替位二进制数 简单
转字符串法
错位异或法
class Solution {public: bool hasAlternatingBits(int n) { unsigned int t = (n ^ (n>>1)); return (t & (t+1)) == 0;//t&(t-1)用于消除最低位级的1 }};
476. 数字的补数 简单
371. 两整数之和 中等
无符号数概念
位运算法
class Solution {public: int getSum(int a, int b) { if (b == 0) return a; return getSum(a^b, (unsigned int)(a&b)<<1);//注意()括号中的强制类型转换 }};
318. 最大单词长度乘积 中等
338. 比特位计数 简单
位运算法(遍历,暴力解法)
class Solution {public: vector<int> countBits(int n) { vector<int> ans(n+1,0); for(int i=0;i<=n;i++){ int total=0; for(int j=0;j<32;j++){ if(i>>j&1){ total++; } } ans[i]=total; } return ans; }};
位运算法:(这种方法有点傻瓜式,相当于预先找完规律再做题)
class Solution {public: vector<int> countBits(int n) { vector<int> ans(n+1,0); ans[0]=0; for(int i=1;i<=n;i++){ if(1&i){ //判断是否为偶数 ans[i]=ans[i-1]+1; //奇数的1的数量比它前面的偶数的1的数量多1 } else{ ans[i]=ans[i/2];//偶数的1的数量一定和它二分之一的1 的个数是一样的 } } return ans; }};
注:
(一个一个对比,每次移动一个未排序序列中的最大值到最后)
auto n=nums.size();int i,j;for(i=0;i<n;i++){ //用于固定最后的已经冒泡冒上来的数字,通过i的增加逐渐减少j循环中对比交换的过程次数 for(j=0;j
每一趟在待排序元素中选取关键字最小(或最大)的元素加入有序子序列
auto n = nums.size();int i,j,relmax;for(i=0;i<n-1;i++){ relmax=n-i-1;//这个是易错点 for(j=0;j
序列分两段,已排序和未排序,每次将未排序序列中的第一个值记录下来,然后把已排序序列中的比这个值大的数都往后移一位,再把该值插入到前面空出来的位置中
插入排序的思路:
1、先用一个循环控制前面已经排好序的序列的范围
auto n=nums.size();int i,j,temp;for(i=1;i<n;i++){// 1 }
2、将该序列后的一个值保存在value中
auto n=nums.size();int i,j,temp;for(i=1;i<n;i++){// 1 temp=nums[i];// 2}
3、再用一个for循环去把已经排序完成的序列中比 temp 大的数依次往后移一位
auto n=nums.size();int i,j,temp;for(i=1;i<n;i++){// 1 temp=nums[i];// 2 for(j = i-1,j >= 0 && temp < nums[j];j--){// 3 nums[j+1]=nums[j]; }}
4、最后将所需要插入的 temp 中的值插入到 j+1 处
auto n=nums.size(); int i,j,temp;for(i=1;i<n;i++){// 1 temp=nums[i];// 2 for(j = i-1;j >= 0 && temp < nums[j];j--){// 3 其中将判断条件 temp < nums[j]和j >= 0写在一起就等价于下面注释所写 nums[j+1]=nums[j]; } nums[j+1]=temp;// 4}return nums;//等价于下面的代码,如果不满足 nums[j]>temp 会直接执行外部循环,而不是一直等到j不在 >=0 的时候结束循环 if(temp < nums[j]){ for(j = i-1,j >= 0;j--){ nums[j+1]=nums[j]; }}//之所以采用这种写法是因为插入排序时前面已经排序完成的序列是有序的,一旦一个数 nums[j]
//错误写法auto n=nums.size();int i,j,temp;for(i=1;i=0;j--){ if(temp
其实说到底就是在原来插入排序的基础上加入了一个控制序列分段的d参数
//本来想写另一种更加好理解的希尔算法程序,但是太过复杂,没必要auto n=nums.size();int i,j,temp,d;for(d=n/2;d>=1;d=d/2){ for(i=0;i
以下程序较为简洁,但是理解需要绕一下(有三个for)
auto n=nums.size();int i,j,temp,d;for(d=n/2;d>=1;d=d/2){//第一个for用于间距d的增减 for(i=d;i=0 && temp < nums[j];j-=d){//第三个for用于保证每个子序列中某个需要插入的数能依次与前面的数进行比较大小 nums[d+j]=nums[j]; } nums[d+j]=temp; }}return nums;
采用递归的方式将一个序列逐步变成两两比较的状态
注意:
1、另外创建的数组 B 不能放到需要递归的函数中
2、遍历效率
for(k=low;k<=high;k++){ B[k]=A[k];}//上面的这个遍历会比下面的遍历方式快,下面的遍历会超时for(int a:A){ B[k++]=a;}
auto n=nums.size();int *B=(int *)malloc(n*sizeof(int));//这步注意指针//该函数是用来给后面的递归函数调用的void merge(int A[],int low ,int mid,int high){ int i,j,k,B[]; for(k=low;k<=high;k++){//第一步,先将原数组low和high之内的元素全部复制到新数组B中 B[k]=A[k]; } for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){//注意此处的k=i保证原数组序列和新归并的序列起点在大的整体数组中的位置一样 if(B[i]<=B[j]) A[k]=B[i++]; else A[k]=B[j++]; } //将数组中剩余的元素复制到A数组中 while(i<=mid){A[k++]=B[i++];} while(j<=high){A[k++]=B[j++];}}//mergeSOrtvoid mergeSort(int A[],int low,int high){ if(low
力扣解题版:
class Solution {private: vector<int> temp;public: void mergeSort(vector<int>& nums,int low ,int high){//该处的vector& nums注意 if(low sortArray(vector& nums) { temp.resize((int)nums.size(), 0);//设置temp的长度,第二个参数的初始值 mergeSort(nums,0,(int)nums.size() - 1); return nums; }};
双指针,low和high
int partition(int nums[],int low,int high){ //int pivot=low;//这种写法是错的,后面low所指向的元素很快会被取代 int pivot=nums[low]; while(low=pivot) high--; nums[low]=nums[high]; while(low
【重写时发生了两个错误导致栈溢出】力扣题解(正常快排)
class Solution {public://int partition(vector& nums,int low,int high){ //int pivot=low;//这种写法是错的,后面low所指向的元素很快会被取代 int pivot=nums[low]; while(low=pivot) high--;//此处的low& nums,int low,int high){ if(low sortArray(vector& nums) { QuickSort(nums,0,(int)nums.size()-1); return nums; }};
随机选取枢纽的快排
class Solution { int partition(vector<int>& nums, int l, int r) { int pivot = nums[r]; int i = l - 1; for (int j = l; j <= r - 1; ++j) { if (nums[j] <= pivot) { i = i + 1; swap(nums[i], nums[j]); } } swap(nums[i + 1], nums[r]); return i + 1; } int randomized_partition(vector<int>& nums, int l, int r) { int i = rand() % (r - l + 1) + l; // 随机选一个作为我们的主元 swap(nums[r], nums[i]); return partition(nums, l, r); } void randomized_quicksort(vector& nums, int l, int r) { if (l < r) { int pos = randomized_partition(nums, l, r); randomized_quicksort(nums, l, pos - 1); randomized_quicksort(nums, pos + 1, r); } } public: vector sortArray(vector& nums) { srand((unsigned)time(NULL)); randomized_quicksort(nums, 0, (int)nums.size() - 1); return nums; }};
快速排序的非递归实现
void QuickSortNotR(int* array,int left,int right){ assert(array); stack<int> s; s.push(left); s.push(right);//后入的right,所以要先拿right while(!s.empty)//栈不为空 { int right = s.top(); s.pop(); int left = s.top(); s.pop(); int index = PartSort(array,left,right); if((index - 1) > left)//左子序列 { s.push(left); s.push(index - 1); } if((index + 1) < right)//右子序列 { s.push(index + 1); s.push(right); } }}————————————————版权声明:本文为CSDN博主「清枫若待佳人醉」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_36528114/article/details/78667034
相关知识:
大根堆:完全二叉树中,根≥左、右
小根堆:完全二叉树中,根≤左、右
一个 for 用来控制往上检查每一个非终端节点
另一个 for 用来保证更换后根节点和孩子节点之后不会导致下面的节点再次违反这个关系,所以用于控制往下检查每个非终端节点
void BuildMaxHeap(vector<int>& nums,int n){//建立大根堆 int k; for(k=n/2;k>=0;k--){//往上依次检查 HeadAdject(nums,k,n); }}void HeadAdject(vector& nums,int k,int n){//k特指我们需要检查的根节点 int i; int temp=nums[k]; for(i=2*k;inums[i] && i+1=nums[i])//为了保证排序是稳定的,因此要加上等号 break; else{ nums[k]=nums[i]; k=i;//一旦更换了位置后,就要再一一向下验证大根堆的正确性 //nums[i]=temp; 一开始这个写错位置了,如果要写在这个位置,那么上面第二个 if(nums[k]>=nums[i]) 应改为if(nums[k]>=nums[i]) } } nums[k]=temp;//一开始写错了,写成nums[i]=temp;}
void HeapSort(vector<int>& nums,int n){ BuildMaxHeap(nums,n);//1 for(int i=n-1;i>=0;i--){//2 and 3 swap(nums[0],nums[i]); HeadAdject(nums,0,i-1);//错误:此处一开始写的是n,没注意到在swap之后,移动到最后的元素就不能让再动了 }}
共有两个错误点:
1、由于数组序号是从0开始的,所以父节点和子节点的计算公式出错(正确的应为 )
d a d = ( s o n − 1 ) / 2 dad = (son-1)/2 dad=(son−1)/2
s o n = d a d ∗ 2 + 1 son = dad*2+1 son=dad∗2+1
2、由于 上面程序中的 HeadSort 和 BuildMaxHeap 两个函数调用 HeadAdject和这个函数是对于 end 这个参数的定义不同导致
class Solution {public: void HeadAdject(vector<int>& nums,int k,int end){ int dad = k; int son = dad * 2 + 1;//这个for第一次调用是正常的调整根节点和孩子节点的位置,第二、三......次调用就是为了验证第一次调用是是否使得下面不满足大根堆的要求 for(;son<=end;) { if (son + 1 <= end && nums[son] < nums[son + 1]) //比较左右孩子 son++; if (nums[dad] > nums[son]) break;//也可以是return else { swap(nums[dad], nums[son]);//发生了swap,则需要对于更换后的孩子节点进行大根堆验证 dad = son; son = dad * 2 + 1; } }} //写程序先写主函数,这个函数是思路 void HeapSort(vector& nums,int n){ for(int k=n/2-1/2;k>=0;k--){//建立大根堆,从最后一个根节点往上依次进行 HeapAdject HeadAdject(nums,k,n-1); } for(int i=n-1;i>=1;i--){ swap(nums[0],nums[i]); //移动完最大的节点后,重新建立大根堆 HeadAdject(nums,0,i-1);//i-1为易错点,因为swap之后,最后一个元素就不能再动了 }} vector sortArray(vector& nums) { int n=(int)nums.size(); HeapSort(nums,n); return nums; }};
class Solution {public: void HeapAdject(vector<int>& nums,int dad,int end){ for(int son=2*dad+1;dad<=end && son<=end;){//dad<=end冗余 if(nums[son]& nums,int end){ for(int dad=end/2;dad>=0;dad--){ HeapAdject(nums,dad,end); } for(int i=end;i>=1;i--){// swap(nums[0],nums[i]); HeapAdject(nums,0,i-1);// }} vector sortArray(vector& nums) { int n=(int)nums.size(); HeapSort(nums,n-1); return nums; }};
class Solution {public: void HeapAdject(vector<int>& nums,int k,int end){ int dad = k; int son = dad * 2 + 1; for(;son<=end;){ //if(nums[son] < nums[son+1] && son+1 <= end) if(son+1 <= end && nums[son] < nums[son+1]) son++; if(nums[dad] > nums[son]) break; else{ swap(nums[dad],nums[son]); dad=son; son=2*dad+1; } }}void HeapSort(vector& nums,int n){ int end=n-1; for(int dad=end/2;dad>=0;dad--){ HeapAdject(nums,dad,end); } for(int i=end;i>=1;i--){// swap(nums[0],nums[i]); HeapAdject(nums,0,i-1);// }} vector sortArray(vector& nums) { int n=(int)nums.size(); HeapSort(nums,n); return nums; }};
1、分类计数
2、依次输出,注意输出数字的次数
class Solution {private:void SelectionSort(vector<int>& nums){ int max=nums[0];//桶的最大下标为max int n=(int)nums.size(); for(int j=1;jmax){ max=nums[j]; } } vector T(max+1,0);//创建大小为max长度的桶 for(int i=0;i0){ nums[k++]=a; T[a]--; } }}public: vector sortArray(vector& nums) { SelectionSort(nums); return nums; }};
class Solution {public: vector<int> sortArray(vector<int>& nums) { int n=(int)nums.size(); int max=INT_MIN; int min=INT_MAX; for(int i=0;i<n;i++){ if(nums[i]>max) max=nums[i]; if(nums[i]<min) min=nums[i]; } int distance=max-min; vector<int> T(distance+1,0);//创建容器 for(int i=0;i0){ nums[a++]=i+min; T[i]--; } } return nums; }};
class Solution{public: vector<int> sortArray(vector<int>& nums) { int n=nums.size(); if(n<=1)return nums; //比如 要在一个数组中找出最大值,那么可以先定义一个最小值max=INT_MIN int min=INT_MAX,max=INT_MIN; for(int i=0;imax)max=nums[i]; if(nums[i] count(k,0);//创建一个长度为k的初始值为0的容器 for(int i=0;i0) { nums[loc++]=i+min;//重新加上min恢复到原来的元素大小 count[i]-=1; } } return nums; }};
按照类型去刷题
两种类型
从easy开始做