算法就是要经常复习
- 1、剑指 Offer 14- I. 剪绳子
- 2、剑指 Offer 14- II. 剪绳子 II
- 3、剑指 Offer 20. 表示数值的字符串
- 4、剑指 Offer 16. 数值的整数次方
- 5、剑指 Offer 35. 复杂链表的复制
- 6、92. 反转链表 II
- 7、 剑指 Offer 36. 二叉搜索树与双向链表
- 8、剑指 Offer 31. 栈的压入、弹出序列
- 9、剑指 Offer 39. 数组中出现次数超过一半的数字
- 10、4. 寻找两个正序数组的中位数
- 11、数字划分 ---- 需要注意的题型-》放苹果
- 12、整数划分
- 13、放苹果
- 15、旋转数组的最小值
- 16、给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
- 17、判断ip地址的有效性
- 18、atoi函数实现
- 19、正则表达式匹配,通配符匹配
- 20、回溯系列复习
- 21、动态规划系列复习
- 22、并查集复习
- 24、大数相加,大数相乘,快速幂等小技巧
- 25、不用运算符实现加法,位运算相关
- 26、跳跃游戏
- 滑动窗口
- 所有可能的路径
- 单调栈
- 图的dfs
- 拓扑排序
- 滑动窗口的最大值
- 数组中的逆序对
- min栈
- 链表随机节点
- 随机数索引
- 丑数
- 滑动窗口的最大值
- 队列的最大值
- 构建乘积数组
- 缺失的第一个正数
- 数字字符串转换成ip地址
- 最长有效括号
- 前缀树 trie
- 二叉树中的最大路径和
- 迪杰特斯拉算法(743. 网络延迟时间)
1、剑指 Offer 14- I. 剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
class Solution {
public:
// 减绳子 : dp[i]表示长度为i的绳子剪成m段的最大乘积,这一题m可以不用参与到考虑之中
// 第一段减掉j,那么剩下i-j的长度,你可以选择减也可以选择不剪, 取最大值 max(max(j*(i-j),j*dp[i-j]),dp[i]),这就是状态转移方程
int cuttingRope(int n) {
vector<int> dp(n+1,0);
dp[1] = 0;
dp[2] = 1;
for(int i = 3; i<=n; ++i){
for(int j = 2; j<i; ++j){ //从2开始,如果剪成1,那么对于乘积来说没有增益
dp[i] = max(dp[i],max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}
};
2、剑指 Offer 14- II. 剪绳子 II
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
class Solution {
public:
// 如果需要取余,那么不能使用动态规划来做,再循环中max取余,会导致结果错误
// 只能用贪心来做
int cuttingRope(int n) {
if(n<=3) return n-1;
long res = 1;
while(n >4){
res *= 3;
n = n-3;
res = res % (1000000007);
}
//循环结束后,n为1 2 3 或者 4,如果要获得最大值,那么直接乘以n就是最大的
return (n*res)%1000000007;
}
};
3、剑指 Offer 20. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
若干空格
一个 小数 或者 整数
(可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数
若干空格
小数(按顺序)可以分成以下几个部分:
(可选)一个符号字符(’+’ 或 ‘-’)
下述格式之一:
至少一位数字,后面跟着一个点 ‘.’
至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
一个点 ‘.’ ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
(可选)一个符号字符(’+’ 或 ‘-’)
至少一位数字
class Solution {
public:
// 表示数值的字符串
bool isNumber(string s) {
int idx = 0;
int len = s.size();
//去除前置空格
while(idx < len && s[idx] == ' ') idx++;
//设置四个flag,用于判断
bool hasNum = false, hasDot = false, hasE = false, hasSign = false;
//以e作为分界点
while(idx < len){
//符号的处理
if(s[idx]>='0' && s[idx] <= '9'){
hasNum = true;
}else if(s[idx] == '-' || s[idx] == '+'){
if(hasSign || hasNum || hasDot)
return false;
hasSign = true;
}else if(s[idx] == '.'){
if(hasDot || hasE) return false;
hasDot = true;
}else if(s[idx] == 'e' || s[idx] == 'E'){
if(hasE || !hasNum) return false; //在e之前一定要有数字
hasNum = false;
hasDot = false;
hasSign = false;
hasE = true;
}else break; //后置空格还要处理
idx++;
}
while(idx < len && s[idx] == ' ') idx++;
return hasNum && (idx == len);
}
};
4、剑指 Offer 16. 数值的整数次方
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
class Solution {
public:
// 数值的整数次方,快速幂算法
double myPow(double x, int b) {
double res = 1;
if(x == 0) return 0;
if(b == 0) return 1;
long n = (long)b;
if(n < 0){
n = -n; // 防止溢出
x = 1/x;
}
while(n>0){
if(n&1){
res = res * x;
}
x = x * x; // x等于x^2, x = (x^2)*x^2, x = x4*x^4; ....
n = n>>1;
}
return res;
}
};
5、剑指 Offer 35. 复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
class Solution {
public:
// 复杂链表的复制
Node* copyRandomList(Node* head) {
unordered_map<Node*,Node*> map;
Node *dup = head, *cur = head;
//建立旧链表节点到新链表节点的映射
while(head){
map[head] = new Node(head->val);
head = head->next;
}
//由于新旧节点之间已经建立了映射,建立新结点的指向关系的时候,根据之前建立的映射即可建立指向
while(cur){
map[cur]->next = map[cur->next];
map[cur]->random = map[cur->random];
cur = cur->next;
}
return map[dup];
}
};
6、92. 反转链表 II
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *dumy = new ListNode(-1);
dumy->next= head;
ListNode *prev = dumy;
ListNode *a = dumy, *b = dumy;
int i = 0;
for(int i = 0; i<left-1; ++i){
if(a == nullptr) return nullptr;
a = a->next; //上一个节点
}
for(int i = 0; i<right; ++i){
if(b == nullptr) return nullptr;
b = b->next; // 尾节点
}
a->next = help(a->next,b->next);
return dumy->next;
}
ListNode *help(ListNode *head, ListNode *tail){
ListNode *prev = tail;
ListNode *next = nullptr;
while(head != tail){
next = head->next;
head->next = prev;
prev = head;
head = next;
}
return prev;
}
};
7、 剑指 Offer 36. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
class Solution {
public:
Node* treeToDoublyList(Node* root) {
if(root == nullptr) return root;
help(root);
head->left = pre;
pre->right = head;
return head;
}
Node *pre, *head;
void help(Node *cur){
if(cur == nullptr) return;
//中序遍历
help(cur->left);
//记录前一个节点 pre,然后和当前节点坐指向,最后在主函数中修改head和pre的指向
if(pre == nullptr) head = cur;
else pre->right = cur;
cur->left = pre;
pre = cur;
help(cur->right);
}
};
8、剑指 Offer 31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
class Solution {
public:
// 回溯可做,直接循环
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> st;
int idx = 0;
/* 将数组入栈,然后判断是否和poped中的第一个元素相等,如果是,那么直接*/
for(auto num:pushed){
st.push(num);
while(!st.empty() && st.top() == popped[idx]){
st.pop();
idx++;
}
}
return st.empty();
}
};
9、剑指 Offer 39. 数组中出现次数超过一半的数字
class Solution {
public:
// 数组中出现次数超过一半的数字 摩尔投票法
/*
众数的投票数为1,非众数的投票数为-1,那么总的投票数一定是大于0
当vot == 0的时候,那么在剩下的区间中,众数依然没有变。
*/
int majorityElement(vector<int>& nums) {
int vot = 0, x = 0;
for(int i = 0; i<nums.size(); ++i){
if(vot == 0) x = nums[i];
if(nums[i] == x) vot++;
else vot--;
}
return x;
}
};
10、4. 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
class Solution {
public:
// 寻找两个正序数组的中位数:找第k大的数
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
int k1 = (m+n+1)/2;
int k2 = (m+n+2)/2;
if((m+n)&1)
return help(nums1,0,m-1,nums2,0,n-1,k1);
else
return (help(nums1,0,m-1,nums2,0,n-1,k1) + help(nums1,0,m-1,nums2,0,n-1,k2))/2.0;
}
// 转换为找第k小的数字
int help(vector<int> &nums1, int start1, int end1, vector<int> &nums2, int start2, int end2,int k){
int len1 = end1-start1+1;
int len2 = end2-start2+1;
if(len1 >len2) return help(nums2,start2,end2,nums1,start1,end1,k);
//base case
if(len1 == 0) return nums2[start2+k-1];
if(k == 1) return min(nums1[start1],nums2[start2]);
int i = min(end1,start1+k/2-1);
int j = min(end2,start2+k/2-1);
if(nums1[i] > nums2[j])
return help(nums1,start1,end1,nums2,j+1,end2, k-(j-start2+1));
else return help(nums1,i+1,end1,nums2,start2,end2,k-(i-start1+1));
}
};
11、数字划分 ---- 需要注意的题型-》放苹果
将整数n分成k份,且每份不能为空,任意两种划分方案不能相同(不考虑顺序)。
例如:n=7,k=3,下面三种划分方案被认为是相同的。
1 1 5
1 5 1
5 1 1
问有多少种不同的分法。
/*
将n个小球放到k个盒子中的情况总数 =
1. 至少有一个盒子只有一个小球的情况数 + 2. 所有的盒子都多余一个小球
那么
所有的盒子都多于一个小球:先在每个盒子中放一个小球,然后再分配n-k个球,因此为 f(n-k,k)
至少有一个盒子只有一个小球,那么先在某个盒子中放一个小球,因此为 f(n-1,k-1)
*/
int fun(int n,int k){
if(n==0||n<k||k==0)return 0;
else if(n==k||k==1) return 1;
else return fun(n-k,k)+fun(n-1,k-1);
}
12、整数划分
将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1
正整数n 的这种表示称为正整数n 的划分。正整数n 的不同的划分个数称为正整数n 的划分数。
/*
这一题和上一题相比,少了一个k的限制。那么其实k的最大值为n,所以将上一题改造为f(n,n)即可
*/
13、放苹果
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
/*
假设有n个苹果,k个盘子
n<k: 那么一定有盘子空着的,f(n,k) = f(n,n);
n>=k:
分为两种情况,1、至少有一个盘子是空的,表示为 f(n,k-1)
2、没有盘子空着,那么先在每个盘子放一个苹果,f(n-k,k)
f(n,k) = f(n,k-1) + f(n-k,k)
*/
int apple(int n, int k){
// if(m == 0 || n==1) return 1 ; //原解答
if(n == 0) return 1; //没有苹果
if(k==1) return 1; //一个盘子
if(n<k) return f(n,n);
return f(n,k-1) + f(n-k,k);
}
14、strStr() kmp
kmp算法
15、旋转数组的最小值
class Solution {
public:
// 旋转数组的最小值: 这里是将mid和right进行比较,因为没有targer值,这点要注意,不要懵了
int minNumberInRotateArray(vector<int> rotateArray) {
int left = 0, right = rotateArray.size()-1;
while(left<=right){
int mid = left+(right-left)/2;
if(rotateArray[mid] > rotateArray[right])
left = mid+1;
else if(rotateArray[mid] < rotateArray[right])
right = mid; //不能写为mid-1,如果right刚好指向最小值呢?那不就将最小值过滤了么
else right--; //这个时候最小值可能在左边也可能在右边,要进行先行搜索
//可以写为left++么?不可以,因为如果不能保证现有区间内一定有一个值和left相同,如果left刚好是最小值呢,那不就gg了
}
return rotateArray[left]; // 为啥是left呢?因为最终一定会mid,left,right指向同一个元素,此时right需要--;left才是真正的结果
}
};
16、给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
class Solution {
public:
// 和为0的最长连续子数组
int findMaxLength(vector<int>& nums) {
for(int i = 0; i<nums.size();++i)
if(nums[i] == 0) nums[i] = -1;
int sum_j = 0;
int start = 0;
int finish = 0;
// 分别记录前缀和的最大和最小索引
unordered_map<int,int> memMin;
unordered_map<int,int> memMax;
for(int i = 0; i<nums.size(); ++i){
sum_j += nums[i];
if(memMin.count(sum_j)){
memMax[sum_j] = max(memMax[sum_j],i);
}else{
memMin[sum_j] = i;
memMax[sum_j] = i;
}
}
for(auto it = memMin.begin(); it != memMin.end(); ++it){
int minIndex = it->second;
int maxIndex = memMax[it->first];
if(it->first == 0) //如果前缀和为0,那么只需要记录最大的index就可以了,最小值设置为-1(不能设置为0,因为我们是包含0号索引的)
minIndex = -1;
if(maxIndex-minIndex > finish-start){
start = minIndex;
finish = maxIndex;
}
}
return finish-start;
}
};
17、判断ip地址的有效性
编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址。
如果是有效的 IPv4 地址,返回 “IPv4” ;
如果是有效的 IPv6 地址,返回 “IPv6” ;
如果不是上述类型的 IP 地址,返回 “Neither” 。
IPv4 地址由十进制数和点来表示,每个地址包含 4 个十进制数,其范围为 0 - 255, 用(".")分割。比如,172.16.254.1;
同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。
IPv6 地址由 8 组 16 进制的数字来表示,每组表示 16 比特。这些组数字通过 (":")分割。比如, 2001:0db8:85a3:0000:0000:8a2e:0370:7334 是一个有效的地址。而且,我们可以加入一些以 0 开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的 IPv6 address地址 (即,忽略 0 开头,忽略大小写)。
然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (:😃 的情况。 比如, 2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。
同时,在 IPv6 地址中,多余的 0 也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334 是无效的。
// 这一题写的就是纯逻辑做好判断:首先是分隔符的个数要判断,然后是字符串的大小,长度要判断好,是否是0开头要判断好
class Solution {
public:
string validIPAddress(string IP) {
string isIP4 = "IPv4", isIP6 = "IPv6", neither = "Neither";
if(count(IP.begin(),IP.end(),'.')) return validIP4(IP);
else return validIP6(IP);
return neither;
}
string validIP4(string IP){
int index = 0;
string tmp;
int dotCnt = 0;
while(index < IP.size()){
if(IP[index] == '.'){
int integer = atoi(tmp.c_str()); // 如果越界返回-1
//判断是否以0开头 判断两个点之间是否有数字 判断是否越界
if(++dotCnt == 4 || tmp.size() == 0 || (tmp[0] == '0' && tmp.size() != 1) || !(integer>=0 && integer<=255))
return "Neither";
tmp = "";
}else if(IP[index] >= '0' && IP[index] <= '9'){
tmp += IP[index];
}else
return "Neither";
index++;
}
if(tmp.size() == 0 || (tmp[0] == '0' && tmp.size() != 1) || atoi(tmp.c_str()) > 255 || dotCnt != 3)
return "Neither";
return "IPv4";
}
string validIP6(string IP){
if(IP.size() == 0) return "Neither";
int index = 0;
string pattern = "0123456789abcdefABCDEF";
string tmp="";
int cnt = 0;
while(index < IP.size()){
if(IP[index] == ':'){
if(++cnt == 8 || tmp.size() == 0 || tmp.size() > 4) return "Neither";
tmp = "";
}else if(count(pattern.begin(),pattern.end(),IP[index]) == 1){
tmp += IP[index];
}else
return "Neither";
index++;
}
if(tmp.size() == 0 || tmp.size() > 4 || cnt != 7)
return "Neither";
return "IPv6";
}
};
18、atoi函数实现
class Solution {
public:
// 前置空格,符号,非法字符,溢出
int strToInt(string str) {
int idx = 0;
int len = str.size();
int flag = 1;
int res = 0;
// space
while(idx<len && str[idx] == ' ') idx++;
//sign
if(str[idx] == '-'){
flag = -1;
idx++;
}else if(str[idx] == '+') idx++;
while(idx < len){
if(str[idx]<'0' || str[idx]>'9')
return res*flag;
int num = str[idx]-'0';
if(res > INT_MAX/10 || (res == INT_MAX/10 && num > INT_MAX%10))
return flag==-1?INT_MIN:INT_MAX;
res = 10*res + num;
idx++;
}
return res*flag;
}
};
19、正则表达式匹配,通配符匹配
// 正则表达式
class Solution {
public:
bool isMatch(string s, string p) {
return helper(s,0,p,0);
}
map<pair<int,int>,bool> mem;
bool helper(string s, int i, string p, int j){
if(j == p.size()) return s.size()==i;
if(mem.count(make_pair(i,j))) return mem[make_pair(i,j)];
bool cur = (i<s.size()) && (s[i]==p[j] || p[j]=='.');
// j<=p.size()-2 因为“a*”这种,如果没有等于,那么就不会进行*的判断了
if(j<=p.size()-2 && p[j+1] == '*'){
bool noMatch = helper(s,i,p,j+2);
bool oneMatch = cur && helper(s,i+1,p,j);
mem[make_pair(i,j)] = noMatch || oneMatch;
}else
mem[make_pair(i,j)] = cur && helper(s,i+1,p,j+1);
return mem[make_pair(i,j)];
}
};
// 通配符匹配,和正则表达式还是有点不同,
class Solution {
public:
bool isMatch(string s, string p) {
return doHelp(s,0,p,0);
}
map<pair<int,int>,int> mem;
bool doHelp(string &s, int i, string &p, int j){
if(j == p.size()) return s.size() == i;
if(i == s.size()) return p.size() == j || (p[j] == '*' && doHelp(s,i,p,j+1));
if(mem.count(make_pair(i,j))) return mem[make_pair(i,j)];
//当前字符是否匹配
bool ans = (i<s.size())&&((s[i] == p[j]) || (p[j] == '?'));
if(p[j] == '*'){
// 可以匹配0个,也可以匹配1个
ans = doHelp(s,i,p,j+1) || doHelp(s,i+1,p,j);
mem[make_pair(i,j)] = ans;
return ans;
}
mem[make_pair(i,j)] = ans && doHelp(s,i+1,p,j+1);
return mem[make_pair(i,j)];
}
};
20、回溯系列复习
子集
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> tmpStore;
helper(nums,0,tmpStore);
return res;
}
vector<vector<int>> res;
void helper(vector<int> &nums, int start, vector<int> &tmpStore){
res.push_back(tmpStore);
if(tmpStore.size() == nums.size())
return;
for(int i = start; i<nums.size(); ++i){
// 做选择
tmpStore.push_back(nums[i]);
helper(nums,i+1,tmpStore);
// 撤销选择
tmpStore.pop_back();
}
}
};
21、动态规划系列复习
22、并查集复习
23、二分复习
在排序数组中查找元素的第一个和最后一个位置
class Solution {
public:
// 二分查找,查找第一个和最后一个位置
vector<int> searchRange(vector<int>& nums, int target) {
int left = searchLeft(nums,target);
int right = searchRight(nums,target);
return {left,right};
}
int searchLeft(vector<int> &nums, int target){
int left = 0, right = nums.size()-1;
while(left <= right){
int mid = left + (right-left)/2;
if(nums[mid] == target) right = mid-1;
else if(nums[mid] > target) right = mid -1;
else if(nums[mid] < target) left = mid + 1;
}
if(left >= nums.size() || nums[left] != target) return -1;
return left;
}
int searchRight(vector<int> &nums, int target){
int left = 0, right = nums.size()-1;
while(left <= right){
int mid = left + (right-left)/2;
if(nums[mid] == target) left = mid + 1;
else if(nums[mid] > target) right = mid-1;
else if(nums[mid] < target) left = mid + 1;
}
if(right <0 || nums[right] != target) return -1;
return right;
}
};
24、大数相加,大数相乘,快速幂等小技巧
大数相加
class Solution {
public:
// 大数加法
// 有三个点需要特别注意的,再代码中已经标出
string addStrings(string num1, string num2) {
int carry = 0, sum = 0;
int i = num1.size()-1, j = num2.size()-1;
string res;
while(i >= 0 || j >= 0){
// 1、如果越界了就补0
int n1 = i>=0? num1[i]-'0':0;
int n2 = j>=0? num2[j]-'0':0;
sum = n1 + n2 + carry;
carry = sum/10;
// 2、直接再字符串前面添加,后面就不用反转了
res = to_string(sum%10) + res;
i--;
j--;
}
// 2、 最后还要判断carry是否是1
return carry==1?'1'+res:res;
}
};
大数相乘
class Solution {
public:
string multiply(string str1, string str2) {
if(str1 == "0" || str2 == "0") return "0";
int len1 = str1.size(), len2 = str2.size();
// 结果的最大长度为len1+len2
vector<int> res(len1+len2,0);
for(int i = len1-1; i>=0; --i){
int n1 = str1[i]-'0';
for(int j = len2-1; j>=0; --j){
int n2 = str2[j]-'0';
int sum = res[i+j+1]+n1*n2;
res[i+j+1] = sum%10;
res[i+j] += sum/10;
}
}
string strRes;
for(int i = 0; i<res.size(); ++i){
if(i ==0 && res[i] == 0) continue;
strRes += to_string(res[i]);
}
return strRes;
}
};
25、不用运算符实现加法,位运算相关
不用运算符实现加法
class Solution {
public:
int add(int a, int b) {
// 不用运算符实现加法
// ^ 没有进位的加法
// & 就是进位的值
while(b){
// c++中负数不支持左移位
int c = (unsigned int)(a&b)<<1;
a = a^b;
b = c;
}
return a;
}
};
26、跳跃游戏
跳跃游戏1
class Solution {
public:
// 跳跃游戏
bool canJump(vector<int>& nums) {
int len = nums.size();
if(len <= 1) return true;
//vector<int> dp(nums.size(),0); //nums[i]可以到达的最大索引
//dp[0] = nums[0];
int prev = nums[0];
int res = prev;
for(int i = 1; i<len-1; ++i){
// 判断上一步是否能达到当前位置
if(prev < i) return false;
res = max(i + nums[i],prev);
prev = res;
}
return res>=len-1;
}
};
跳跃游戏2:找出能到达数组最后一个位置的最小跳跃次数
class Solution {
public:
int jump(vector<int>& nums) {
// 能跳跃到数组最后一个位置的最小跳跃次数
int len = nums.size();
if(len <= 1) return 0;
int maxLen = nums[0];
int tmp = maxLen;
int step = 1;
for(int i = 1; i<len-1; ++i){
tmp = max(i+nums[i],tmp); // 记录下一次能跳跃到的最大位置
if(maxLen == i){ // 到达当前跳的最大位置,进行下一条,保证每一跳都是跳跃到最大的位置
maxLen = tmp;
step++;
}
}
return step;
}
};
滑动窗口
最小覆盖子串
class Solution {
public:
// 最小覆盖子串
string minWindow(string s, string t) {
unordered_map<char,int> need, win;
for(auto c:t) need[c]++;
int left = 0, right = 0;
int valid = 0;
int start = 0, len = s.size()+1;
while(right < s.size()){
// 向右收缩
char c = s[right];
right++;
if(need.count(c)){
win[c]++;
if(need[c] == win[c])
valid++;
}
// 向左收缩,直到不满足条件
while(valid == need.size()){
if(right- left < len){
start = left;
len = right-left;
}
char d = s[left];
left++;
if(need.count(d)){
if(need[d] == win[d])
valid--;
win[d]--;
}
}
}
return len==s.size()+1?"":s.substr(start,len);
}
};
所有可能的路径
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
vector<int> path;
help(graph,0,path);
return res;
}
void help(vector<vector<int>> &graph, int s, vector<int> &path){
//加入路径
path.push_back(s);
if(s == graph.size()-1){
res.push_back(path);
path.pop_back(); //这里也要将最后一个节点pop出来
return;
}
for(auto v:graph[s]){
// 遍历邻接表
help(graph,v,path);
}
path.pop_back();
}
};
单调栈
下一个更大元素 II
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
stack<int> st;
vector<int> res(nums.size());
// 循环数组
for(int i = nums.size()-1; i>=0; --i){
st.push(nums[i]);
}
for(int i = nums.size()-1; i>=0; --i){
while(!st.empty() && nums[i] >= st.top()){
st.pop();
}
res[i] = st.empty()?-1:st.top();
st.push(nums[i]);
}
return res;
}
};
每日温度
class Solution {
public:
// 每日温度: 单调栈使用
vector<int> dailyTemperatures(vector<int>& temperatures) {
vector<int> res(temperatures.size());
stack<int> st;
for(int i = temperatures.size()-1; i>=0; --i){
while(!st.empty() && temperatures[i] >= temperatures[st.top()]){
st.pop();
}
// 栈顶元素大于当前元素
res[i] = st.empty()?0:(st.top()-i);
st.push(i); //因为要求索引的差值,所以这里直接存放索引,在while中利用索引再计算对应的值
}
return res;
}
};
图的dfs
课程表
class Solution {
public:
// 主要判断这个课程表的图中是否有环存在
bool hasCircle = false;
// s为遍历图的起始点
void dfs(vector<vector<int>> &graph, int s, vector<bool> &visited, vector<bool> &onPath){
if(onPath[s] == true)
{
hasCircle = true;
}
if(visited[s])
return;
/* 前序遍历代码位置 */
visited[s] = true;
onPath[s] = true;
for(auto t:graph[s]){
dfs(graph,t,visited,onPath);
}
onPath[s] = false;
/* 后续遍历代码位置 */
}
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
// 建图: 领接表
vector<vector<int>> graph(numCourses,vector<int>());
for(auto pre:prerequisites){
int from = pre[1];
int to = pre[0];
graph[from].push_back(to);
}
vector<bool> visited(numCourses,false);
vector<bool> onPath(numCourses,false);
// 不是所有的节点都相连,需要用for将所有节点都调用一次
for(int i = 0; i<numCourses; ++i){
dfs(graph,i,visited,onPath);
}
return !hasCircle;
}
};
拓扑排序
课程表2
class Solution {
public:
// 拓扑排序:必须要是一个有向无环图,则图的拓扑排序就是将这个图拉平,同时所有的箭头指向相同
// 所以:1、首先要进行是否有环的检测 2、进行拓扑排序, 图的后序遍历的逆序就是拓扑排序的结果
//(后序遍历相当于是先遍历子节点然后再遍历父节点,相当于父节点依赖于子节点。 而拓扑排序的结果是所有节点指向向右,也就是子节点依赖于父节点
// 因此他们的依赖关系是相反的,拓扑排序是后序遍历结果的逆序)
// 拓扑排序的结果就是可行的一个课程安排
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
// 建领接表
vector<vector<int>> graph(numCourses);
for(auto pre: prerequisites){
int from = pre[1];
int to = pre[0];
graph[from].push_back(to);
}
//判断是否有环
vector<bool> onPath(numCourses);
vector<bool> visited(numCourses);
// 因为可能不是所有的节点都联通
for(int i = 0; i<numCourses; ++i)
dfs(graph, i, visited, onPath);
if(hasCircle){
// 有环,直接返回
return {};
}
// 无环, 进行拓扑排序
vector<bool> visited2(numCourses);
for(int i = 0; i<numCourses; ++i){
travase(graph,i,visited2);
}
return vector<int>(res.rbegin(),res.rend());
}
vector<int> res;
void travase(vector<vector<int>>&graph, int s, vector<bool> &visited){
if(visited[s])
return;
visited[s] = true;
for(auto g: graph[s]){
travase(graph, g, visited);
}
// 后序遍历结果
res.push_back(s);
}
bool hasCircle = false;
void dfs(vector<vector<int>> &graph, int s, vector<bool> &visited, vector<bool> &onPath){
// 这个是否有环的判断要在是否遍历过之前判断
if(onPath[s]){
hasCircle = true;
}
if(visited[s]) {
return;
}
onPath[s] = true; //判断是否有环
visited[s] = true;
for(auto g:graph[s]){
dfs(graph, g, visited, onPath);
}
onPath[s] = false;
}
};
滑动窗口的最大值
vector<int> help(const vector<int>& num, unsigned int size) {
vector<int> res;
int left = 0, right = 0;
int win = 0;
priority_queue<node *> q;
while(right < num.size()){
int c = num[right];
right++;
win++;
q.push(new node(c, right-1));
if(win == size){
node* tmp = q.top();
res.push_back(tmp->val);
// 用一个数组暂时存储,然后再放进去。将left的数据pop出来
vector<node *> store;
while(q.top()->index != left){
store.push_back(q.top());
q.pop();
}
q.pop();
for(auto x: store)
q.push(x);
left++;
win--;
}
}
return res;
}
数组中的逆序对
class Solution {
public:
// 数组中逆序对: 看成一个排序问题,利用归并排序,排序过程中统计逆序对的个数
int reversePairs(vector<int>& nums) {
mergeSort(nums,0,nums.size()-1);
return res;
}
int res = 0;
void mergeSort(vector<int> &nums, int left, int right){
if( left < right){
int mid = left + (right-left)/2;
mergeSort(nums,left,mid);
mergeSort(nums,mid+1,right);
merge(nums, left, mid, right);
}
}
void merge(vector<int> &nums, int left, int mid, int right){
int i = left, j = mid+1;
int len = right-left+1;
vector<int> tmp(len,0);
int index = 0;
while(i<=mid && j<=right){
if(nums[i] <= nums[j]){
tmp[index++] = nums[i++];
res += (j-(mid+1)); // 需要计算的地方, 计算右边数组中比左边某个元素小的个数
}else{
tmp[index++] = nums[j++];
}
}
while(i<=mid){
tmp[index++] = nums[i++];
res += (right-(mid+1)+1); //需要计算的地方,右边数组已经遍历完全,那么左边数组的每个元素,都需要累加右边数据长度个逆序对
}
while(j<=right){
tmp[index++] = nums[j++];
}
for(int k = 0; k<len; ++k){
nums[left+k] = tmp[k];
}
}
};
min栈
class MinStack {
public:
/** 这种写法是 O(n)的空间复杂度和O(1)的时间复杂度,怎么优化为O(1)的空间复杂度?
O(1)空间和O(1)时间复杂度:在栈中存放元素和min的差值
*/
void push(int x) {
st.push(x);
if(stMin.empty() || stMin.top() >= x) stMin.push(x);
}
void pop() {
if(st.top() == stMin.top())
stMin.pop();
st.pop();
}
int top() {
if(st.empty())
return -1;
return st.top();
}
int min() {
return stMin.top();
}
private:
stack<int> st, stMin;
};
// 最优的解法如何写
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
if(st.empty()){
st.push(0);
minVal = x;
}else{
int sub = x-minVal;
st.push(sub); // 记录x和minVal的差值
minVal = sub<0?x:minVal; // 总是记录最小值
}
}
void pop() {
int top = st.top();
if(top<0){
// 说明要弹出的这个值是最小值,那么需要当前的最小值和top值求得弹出这个值之后的最小值
minVal = minVal - top;
}
st.pop();
}
int top() {
return minVal + st.top(); // 有问题
//return -1;
}
int min() {
return minVal;
}
private:
stack<int> st;
int minVal;
};
链表随机节点
class Solution {
public:
/** @param head The linked list's head.
Note that the head is guaranteed to be not null, so it contains at least one node. */
Solution(ListNode* head) {
this->head = head;
}
/** 对于一个这样的长度未知的链表来说,对于第i个节点,每次有1/i的概率选择该节点,1-1/i的概率保持原有的选择
如果是选择k个节点,那么就是k/i的概率选择该节点,1-k/i的概率保持原有的选择。
因为:
1/i * (1-1/(i+1) * (1-1/(i+2) * ... (1-1/n))) = 1/n
也就是说,每个节点被选择的概率是相同的
*/
int getRandom() {
ListNode *cur = head;
int res = -1;
int i = 1;
while(cur){
if( rand()%i == 0){
// 获取的值的范围是 0~i-1, 因此获取0的概率是 1/i
// 也就是 1/i 的概率选择该节点, 否则保持原来的选择
res = cur->val;
}
i++;
cur = cur->next;
}
return res;
// 随机选择k个如何写?
int k = 5;
ListNode *p = head;
vector<int> resk(k);
// 首先选k个
for(int i = 0; i<k && p!=nullptr; ++i){
resk[i] = p->val;
p = p->next;
}
// 以 k/i的概率选择当前节点
i = k;
while(p){
int r = rand()%i;
if(r < k){
resk[k] = p->val;
}
i++;
p = p->next;
}
private:
ListNode *head;
};
随机数索引
class Solution {
public:
Solution(vector<int>& nums) {
this->nums = nums;
}
// 方法1: 暴力:用一个map或者二维数组存放索引然后直接rand获取一个随机的索引值
// 方法2:水塘抽水算法
int pick(int target) {
int i = 1;
int index = 0;
int res = 0;
while(index < nums.size()){
if(nums[index] == target){
int r = rand()%i;
if(r == 0){
res = index;
}
i++;
}
index++;
}
return res;
}
private:
vector<int> nums;
};
丑数
class Solution {
public:
// 丑数:最小堆
int nthUglyNumber(int n) {
priority_queue<long, vector<long>, greater<long>> minQ;
unordered_map<int,int> deDup; // 去重, 优先队列是可以重复的
minQ.push(1);
deDup[1] = 1;
for(int i = 2; i<=n; ++i){
long top = minQ.top();
minQ.pop();
for(auto factor: {2,3,5}){
long tmp = top*factor;
if(!deDup.count(tmp)){
minQ.push(tmp);
deDup[tmp] = 1;
}
}
}
return minQ.top();
}
};
滑动窗口的最大值
class Solution {
public:
// 滑动窗口的最大值: 优先队列 或者 单调队列
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
return helperFunction(nums,k);
vector<int> res;
if(k == 0 || nums.size() == 0) return res;
// 将窗口中的值放入到优先队列中
priority_queue<pair<int,int>> q;
for(int i = 0; i<k; ++i){
q.push(make_pair(nums[i],i));
}
res.push_back(q.top().first);
for(int i = k; i<nums.size(); ++i){
q.push(make_pair(nums[i],i));
// 如果当前的最大元素不在滑动窗口之内,就pop出来,那么留下来的就是最大的滑动窗口值
while(q.top().second <= i-k){
q.pop();
}
res.push_back(q.top().first);
}
return res;
}
// 单调队列:
vector<int> helperFunction(vector<int> &nums, int k){
if(k == 0 || nums.size() == 0) return {};
deque<int> d;
for(int i = 0; i<k; ++i){
while(!d.empty() && nums[i] >= nums[d.back()]){
d.pop_back();
}
d.push_back(i); // 放入的是索引
}
vector<int> res{nums[d.front()]};
for(int i = k; i<nums.size(); ++i){
// 保留最大元素的索引
while(!d.empty() && nums[i] >= nums[d.back()]){
d.pop_back();
}
d.push_back(i);
// 提出窗口之外的最大值
while(!d.empty() && d.front() <= i-k){
d.pop_front();
}
res.push_back(nums[d.front()]);
}
return res;
}
};
队列的最大值
class MaxQueue {
public:
MaxQueue() {
}
int max_value() {
// 单调队列
if(dq.empty())
return -1;
return dq.front();
}
void push_back(int value) {
// 单调队列:栈中的元素是单调的,本题中,是单调递减的
while(!dq.empty() && dq.back() < value){
dq.pop_back();
}
dq.push_back(value);
MaxQ.push(value);
}
int pop_front() {
if(MaxQ.empty()){
return -1;
}
int front = MaxQ.front();
if(front == dq.front())
dq.pop_front();
MaxQ.pop();
return front;
}
private:
queue<int> MaxQ;
deque<int> dq;
};
构建乘积数组
class Solution {
public:
// 构建乘积数组:不能使用除法: 首先构建res数组,利用前缀的特性,然后继续乘
vector<int> constructArr(vector<int>& a) {
vector<int> res(a.size(),1);
if(a.size() == 0){
return {};
}
// 矩阵的下三角
for(int i = 1; i<res.size(); ++i){
res[i] = res[i-1]*a[i-1];
}
// 从后向前计算上三角
int tmp = a[a.size()-1];
for(int i = a.size()-2; i>=0; --i){
res[i] = res[i]*tmp;
tmp = tmp * a[i];
}
return res;
}
};
缺失的第一个正数
class Solution {
public:
// 缺失的第一个正数:原地哈希的思想,将对应的元素值映射到对应索引的位置
/*
例如:将1映射到nums[1]的位置上, 2映射到nums[2]上
*/
int firstMissingPositive(vector<int>& nums) {
nums.push_back(-1);
int len = nums.size();
for(int i = 0; i<len; ++i){
// 因为交换之后还可能需要继续交换,因此需要用循环不断判断和交换
// nums[i] != nums[nums[i]] 的判断很重要,而不是 i != nums[i]: 写后面的可能会死循环,如输入[1,1]
// 前者,包含的意思是nums[i]位置的元素不等于nums[i],而i位置的元素等于nums[i],可以进行交换使得nums[i]位置的元素等于nums[i]
while(nums[i]>=0 && nums[i]<len && nums[i] != nums[nums[i]]){
swap(nums[i], nums[nums[i]]);
}
}
for(int i = 1; i<len; ++i){
if(nums[i] != i){
return i;
}
}
return len;
}
};
数字字符串转换成ip地址
class Solution {
public:
/**
*
* @param s string字符串
* @return string字符串vector
*/
vector<string> restoreIpAddresses(string s) {
// write code here
string tmp;
helper(s, 0, tmp, 0);
return res;
}
vector<string> res;
void helper(string s, int start, string tmp, int step){
if(step==4 && start==s.size()){
res.push_back(tmp.substr(0, tmp.size()-1));
return;
}
if(step==4 || start==s.size()){
return;
}
for(int i = start; i<start+3; ++i){
if(i>=s.size()){
return;
}
// 这里 i==start+2 是必须的,只有三个元素的时候进行比较才是正确的 "4">"255"
if(i == start+2 && s.substr(start, i-start+1) > "255"){
//cout<<"substr: "<<s.substr(start, i-start+1)<<" "<<tmp<<endl;
return;
}
// 防止.01.出现
if(s[start]=='0' && i != start){
return;
}
tmp += s[i];
tmp += '.';
//cout<<tmp<<" "<<start<<" "<<step<<endl;
helper(s, i+1, tmp, step+1);
tmp = tmp.substr(0, tmp.size()-1);
}
}
};
最长有效括号
class Solution {
public:
/**
*
* @param s string字符串
* @return int整型
*/
// 栈: 栈底保持为 最后一个没有被匹配的右括号的下标, 方下标是个小技巧\
// 栈底的右括号坐标,主要是为了 )()()() 这种情况,就是刚好后面的匹配完,需要记录上次没有
// 匹配的位置,计算最大值
int longestValidParentheses(string s) {
// write code here
stack<int> st;
st.push(-1);
int res = 0;
for(int i = 0; i<s.size(); ++i){
if(s[i] == '('){
st.push(i);
}else{
st.pop();
if(st.empty()){
st.push(i);
}else{
res = max(res,i-st.top());
}
}
}
return res;
}
};
前缀树 trie
class Trie {
public:
Trie() {
isEnd = false;
//cout<<sizeof(next)<<endl;
memset(next, 0, sizeof(next)); // sizeof返回占有的字节数
}
void insert(string word) {
// 构建前缀树,像是构建链表一样
Trie *node = this;
for(auto c: word){
int idx = c-'a';
if(node->next[idx] == nullptr){
node->next[idx] = new Trie();
}
node = node->next[idx];
}
node->isEnd = true;
}
bool search(string word) {
Trie *node = this;
for(auto c: word){
int idx = c-'a';
if(node->next[idx] == nullptr){
return false;
}
node = node->next[idx];
}
return node->isEnd;
}
bool startsWith(string prefix) {
Trie *node = this;
for(auto c: prefix){
int idx = c-'a';
if(node->next[idx] == nullptr){
return false;
}
node = node->next[idx];
}
return true;
}
private:
bool isEnd;
Trie *next[26]; // 指针数组,字母映射表,每个节点的下一个节点可能对应的字母,用对应的26个字母的位置表示
};
二叉树中的最大路径和
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int res = INT_MIN;
int maxPathSum(TreeNode* root) {
maxPathSumHelper(root);
return res;
}
/* 定义这个函数为过root节点的最大路径值*/
int maxPathSumHelper(TreeNode *root){
// base case
if(root == nullptr){
return 0;
}
int left = maxPathSumHelper(root->left);
int right = maxPathSumHelper(root->right);
/*
这里为什么要分开计算呢?
res的值是 val, left+val, right+val 和 left+right+val 中的最大值,但是这个函数的返回值不能是left+right+val
这种情况是横跨了当前节点,不能作为返回值,因为同一个节点在路径中至多出现一次
*/
int cur = max(root->val, max(left+root->val, right+root->val));
int tmp = max(cur, left+right+root->val);
res = max(res, tmp);
return cur;
}
};
迪杰特斯拉算法(743. 网络延迟时间)
class State{
public:
int id;
int distFromStart; // start到当前id节点的最短路径
State(int _id, int _dist):id(_id),distFromStart(_dist){}
};
class cmp{
public:
bool operator()(const State *a, const State *b){
return a->distFromStart < b->distFromStart;
}
};
class Solution {
public:
// odijkstra
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
vector<int> distTo = dijsktra(times, n, k);
int res = -1;
for(int i = 1; i<distTo.size(); ++i){
if(distTo[i] == INT_MAX)
return -1;
res = max(res, distTo[i]);
}
return res;
}
vector<int> dijsktra(vector<vector<int>> ×, int n, int k){
// build graph
vector<vector<int>> graph(n+1, vector<int>());
map<pair<int,int>, int> weight;
for(auto time: times){
graph[time[0]].push_back(time[1]);
weight[make_pair(time[0], time[1])] = time[2];
}
vector<int> distTo(n+1,INT_MAX); //start到i节点的最短路径,初始的时候不可达
distTo[k] = 0;
priority_queue<State*, vector<State*>, cmp> pq;
State *node = new State(k, 0);
pq.push(node);
while(!pq.empty()){
// 选择最有潜力的那个节点,因为是优先队列,所以距离最小的那个潜力最大
State *cur = pq.top();
pq.pop();
int curId = cur->id;
int distFromStart = cur->distFromStart;
if(distFromStart > distTo[curId]){
continue; // 已经有更近的路径到达了当前节点
}
for(int nodeId: graph[curId]){
int nextDist = distTo[curId] + weight[make_pair(curId, nodeId)];
if(nextDist < distTo[nodeId]){
distTo[nodeId] = nextDist;
State *tmp = new State(nodeId, nextDist);
pq.push(tmp);
}
}
} // while
return distTo;
}
};
vector<int> dijsktraV2(vector<vector<int>> &nums, int start, int end, int n){
// build graph
vector<vector<int>> graph(n+1, vector<int>());
vector<pair<int,int>> weight; // 记录weight
for(auto num: nums){
graph[num[0]].push_back(num[1]);
weight[make_pair(num[0], num[1])] = num[2];
}
// bfs
priority_queue<Mem*, vector<Mem*>, cmp> pq;
pq.push(new Mem(start, 0));
vector<int> distTo(n+1, INT_MAX);
distTo[start] = 0;
while(!pq.empty()){
Mem *curNode = pq.top(); // 每次找到优先队列中最短的节点
pq.pop();
int curId = curNode->id;
int curNodeDist = curNode->dist;
// 到达了目的节点,由于是优先队列中弹出的,所以第一次遇见的时候就是最短的
if(curId == end){
return curNodeDist;
}
//已经有了一条路径到了curId节点,那么解析
if(curNodeDist > distTo[curId]){
continue;
}
for(int nextNodeId: graph[curId]){
int nextNodeDist = distTo[curId] + weight[make_pair(curId, nextNodeId)];
if(nextNodeDist < distTo[nextNodeId]){
distTo[nextNodeId] = nextNodeDist;
pq.push(nextNodeId);
}
}
}// while
return -1;
}