1.二维数组
二分法
int m = arr.size();
int n = arr[0].size();
int r = 0, c = n-1; //右上角
while(r<m && c>=0)
//在左下角之前
if(target > arr[r][c]){
++r;//当前小了,往下找
}
else{
--c;//当前大了,往左找
}
2.替换空格
Java
{
if(s == null || "".equals(s))
return s;
return s.replaceAll(" ","REPLACE");
}
正常
O(N)
O(N)
string newstr='';
for(int i=0;i<str.length();i++)
{
if(s[i] != ' ') newstr += str[i];
else newstr += '%20';
}
return newstr;
O(N)
O(1) 用的原来的str,只是用常数个零食变量
双指针
漫漫云天字翱翔 已收藏
//先找到所有的空格
if(str[i] == ' ') spaceCount++;
//所有的空格换成%20
totalLen = length + spaceCount*2
//可以从后往前找
//j是延长后的str的指针
for(int i = length-1, j = totalLen - 1; j>i; j--, i--)
{
if(str[i] != ' ') str[j] = str[i];
else{
str[j]='0';
str[j-1]='2';
str[j-2]='%';
j-=2;
}
}
return s;
- 反向打印链表
后序遍历
if(!root) return;
dfs(root->left);
dfs(root->right);
链表的递归反向
vector<int> ret;
if(!head) return ret;
ret = printfromTailtoHead(head->next);
ret.push_back(head->val);
return ret;
4.(重要)重建二叉树
二叉树
前序:根左右
中序:左根右
后序:左右根
TreeNode* reconstructBT(vector<int> pre, vector<int> vin){
if(pre.size()==0||vin.size()==0) return NULL;
vector<int> PRE_LEFT, PRE_RIGHT, VIN_LEFT, VIN_RIGHT;
TreeNode* ret = new TreeNode(pre[0]);
//找vin中根节点所在的位置存在gen中
int gen=0;
for(int i=0;i<vin.size();i++){
if(vin[i]==pre[0]){
gen = i;
break;//找到根节点在中序遍历中的位置
}
}
//对中序遍历,根节点在左右子树中间
//左子树
for(int i=0;i<gen;i++){
VIN_LEFT.push_back(vin[i]);
PRE_LEFT.push_back(pre[i+1]);//先序遍历第一个是根节点,之后先是所有的左子树
}
//右子树
for(int i =gen+1;i<vin.size();i++){//从gen+1开始
VIN_RIGHT.push_back(vin[i]);
PRE_RIGHT.push_back(pre[i]);
}
//递归建树
ret->left = reconstructBT(PRE_LEFT, VIN_LEFT);
ret->right = reconstructBT(PRE_RIGHT, VIN_RIGHT);
return ret;
}
5.(简单经典)用两个栈实现队列
stack<int> s1;//保存用
stack<int> s2;//辅助用
push正常push
pop分情况
int pop(){
//情况1:不止一个元素,需要用s2的辅助栈
while(s1.size() != 1){
s2.push(s1.top());
s1.pop();//stack是FILO,我们要FIFO,这里不是return,只是找到first in
}
int value = s1.top();//FI element
s1.pop();//FI要pop
//情况2:把辅助栈里的元素放回数据栈
while(!s2.empty()){
s1.push(s2.top());
s2.pop();
}
return value;
}
- 旋转数组的最小数字
int minArray(vector<int>& numbers) {
//二分查找
int low=0;
int high=numbers.size()-1;
while(low<high)
{
int mid = low + (high-low)/2;
if(numbers[mid]<numbers[high]){
high = mid;//我们在找最小值
}
else if(numbers[mid]>numbers[high]){
low = mid+1;//前面的肯定是递增,都比high高只能往后找
}
else{
high -= 1;
}
}
return numbers[low];//找的最小值
}
7.(入门经典)
递归很慢
int Fib(int n){
if(n==0) return 0;
if(n==1||n==2) return 1;
return Fib(n-1)+Fib(n-2);
}
很简单的好解法
int Fib(int n){
if(n==0) return 0;
if(n==1||n==2) return 1;
int first=0,second=1,third=1;
for(int i=2;i<=n;++i){
third=first+second;
first=second;
second=third;
}
return third;
}
更好
int fib(int n) {
int a=0,b=1,c=0;//注意于fib的区别
for(int i=0;i<n;++i){
a=b,b=c;
c=(a+b)%1000000007;
}
return c;//注意于fib的区别
}
8(经典简单)青蛙跳台阶
*和Fib其实一样
- 递归,太慢
int jump(int n){
if(n==1) return 1;
if(n==2) return 2;
return jump(n-1)+jump(n-2);
//有n级台阶的跳法等于n-1和n-2两种情况的综合
}
- Fib
int Fib(int n){
if(n<=2) return n;
int first=1,second=2,third=0;
for(int i=3;i<=n;++i){
third=first+second;
first=second;
second=third;
}
return third;
}
更好
int numWays(int n) {
int a=0,b=1,c=1;//注意于fib的区别
for(int i=0;i<n;++i){
a=b,b=c;
c=(a+b)%1000000007;
}
return b;//注意于fib的区别
}
9 复杂的跳台阶
n级台阶,
第一步有n种可能
第二步可能是f(n) = f(n-1)+f(n-2)+…+f(1)
同理,f(n-1) = f(n-2)+f(n-3)+…+f(1)
所以,f(n) = 2f(n-1)
从这里可得,每次都是2
已知f(1)=1;f(2)=2;
2^(n-1)
return pow(2,n-1)
10 矩阵覆盖
依然是Fib的变种
n个21的小矩形覆盖一个2n的大矩形
已知f(1)=1;f(2)=2;
f(n) = f(n-1) + f(n-2)
因为一条
hen着覆盖,2n就变成2(n-1)
shu着,变成2*(n-2)//因为是2*n,占掉两格另外一边也固定了
11 32位二进制中1的个数
思路:一个不为零的数-1后,二进制原来最右边的1->0,其后所有0变为1,1保持不变。
–>把一个整数-1,在和原来做and运算,会把整数最右的一个1变成0
–>可以重复多少次,就有几个1
int ret=0;
while(n!=0){
n=n&(n-1);
ret++;
}
return ret;
用CPP的bit set
bitset<32> bit(n);
return bit.count();
- (再看)double的int次方
快速幂
x^n
double myPow(double x, int n){
double res=1;
double base=x;
if(n<0){
n = -(++n);//abs(INT_MIN)>INT_MAX,先自增1
}
while(n>0){
if(n&1) res*=x;
n=n>>1;
x*=x;
}
return n<0?1/(res*base):res;//这里补上了自增的1
}
13 奇数跳到偶数前
1.直接遍历两边,一次奇数一次偶数
2.头尾双指针
O(N)
O(N)
遍历一遍
int res[arr.size];
head=idx_head=0;
tail=idx_tail=arr.size()-1;
while(head<arr.size() && tail >=0){
if(arr[head]%2==1){
res[idx_head]=arr[head];
idx_head++;
}
head++;//head只处理奇数
if(arr[tail%2==0]){
}
- 链表中的倒数第k节点
双指针
slow = head;
while(k!=0){
k--;
if(head != nullptr) head = head->next;
else
return nullptr;
}
//head先走k步
while(head != nullptr){
slow = slow->next;
head = head->next;
}
return slow;
- 反转链表
3指针
//初始化
pre = nullptr;
cur = head;
next = nullptr;
while(cur){
next = cur->next;//保存
cur->next = pre;//反转
pre = cur;//更新头节点
cur = next;//移向下一个
}
return pre;
16.合并两个排序的列表
一般创建单列表都会设一个虚拟头节点(哨兵)
if(head1 == nullptr) return head2;
if(head2 == nullptr) return head1;
ListNode* vhead = new ListNode(-1);
ListNode* cur = vhead;
while(head1 && head2){
if(head1->val <= head2->val){
cur->next=head1;
head1=head1->next;
}
else{
cur->next=head2;
head2=head2->next;
}
cur=cur->next;
}
cur->next = (head1 !=nullptr ? head1:head2);
return vhead->next;
TODO: 递归版本
- 树的子结构(再看下)
树的题目,大多数都用递归
tree1中是否含有tree2
bool core(root1, root2){
if(root2 == nullptr) return true;
if(root1 == nullptr) return false;
if(root1->val == root2->val){
//根一样,再判断左右子树
return core(root1->left,root2->left) && core(root1->right,root2->right)
}
return false;
}
bool hasSubtree(root1,root2){
if(root1==nullptr||root2==nullptr) return false;
return hasSubtree(root1->left,root2)||hasSubtree(root1->right,root2)||core(root1,root2);
}
- 二叉树的镜像
有很多东西,可复习
void Mirror(TreeNode* proot){
if(proot == nullptr) return;
swap(proot->left,proot->right);
Mirror(proot->left);
Mirror(proot->right);
}
- 顺时针打印矩阵
再看,很考验思路
vector<int> spiralOrder(vector<vector<int>>& matrix){
if(matrix.size() == 0 || matrix.size() ==0) return vector<int> ();
int left = 0, right = matrix[0].size() - 1, top = 0, bottom = matrix.size() - 1;
while(left<=right && top<=bottom){
for(int i=left;i<=right;++i){
//cout<<matrix[top][i]<<" ";
result.push_back(matrix[top][i]);
}
if(++top>bottom) break;//top == bottom;
for(int i=top;i<=bottom;++i){
//cout<<matrix[i][right]<<" ";
result.push_back(matrix[i][right]);
}
if (--right<left) break;//避免left=right
for (int i=right;i>=left;--i){
//cout << matrix[bottom][i]<< " ";
result.push_back(matrix[bottom][i]);
}
if(--bottom<top) break;
for(int i=right;i>=left;--i){
//cout << matrix[i][left]<< " ";
result.push_back(matrix[i][left]);
}
if(++left > right) break;
}
return result;
}
- stack实现min函数
时间复杂度O(1)
空间复杂度O(n),开辟一个辅助栈
正常情况下返回最小值需要遍历整个stack
时间复杂度O(n)
双栈法:普通栈normal,辅助栈minval
stack<int> normal, minval;
void push(int val){
normal.push(val);
if(minval.empty()){
minval.push(val);
}
else{
minval.push(std::min(val,minval.top()));//一定要有std
}
}
void pop(){
normal.pop();
minval.pop();
}
int top(){
return normal.top();
}
int min(){
return minval.top();
}
- stack
复习
辅助栈
int len = pushV.size();
int popIndex=0;
stack<int> st;
for(int i=0;i<len;++i){
st.push(pushV[i]);
while(popIndex<len && !st.empty() && st.top() == popV[popIndex]){
st.pop();
popIndex++;//该位pop没问题
}
return st.empty();//如果辅助栈中能全pop出去,即可能
}
- 二叉树
queue<TreeNode*> q;
q.push(root);
TreeNode* temp;
vector<int> result;
while(!q.empty()){
temp = q.front();
q.pop();
result.push_back(temp->val);
if(temp->left) q.push(node->left);
if(node->right) q.push(node->right);
}
return result;
- (未看)使用后序遍历
递归解决树的问题
-
(未看)二叉树路径和
-
双头链表
(可复习)
if (head == nullptr) return nullptr;
unordered_map<Node*, Node*> unmp;
for (Node* p = head; p != nullptr;p=p->next)
{
unmp[p] = new Node(p->val);
}
for (Node* p = head; p != nullptr; p = p->next)
{
unmp[p]->next = unmp[p->next];//这里要注意是 unmp[p->next] 千万注意,好好想想
unmp[p]->random = unmp[p->random];//下同
}
return unmp[head];
26 二叉搜索树与双向链表(未看)
27 字符串的排列(未看)
28 数组中出现次数超过一半的数字
很简单的unordered map,可以自己试一下
int MoreThanHalfNum_Solution(vector<int> numbers) {
unordered_map<int, int>unmp;
int len = numbers.size();
for (int i = 0; i < len; ++i) {
unmp[numbers[i]]++;
if (unmp[numbers[i]] > len / 2) return numbers[i];
}
return 0;
}
-
最小的k个数
pivot是数组中第pos-l+1小的数
类似快排的思路,可以再复习下!
https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/submissions/ -
连续子数组的最大和
简单,可以再看看
分治:O(nlogn)
O(logn)
动态规划:O(n)
O(1)
状态定义:
dp[i]
转移方程:
dp[i-1]>0 --> dp[i]=dp[i-1]+nums[i]
dp[i-1]<=0 --> dp[i] = nums[i]//dp[i-1]+nums[i]比nums[i]小
初始状态:dp[0]=nums[0]
返回值:dp列表中的最大值
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre=0, res=nums[0];
for(auto &x: nums){
pre = max(pre+x,x);
res = max(res,pre);
}
return res;
}
};
31 整数1出现的系数
略过
if(n<10) return 1;
int high=n, pow=1;
while(high>=10){
high=high/10;
pow=pow*10;
}
int last = n-high*pow;
32 数组排成最小的数
略过
33 丑数
x(n+1) = min(xa2,xb3,xc*5)
动态规划
完成
34 只出现一次的字符
用unordered map做,挺简单的
完成
35 数组中的逆排序
跳过
36 链表的第一个公共节点
简单且重要!
双指针
O(m+n)
O(1)
已解决
37 数字出现的次数
方法一:二分查找
经典!!!
直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见 \textit{target}target 的下标,但这个方法的时间复杂度为 O(n)O(n),没有利用到数组升序排列的条件。
由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的过程。
考虑 \textit{target}target 在数组中出现的次数,其实我们要找的就是数组中「第一个等于 \textit{target}target 的位置」(记为 \textit{leftIdx}leftIdx)和「第一个大于 \textit{target}target 的位置减一」(记为 \textit{rightIdx}rightIdx)。当 \textit{target}target 在数组中存在时,\textit{target}target 在数组中出现的次数为 \textit{rightIdx}-\textit{leftIdx}+1rightIdx−leftIdx+1。
二分查找中,寻找 \textit{leftIdx}leftIdx 即为在数组中寻找第一个大于等于 \textit{target}target 的下标,寻找 \textit{rightIdx}rightIdx 即为在数组中寻找第一个大于 \textit{target}target 的下标,然后将下标减一。两者的判断条件不同,为了代码的复用,我们定义 binarySearch(nums, target, lower) 表示在 \textit{nums}nums 数组中二分查找 \textit{target}target 的位置,如果 \textit{lower}lower 为 \rm truetrue,则查找第一个大于等于 \textit{target}target 的下标,否则查找第一个大于 \textit{target}target 的下标。
最后,因为 \textit{target}target 可能不存在数组中,因此我们需要重新校验我们得到的两个下标 \textit{leftIdx}leftIdx 和 \textit{rightIdx}rightIdx,看是否符合条件,如果符合条件就返回 \textit{rightIdx}-\textit{leftIdx}+1rightIdx−leftIdx+1,不符合就返回 00
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/solution/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-wl6kr/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二分查找的时间复杂度为 O(logn),一共会执行两次,因此总时间复杂度为 O(logn)。
int L(int *nums, int x, int size)
{
int l=0, r=size-1, mid=0;
while( l < r ) {
mid = l + (r-l)/2;
if( nums[mid] >= x ) r = mid;
else l = mid + 1;
}
return nums[l] == x?l:0;
}
int R(int *nums, int x, int size)
{
int l=0, r=size-1, mid=0;
while( l < r ) {
mid = l + (r-l)/2 + 1;
if( nums[mid] <= x ) l = mid;
else r = mid - 1;
}
return nums[l] == x?l:-1;
}
int search(int* nums, int numsSize, int target){
if( !numsSize ) return 0;
return R(nums,target,numsSize) - L(nums,target,numsSize) + 1;
}
38 二叉树的深度
简单,重要的递归
int maxDepth(TreeNode* root) {
if(root==nullptr) return 0;
int leftdepth = maxDepth(root->left);
int rightdepth = maxDepth(root->right);
return 1+ max(leftdepth,rightdepth)
}
int height(TreeNode* root) {
if (root == NULL) {
return 0;
} else {
return max(height(root->left), height(root->right)) + 1;
}
}
39 平衡二叉树
int height(TreeNode* root) {
if (root == NULL) {
return 0;
} else {
return max(height(root->left), height(root->right)) + 1;
}
}
bool isBalanced(TreeNode* root) {
if (root == NULL) {
return true;
} else {
return abs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
}
}
40 掠过
41 和为S的整数序列
掠过
42 和为S的两个数字
简单
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> result;
if(nums.size()==0) return result;
int low=0,high=nums.size()-1;
while(low<=high){
if(nums[low]+nums[high]==target){
result.push_back(nums[low]);
result.push_back(nums[high]);
return result;
}
else if(nums[low]+nums[high]<target) low++;
else high--;
}
return result;
}
43 左旋转字符串
string reverseLeftWords(string s, int n) {
int len=s.size();
if(len==0) return s;
if(n>=len) n=n%len;//n超过了str的长度
string temp=s+s;
string result;
result.resize(len);
for(int i=n,idx=0;i<len+n;i++,idx++){
result[idx] = temp[i];
}
return result;
}
- 反转单词序列
掠过
45 扑克牌顺子
简单
重要
bool isStraight(vector<int>& nums) {
int minIndex = 0;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size()-1; i++)
{
if(nums[i] == 0) ++minIndex;
else if(nums[i] == nums[i+1]) return false; //除开为0的情况,存在重复时
}
return nums[4] - nums[minIndex] <= 4;
}
- 圆圈中剩下的数
int lastRemaining(int n, int m) {
if(n==1) return 0;
else{
return (lastRemaining(n-1,m)+m)%n;
}
}
47
48 不使用加号使用加法
while(num2!=0){
int sum=num1^num2;//异或
int carry=(num1&num2)<<1;
num1=sum;
num2=carry;
}
return num1;
49 跳过
- 数组中重复的数字
int findRepeatNumber(vector<int>& nums) {
unordered_map<int, bool> map;
for(int num : nums) {
if(map[num]) return num;
map[num] = true;
}
return -1;
}
51 二叉搜索树公共祖先节点
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector<TreeNode*> path_p=getPath(root,p);
vector<TreeNode*> path_q=getPath(root,q);
TreeNode* ancestor;
for(int i=0;i<path_p.size()&&i<path_q.size();++i){
if(path_p[i]==path_q[i]){
ancestor = path_p[i];
}
else{
break;
}
}
return ancestor;
}
vector<TreeNode*> getPath(TreeNode* root, TreeNode* target){
vector<TreeNode*> path;
TreeNode* node = root;
while(node != target){
path.push_back(node);
if(target->val<node->val){
node=node->left;
}
else{
node=node->right;
}
}
path.push_back(node);
return path;
}
52(68)二叉树的公共节点
*没太看懂
TreeNode* ans;
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
dfs(root,p,q);
return ans;
}
bool dfs(TreeNode* root, TreeNode* p, TreeNode* q){
if(root==nullptr) return false;
bool lson = dfs(root->left,p,q);
bool rson = dfs(root->right,p,q);
if(lson&&rson||((root->val ==p->val||root->val ==q->val)&&(lson||rson))){
ans = root;
//1. lson&&rson说明左子树和右子树都包含p或者q,一个p,一个q
//2. root正好是p或q节点,检查左或右子树是否至少有一个包含p或q
//这两种情况,我们就找到了最近公共祖先
//递归是从底(叶子)向上的,所以所有满足条件的一定是深度最大的
}
return lson||rson||(root->val ==p->val||root->val ==q->val);
}//返回的是root是否包含p或q节点
July28
33
再看下
34
public:
vector<vector<int>> ret;
vector<int> path;
void dfs(TreeNode* root, int target){
if(root==nullptr) return;
path.push_back(root->val);
target -= root->val;
if(root->left==nullptr&&root->right==nullptr&&target==0){
ret.push_back(path);
dfs(root->left,target);
dfs(root->right,target);
path.pop_back();//重要
}
}
vector<vector<int>> pathSum(TreeNode* root, int target) {
dfs(root,target);
return ret;
}
};
//DFS
//O(N^2) 上半部分链状,下半部分完全二叉树
//O(N)
Node* copyRandomList(Node* head) {
if(head==nullptr) return nullptr;
Node* cur=head;
unordered_map<Node*, Node*> map;
//创建新节点
while(cur!=nullptr){
map[cur] =new Node(cur->val);
cur = cur->next;
}
cur = head;
//构建新链表的指向
while(cur!=nullptr){
map[cur]->next = map[cur->next];//重要
map[cur]->random = map[cur->random];//重要
cur = cur->next;
}
//返回
return map[head];
}
再看,要再复习!!没看懂
Node* pre=nullptr, *head=nullptr;
Node* treeToDoublyList(Node* root) {
if(!root) return root;
dfs(root);
head->left=pre;
pre->right=head;
return head;
}
void dfs(Node* cur){
if(cur==nullptr) return;
dfs(cur->left);
if(pre!=nullptr) pre->right=cur;
else head=cur;//保留链表头节点
cur->left=pre;
pre=cur;
dfs(cur->right);
}
再看,没看懂
Offer 44. 数字序列中某一位的数字
int findNthDigit(int n) {
int digit=1;//记录位数
long start=1;//记录某一位数开始的第一个数
long count=digit*start*9;//记录某一位数包含的所有数字个数
//判断第n位数字是几位数
while(n>count){
n-=count;
digit+=1;
start*=10;
count=digit*start*9;
}
//判断第n位数字是几位数
long number = start + (n-1)/digit;
//判断第n位数字在第二步中找出的数是第几位
string s_number = to_string(number);
return s_number[(n-1)%digit] - '0';
}
完全没看懂…
再看
class Solution {
public:
string minNumber(vector<int>& nums) {
vector<string> strs;
for(int i=0;i<nums.size();i++){
strs.push_back(to_string(nums[i]));
}
quickSort(strs,0,strs.size()-1);
string res;
for(string s: strs){
res.append(s);
}
return res;
}
void quickSort(vector<string>& strs, int l, int r){
if(l>=r) return;
int i=l,j=r;
while(i<j){
while(strs[j]+strs[l]>=strs[l]+strs[j]&& i<j) j--;
while(strs[i]+strs[l]<=strs[l]+strs[i]&& i<j) i++;
swap(strs[i],strs[j]);
}
swap(strs[i],strs[l]);
quickSort(strs,l,i-1);
quickSort(strs,i+1,r);
}
};
//快排平均O(nlogn),最差O(N2)
//O(N)
再看,复习下
int translateNum(int num) {
//动态规划转移方程
//f(i)=f(i-1)+f(i-2)
//边界条件f(-1)=0, f(0)=1
//O(n) O(1):滚动数组(常见的空间优化)
string src = to_string(num);
int a=0, b=0, c=1;
for(int i=0;i<src.size();++i){
a=b;
b=c;
c=0;
c = b+c;
if(i==0) continue;
auto pre=src.substr(i-1,2);
if(pre<="25"&&pre>="10"){
c = c+a;
}
}
return c;
}
47 礼物
动态规划,再看
int maxValue(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
//定义动态规划的存储
vector<vector<int>>dp(m,vector<int>(n,0));
dp[0][0]=grid[0][0];
//初始化动态规划的临界条件
for(int i=1;i<n;i++){
dp[0][i]=dp[0][i-1]+grid[0][i];
}
for(int j=1;j<m;j++){
dp[j][0]=dp[j-1][0]+grid[j][0];
}
//确定动态规划的转移方程
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=max(dp[i][j-1],dp[i-1][j])+grid[i][j];
}
}
//返回答案
return dp[m-1][n-1];
}
34 路径
class Solution {
public:
vector<vector<int>> ret;
vector<int> path;
void dfs(TreeNode* root, int target) {
if (root == nullptr) {
return;
}
path.emplace_back(root->val);
target -= root->val;
if (root->left == nullptr && root->right == nullptr && target == 0) {
ret.emplace_back(path);
}
dfs(root->left, target);
dfs(root->right, target);
path.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int target) {
dfs(root, target);
return ret;
}
};
48最长不重复
64
int sumNums(int n) {
n && (n += sumNums(n-1));
return n;
}
66
vector<int> constructArr(vector<int>& a) {
int len = a.size();
if(len == 0) return {};
vector<int> b(len,1);
b[0] = 1;
int tmp = 1;
for(int i = 1; i < len; i++){
b[i] = b[i-1] * a[i-1];
}
for(int i = len - 2; i>=0; i--){
tmp *= a[i+1];
b[i] *= tmp;
}
return b;
}
int strToInt(string str) {
int i=0, sign=1;
long res=0;//直接用long暂时解决越界的问题
while(str[i]==' ') i++;
if(str[i] == '-') sign = -1;
if(str[i] == '-'||str[i] == '+') i++;
for(;i<str.size();i++){
if(str[i]>'9'||str[i]<'0') break;
res = res*10 + (str[i]-'0');//转换
if(res>=INT_MAX&&sign==1) return INT_MAX;
if(res>INT_MAX&&sign==-1) return INT_MIN;
}
return sign*res;
}
12
没太看懂
bool exist(vector<vector<char>>& board, string word) {
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
if(dfs(board,word,i,j,0))
return true;
}
}
return false;
}
bool dfs(vector<vector<char>>& b, string& w, int i, int j, int k){
if(i>=b.size()||i<0||j>=b[0].size()||j<0||b[i][j]!=w[k])
return false;
if(k == w.length()-1)
return true;
char temp = b[i][j];
b[i][j] = '/'; //该元素已访问过
bool res = dfs(b,w,i+1,j,k+1) ||
dfs(b,w,i-1,j,k+1) ||
dfs(b,w,i,j+1,k+1) ||
dfs(b,w,i,j-1,k+1);
b[i][j] = temp;
return res;
}
13 没看懂
int count = 0;
int movingCount(int m, int n, int k) {
if(k==0) return 1;
vector<vector<int>> visited(m, vector<int>(n));
dfs(visited,0,0,m,n,k);
return count;
}
void dfs(vector<vector<int>>& visited, int x, int y, int& m, int &n, int k){
if (x>=m||x<0||y>=n||y<0||(x/10+x%10+y/10+y%10)>k||visited[x][y]==1)
return;
++count;
visited[x][y] = 1;
int dx[4] = {-1,0,1,0}, dy[4]={0,1,0,-1};
for(int q = 0; q < 4; ++q){
int i = x + dx[q], j = y + dy[q];
dfs(visited,i,j,m,n,k);
}
}
二刷
4 二维数组的查找
从右上到左下找
7 重建二叉树
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size()==0||inorder.size()==0) return NULL;
vector<int> preorder_LEFT, preorder_RIGHT, inorder_LEFT, inorder_RIGHT;
TreeNode* ret = new TreeNode(preorder[0]);
//找inorder中根节点所在的位置存在gen中
int gen=0;
for(int i=0;i<inorder.size();i++){
if(inorder[i]==preorder[0]){
gen = i;
break;//找到根节点在中序遍历中的位置
}
}
//对中序遍历,根节点在左右子树中间
//左子树
for(int i=0;i<gen;i++){
inorder_LEFT.push_back(inorder[i]);
preorder_LEFT.push_back(preorder[i+1]);//先序遍历第一个是根节点,之后先是所有的左子树
}
//右子树
for(int i =gen+1;i<inorder.size();i++){//从gen+1开始
inorder_RIGHT.push_back(inorder[i]);
preorder_RIGHT.push_back(preorder[i]);
}
//递归建树
ret->left = buildTree(preorder_LEFT, inorder_LEFT);
ret->right = buildTree(preorder_RIGHT, inorder_RIGHT);
return ret;
}
12 矩阵中的路径
dfs+剪枝
如果已访问过,
如果b[i][j]!=w[k] return FALSE
bool exist(vector<vector<char>>& board, string word) {
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
if(dfs(board,word,i,j,0))
return true;
}
}
return false;
}
//k 是用来判断word中的index,从0开始
bool dfs(vector<vector<char>>& b, string& w, int i, int j, int k){
if(i>=b.size()||i<0||i>=b.size()||j<0||j>=b[0].size()||b[i][j]!=w[k])
return false;
if(k == w.length()-1)
return true;
char temp = b[i][j];
b[i][j] = '/'; //该元素已访问过
//k每次+1,从上下左右寻找
bool res = dfs(b,w,i+1,j,k+1) ||
dfs(b,w,i-1,j,k+1) ||
dfs(b,w,i,j+1,k+1) ||
dfs(b,w,i,j-1,k+1);
b[i][j] = temp;//当次遍历结束,要还原
return res;
}
13 机器人
dfs
和12类似
int count = 0;//共用变量,记录可能的格子
int movingCount(int m, int n, int k) {
if(k==0) return 1;
vector<vector<int>> visited(m, vector<int>(n));//m*n的矩阵
dfs(visited,0,0,m,n,k);//从0,0开始
return count;
}
//k是输入,所有数位要求之和
void dfs(vector<vector<int>>& visited, int x, int y, int& m, int &n, int k){
if (x>=m||x<0||y>=n||y<0||(x/10+x%10+y/10+y%10)>k||visited[x][y]==1)
return;
count++;
visited[x][y] = 1;
// int dx[4] = {-1,0,1,0}, dy[4]={0,1,0,-1};
// for(int q = 0; q < 4; ++q){
// int i = x + dx[q], j = y + dy[q];
// dfs(visited,i,j,m,n,k);
// }
dfs(visited,x+1,y,m,n,k);
dfs(visited,x-1,y,m,n,k);
dfs(visited,x,y+1,m,n,k);
dfs(visited,x,y-1,m,n,k);
}
14
cutting rope
dynamic programming
- dp[i] 长度为i的身子剪短后的最大乘积
- dp[i] = max(dp[i], max(j*(i-j)),jdp[i-j])
//当前长度,只剪成2段,j和(i-j),或者jdp[i-j] - dp[2] = 1
vector<int> dp(n+1,0);
dp[1] = 1;
dp[2] = 1;
for(int i=3;i<=n;i++){
for(int j=2;j<=i;j++){
dp[i] = max(dp[i],max(j*(i-j), j*dp[i-j]));
}
}
return dp[n];
/*
int cuttingRope(int n) {
//dp = max(result[i], result[j]*result[i-j])
if(n <= 1) return 0;
if(n == 2) return 1;
if(n == 3) return 2;
vector<int> result(n+1, 0);
result[0] = 0;
result[1] = 1;
result[2] = 2;
result[3] = 3;
for(int i=4;i<=n;i++){
for(int j=1;j<=i/2;j++){
result[i]=max(result[j]* result[i-j], result[i]);
}
}
return result[n];
}
*/
大数求余问题
贪心算法
尽可能分为长度为3的小段时乘积最大
int cuttingRope(int n) {
if(n==2) return 1;
if(n==3) return 2;
int b = n%3, p=1000000007;//b是余数,最后要乘的
long ret = 1;
int lineNums = n/3;//可以截成3的数量
for(int i=1;i<lineNums;i++)
ret = 3*ret % p;//一直乘3,由于从i=1开始,少乘了一次
if(b == 0)
return (int)(ret * 3 % p);//被3整除算前一段
if(b == 1)
return (int)(ret * 4 % p);//被3整数余1,算前一段可以是2*2=4
return (int)(ret * 6 % p);//被3整除余2,算前一段最大可以是2*3=6
}
26 二叉树的子结构
bool isSubStructure(TreeNode* A, TreeNode* B) {
//dfs先序遍历
if(A==nullptr||B==nullptr) return false;
bool ret = false;
if(A->val == B->val) ret = hasSubtree(A,B);
if(!ret){
ret = isSubStructure(A->left,B) || isSubStructure(A->right,B);
}
return ret;
}
// //bfs层序遍历
// bool ret = false;
// if(A==nullptr||B==nullptr){
// return ret;
// }
// queue<TreeNode*> q;
// q.push(A);
// while(!q.empty()){
// int size = q.size();
// for(int i=0;i<size;i++){
// TreeNode* temp = q.front();
// q.pop();
// if(temp->val == B->val){
// ret = hasSubtree(temp, B);//头是一样的时用这个比较
// if(ret) return ret;
// }
// if (temp->left != nullptr)
// q.push(temp->left);
// if (temp->right != nullptr)
// q.push(temp->right);
// }
// }
// return ret;
// }
bool hasSubtree(TreeNode* A, TreeNode* B){
if(B == nullptr) return true;
if(A == nullptr || A->val != B->val) return false;
return hasSubtree(A->left,B->left) && hasSubtree(A->right,B->right);
}
32
class Solution {
public:
vector<int> levelOrder(TreeNode* root) {
vector<int> res;
if(!root) return res;
queue<TreeNode*> q;
q.push(root);
while(q.size()){
TreeNode* node=q.front();
q.pop();
res.push_back(node->val);
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
return res;
}
};
//从上到下,广度优先搜素
//BFS
//O(N),O(N),平衡二叉树,N/2个节点在queue中
//用queue实现层级遍历
vector<vector<int>> levelOrder(TreeNode* root) {
if(!root) return {};
vector<vector<int>> ret;
vector<int> temp;
queue<TreeNode*> Q;
Q.push(root);
while(Q.size()){
int size=Q.size();
for(int i=0;i<size;++i){
TreeNode* node=Q.front();
temp.push_back(node->val);
Q.pop();
if(node->left) Q.push(node->left);
if(node->right) Q.push(node->right);
}
ret.push_back(temp);
temp.clear();//要清空
}
return ret;
}
vector<vector<int>> levelOrder(TreeNode* root) {
if(!root) return {};
vector<vector<int>> ret;
queue<TreeNode*> Q;
Q.push(root);
while(Q.size()){
vector<int> temp;
int size=Q.size();
for(int i=0;i<size;i++){
TreeNode* node=Q.front();
Q.pop();
temp.push_back(node->val);
if(node->left) Q.push(node->left);
if(node->right) Q.push(node->right);
}
if(ret.size()%2==1) reverse(temp.begin(),temp.end());
//奇数行要反转
ret.push_back(temp);
}
return ret;
}
33
二叉树的后序遍历
bool verifyPostorder(vector<int>& postorder) {
return recur(postorder,0,postorder.size()-1);
}
bool recur(vector<int>& postorder, int start, int end){
if(start>=end) return true;
int p = start;
//二叉搜索树: 右 > 根 > 左
//后序遍历:左右根
while(p<end && postorder[p]<postorder[end]) p++;//找到右子树的第一个
if(p == end) return recur(postorder,start,end-1);
//没有右子树,只需要验证左子树
int right1 = p;
while(p<end && postorder[p]>postorder[end]) p++;//检查右子树是否全部比根大
if(p<end) return false;
return recur(postorder,start,right1-1) &&
recur(postorder,right1,end-1);
}
34 二叉树和为target的路径
class Solution {
public:
vector<vector<int>> ret;
vector<int> path;
void dfs(TreeNode* root, int target) {
if (root == nullptr) {
return;
}
path.push_back(root->val);
target -= root->val;
if (root->left == nullptr && root->right == nullptr && target == 0) {
ret.push_back(path);
}
dfs(root->left, target);
dfs(root->right, target);
path.pop_back();//remove the last element of a path to see other possibilities
}
vector<vector<int>> pathSum(TreeNode* root, int target) {
dfs(root, target);
return ret;
}
};
//DFS
//O(N^2) 上半部分链状,下半部分完全二叉树
//O(N)
35 复杂链表的复制
已看
O(N)
O(N)
Node* copyRandomList(Node* head) {
if(head==nullptr) return nullptr;
Node* cur=head;
unordered_map<Node*, Node*> map;
//创建新节点
while(cur!=nullptr){
map[cur] =new Node(cur->val);
cur = cur->next;
}
cur = head;
//构建新链表的指向
while(cur!=nullptr){
map[cur]->next = map[cur->next];//重要
map[cur]->random = map[cur->random];//重要
cur = cur->next;
}
//返回
return map[head];
}
36 二叉搜索树和双向链表
dfs
再看
中序遍历是增序
Node* pre=nullptr, *head=nullptr;
Node* treeToDoublyList(Node* root) {
if(root==nullptr) return root;
dfs(root);
head->left=pre;
pre->right=head;
return head;
}
void dfs(Node* cur){
if(cur==nullptr) return;
dfs(cur->left);
if(pre!=nullptr) pre->right=cur;
else head=cur;//保留链表头节点
cur->left=pre;
pre=cur;
dfs(cur->right);
}
- 再看, dfs
public:
vector<string> permutation(string s) {
string cur = "";
vector<bool> used(s.size(),false);
dfs(s,cur,used);
for(auto& element: st) ret.push_back(element);
return ret;
}
set<string> st;
vector<string> ret;
void dfs(string& s, string& cur, vector<bool>& used){
if(cur.size() == s.size()){
st.insert(cur);
return;
}
for(int i=0;i<s.size();i++){
if(used[i]) continue;
cur = cur + s[i];
used[i] = true;
dfs(s,cur,used);
used[i]=false;
cur.pop_back();
}
}
38 string
再看下,回溯算法
vector<string> ret;
vector<bool> visited;
void backtrack(const string& s, int i, int n, string& perm) {
//i是当前的char数
//n是perm中需要的char数,这里为3
if (i == n) {
ret.push_back(perm);
return;
}
for (int j = 0; j < n; j++) {
if (visited[j] || (j > 0 && !visited[j - 1] && s[j - 1] == s[j])) {
continue;//以上三种情况不行
//上一位已用过,两位一样
}
visited[j] = true;
perm.push_back(s[j]);
backtrack(s, i + 1, n, perm);//下一位
perm.pop_back();//回溯算法的核心
visited[j] = false;//这位处理好了
}
}
vector<string> permutation(string s) {
int n = s.size();
visited.resize(n);
sort(s.begin(), s.end());
string perm;
backtrack(s, 0, n, perm);
return ret;
}
44
在复习下
int findNthDigit(int n) {
int digit=1;//记录位数
long start=1;//记录某一位数开始的第一个数
long count=digit*start*9;//记录某一位数包含的所有数字个数
//判断第n位数字是几位数
while(n>count){
n=n-count;//减去上一位所有的数字
digit=digit+1;
start=start*10;
count=digit*start*9;
}
//判断第n位数字是几位数
long number = start + (n-1)/digit;
//判断第n位数字在第二步中找出的数是第几位
string s_number = to_string(number);
return s_number[(n-1)%digit] - '0';//这里返回的是int型
}
45
排序,可以再复习下
string minNumber(vector<int>& nums) {
vector<string> strs;
for(int i=0;i<nums.size();i++){
strs.push_back(to_string(nums[i]));
}
quickSort(strs,0,strs.size()-1);//右边是size-1
string res;
for(string s: strs){
res.append(s);
}
return res;
}
void quickSort(vector<string>& strs, int l, int r){
if(l>=r) return;
int i=l,j=r;
while(i<j){
//这里的+是字符串的拼接
//x+y>y+x => x>y
//x+y<y+x => x<y
while(strs[j]+strs[l]>=strs[l]+strs[j]&& i<j) j--;
while(strs[i]+strs[l]<=strs[l]+strs[i]&& i<j) i++;
//x<y在这里的意义是应该在左边
//上面两个while会一直将右边‘小’的和左边‘大’的交换
swap(strs[i],strs[j]);
}
swap(strs[i],strs[l]);
quickSort(strs,l,i-1);//左边
quickSort(strs,i+1,r);//右边
}
};
//快排平均O(nlogn),最差O(N2)
//O(N)
46
dp
滚动数组
在学习下
int translateNum(int num) {
//动态规划转移方程(不同规模问题的联系)
//两种可能性
//1. 如果i和i-1个数字不能一起翻译,即不在10到25之间
//dp[i]=dp[i-1]
//2. 如果可以一起翻译
//dp[i]=dp[i-1]+dp[i-2]
//!!!两种可能性是或的关系
//总的方法数取dp[i]=dp[i-1]+dp[i-2]
//边界条件f(-1)=0, f(0)=1
//O(n) O(1):滚动数组(常见的空间优化)
string src = to_string(num);
int a=0, b=0, c=1;
for(int i=0;i<src.size();i++){
a=b;
b=c;
c=0;
c = b+c;//状态转移方程
if(i==0) continue;
auto pre=src.substr(i-1,2);
if(pre<="25"&&pre>="10"){
c = c+a;
}
}
return c;
}
std::string str="We think in generalities, but we live in details.";
// (quoting Alfred N. Whitehead)
std::string str2 = str.substr (3,5); // "think",从idx3开始往后找5个
47最大价值的礼物
dp
比较好懂的例子
int maxValue(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
//定义动态规划的存储
vector<vector<int>>dp(m,vector<int>(n,0));
dp[0][0]=grid[0][0];
//初始化动态规划的临界条件
//第一行和第一列只能从一个方向到达
for(int i=1;i<n;i++){
dp[0][i]=dp[0][i-1]+grid[0][i];
}
for(int j=1;j<m;j++){
dp[j][0]=dp[j-1][0]+grid[j][0];
}
//确定动态规划的转移方程
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
//由题意可知,只能从左边或者上边到新格子
dp[i][j]=max(dp[i][j-1],dp[i-1][j])+grid[i][j];
}
}
//返回答案
return dp[m-1][n-1];
}
48 字符串,没看懂
背一下
int lengthOfLongestSubstring(string s) {
int maxlen=0,left=0,pos=0;
vector<bool> used(256,false);
while(pos<s.size()){
while(used[s[pos]]) used[s[left++]]=false;//重新开始算
maxlen = max(maxlen, pos-left+1);
used[s[pos++]]=true;//这个用过了
}
return maxlen;
}
49丑数
dp
记一下
int nthUglyNumber(int n) {
vector<int> dp(n+1);
dp[1]=1;
int p2=1,p3=1,p5=1;
for(int i=2;i<=n;i++){
int n2=dp[p2]*2, n3=dp[p3]*3, n5=dp[p5]*5;
dp[i]=min(n2,min(n3,n5));
if(dp[i]==n2) p2++;
if(dp[i]==n3) p3++;
if(dp[i]==n5) p5++;
}
return dp[n];
}
快速排序等基础
53
int missingNumber(vector<int>& nums) {
//binary search O(logn)
//bs requires a sorted seq
int left=0,right=nums.size()-1;
while(left<=right){
int mid = left + (right-left)/2;
if(nums[mid]==mid) left = mid+1;
else right = mid-1;
}
return left;
}
94
中序遍历
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
while(root!=nullptr || !stk.empty()){
while(root!=nullptr){
stk.push(root);
root = root->left;
}
root = stk.top();
stk.pop();
res.push_back(root->val);
root = root->right;
}
return res;
}
//O(n)
//O(n), binary tree is a linked list
54
二叉搜索树的第k大节点
基于中序遍历
int kthLargest(TreeNode* root, int k) {
vector<int> res;
stack<TreeNode*> stk;
while(root!=nullptr || !stk.empty()){
while(root!=nullptr){
stk.push(root);
root = root->left;
}
root = stk.top();
stk.pop();
res.push_back(root->val);
root = root->right;
}
//res is the inorder traversal, 左中右
reverse(res.begin(),res.end());
return res[k-1];
}
57 - II. 和为s的连续正数序列
vector<vector<int>> findContinuousSequence(int target) {
//sliding window
int left=1, right=1, sum=0;
vector<vector<int>> res;
while(left<=target/2){
if(sum<target){
//right slide right
sum += right;
right++;
}
else if(sum>target){
//left slide right
sum -= left;
left++;
}
else{
vector<int> ret;
for(int k=left;k<right;k++){
ret.push_back(k);
}
res.push_back(ret);
sum -= left;
left++;
}
}
return res;
}
Offer 58 - I. 翻转单词顺序
string reverseWords(string s) {
s+=' ';
string temp="";
vector<string> res;
for(char ch:s)
{
if(ch == ' ')
{
if(!temp.empty())//temp is storing words
{
res.push_back(temp);//res stores separate words
temp.clear();
}
}
else temp += ch;//contruct words
}
reverse(res.begin(),res.end());
s.clear();
for(string &str: res){
s += str + ' ';
}
s.pop_back();
return s;
//
//double ptr
}
27
树
TreeNode* mirrorTree(TreeNode* root) {
if(root==nullptr) return nullptr;
TreeNode* left = root->left;
root->left = mirrorTree(root->right);
root->right = mirrorTree(left);
return root;
}
//O(N)
//O(N) 最差情况二叉树退化成链表
28
树
bool isSymmetric(TreeNode* root) {
if(root == nullptr) return true;
return dfs(root->left,root->right);
}
bool dfs(TreeNode* p, TreeNode* q){
if(p==nullptr || q==nullptr) return !p&&!q;//如果两个都是null,return true
if(p->val != q->val) return false;
return dfs(p->left, q->right) && dfs(p->right,q->left);
}
29
矩阵打印
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if(matrix.empty()) return {};
vector<int> res;
int l=0;
int r = matrix[0].size()-1;
int t=0;
int b=matrix.size()-1;
while(1){
//left->right
for(int i=l;i<=r;i++) res.push_back(matrix[t][i]);
if(++t > b) break;
//top->bottom
for(int i=t;i<=b;i++) res.push_back(matrix[i][r]);
if(--r < l) break;
for(int i=r;i>=l;i--) res.push_back(matrix[b][i]);
if(--b < t) break;
for(int i=b;i>=t;i--) res.push_back(matrix[i][l]);
if(++l > r) break;
}
return res;
}
39 哈希
int majorityElement(vector<int>& nums) {
//众数
//OP1.哈希表
//O(n)
unordered_map<int,int> counts;
int maj=0,cnt=0;
for(int num:nums){
++counts[num];
if(counts[num]>cnt){
maj=num;
cnt=counts[num];
}
}
return maj;
//OP2.数组排序
sort(nums.beigin(),nums.end());
return nums[nums.size()/2];
}