目录
DAY1.二分查找
704.二分查找
class Solution {
public:
int search(vector<int>& nums, int target) {
int l=0,r=nums.size()-1;
while(l<r)
{
int mid=l+r>>1;
if(nums[mid]>=target) r=mid;
else l=mid+1;
}
if(nums[l]!=target) return -1;
else return l;
}
};
278.第一个错误版本
直接mid = l+r >> 1会溢出,要改变一下,mid=l+(r-l) / 2
class Solution {
public:
int firstBadVersion(int n) {
int l=1,r=n;
while(l<r)
{
int mid=l+(r-l)/2;
if(isBadVersion(mid)==true) r=mid;
else l=mid+1;
}
return l;
}
};
!35.搜索插入位置
需要加个特判,如果这个数比最终查找位置大,返回位置应该+1
如果比最终位置小,直接返回该数下标即可
例如:1 3 5 6 插入0 输出:0
1 3 5 6 插入7 输出:4
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l=0,r=nums.size()-1;
while(l<r)
{
int mid=l+r+1>>1;
if(nums[mid]<=target) l=mid;
else r=mid-1;
}
if(nums[l]<target) return l+1;
return l;
}
};
DAY2.双指针
977.有序数组的平方
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n=nums.size();
int l=0,r=n-1;
int cnt=n-1;
vector<int>res(n);
while(l<=r)
{
if(nums[l]*nums[l]>nums[r]*nums[r])
res[cnt--]=nums[l]*nums[l],l++;
else res[cnt--]=nums[r]*nums[r],r--;
}
return res;
}
};
189.轮转数组
直接定位法
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n=nums.size();
vector<int>res(n);
for(int i=0;i<n;i++)
res[(i+k)%n]=nums[i];
nums=res;
}
};
翻转数组法:
例如:1 2 3 4 5
第一步先翻转整个数组 5 4 3 2 1
第二步按k划分 5 4 3 2 1
第三步各自翻转 3 4 5 1 2
class Solution {
public:
void reverse(vector<int>& nums,int l,int r)
{
while(l<r)
{
swap(nums[l],nums[r]);
l++;
r--;
}
}
void rotate(vector<int>& nums, int k) {
int n=nums.size();
k%=n;
reverse(nums,0,n-1);
reverse(nums,0,k-1);
reverse(nums,k,n-1);
}
};
DAY3.双指针
!283.移动零
左指针指向为0的数
右指针指向不为0的数
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n=nums.size();
int l=0,r=0;
while(r<n)
{
if(nums[r]) swap(nums[l],nums[r]),l++;
r++;
}
}
};
!167.两数之和 2 - 输入有序数组
双指针法
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int n=numbers.size();
int l=0,r=n-1;
while(l<r)
{
if(numbers[l]+numbers[r]>target) r--;
else if(numbers[l]+numbers[r]<target) l++;
else return{l+1,r+1};
}
return {-1,-1};
}
};
二分法
可以先固定一个数i,再利用二分,在比i大的区间里找另一个数满足n[i]+n[l]==target
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int n=numbers.size();
for(int i=0;i<n;i++)
{
int l=i+1,r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(numbers[i]+numbers[mid]>=target) r=mid;
else l=mid+1;
}
if(numbers[i]+numbers[r]==target) return{i+1,r+1};
}
return {-1,-1};
}
};
DAY4.双指针
557.反转字符串中的单词3
双指针法
class Solution {
public:
string reverseWords(string s) {
int n=s.size();
int l=0,r;
for(int i=0;i<=n;i++)
if(s[i]==' '||s[i]=='\0')
{
for(r=i-1;r>l;r--,l++) swap(s[l],s[r]);
l=i+1;
}
return s;
}
};
class Solution {
public:
string reverseWords(string s) {
stringstream ss(s);
string str;
string res;
bool f=false;
while(ss>>str)
{
reverse(str.begin(),str.end());
if(f)res+=" ";
f=true;
res+=str;
}
return res;
}
};
DAY5.双指针
876.链表的中间结点
暴力遍历
先求链表长度 再找len/2的位置
class Solution {
public:
ListNode* middleNode(ListNode* head) {
int len=0;
ListNode *t=head;
while(t!=NULL)
{
len++;
t=t->next;
}
int i=0;
while(i<len/2)
{
head=head->next;
i++;
}
return head;
}
};
快慢指针
快指针一次走两步 慢指针一次走一步
当快指针走到末尾时 慢指针恰好走到中间位置
- 奇数情况:如1 2 3 4 5 当慢指针在中点3时,快指针在5位置,即high->next=NULL
- 偶数情况:如1 2 3 4 5 6 当慢指针在4时,快指针在6的后一位置,即high=NULL
如果high指针满足以上两个条件的任意一个,则不需要移动指针,而返回慢指针
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *slow=head,*high=head;
while(high!=NULL&&high->next!=NULL)
{
high=high->next->next;
slow=slow->next;
}
return slow;
}
};
19.删除链表的倒数第n个结点
暴力遍历
需要注意特判 链表长度为1 和 删除的是头结点的情况(即len==n)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
int len=0;
ListNode *t=head;
while(t!=NULL)
{
t=t->next;
len++;
}
if(len==1||len==n) return head->next; //如果链表只有一个元素则返回NULL 如果要删除头结点 直接返回头结点之后的链表
ListNode *p=head,*q=NULL;
for(int i=1;i<len-n;i++) p=p->next;
q=p->next;
p->next=q->next;
return head;
}
};
快慢指针
快指针先走n个结点 然后快慢指针同时走
当快指针走到末尾时 慢指针恰好指向倒数第n个节点的前驱结点
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head->next==NULL) return head->next;
ListNode *high=head,*slow=head;
for(int i=0;i<n;i++) high=high->next;
if(!high) return head->next; //如果快指针指出范围 说明要删除的是头结点
while(high->next)
{
high=high->next;
slow=slow->next;
}
slow->next=slow->next->next;
return head;
}
};
DAY6.滑动窗口
3.无重复字符的最长子串
unordered_set
该容器中不包含重复元素
mp.find() 查找对应元素,成功返回对应的迭代器,失败返回最后一个元素迭代器(即 mp.end() )
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if(s.size()==0) return 0;
int maxl=-1,l;
unordered_set<char>mp;
for(int i=0;i<s.size();i++)
{
while(mp.find(s[i])!=mp.end())//如果在窗口中找到重复元素 则擦除左端 移动窗口
{
mp.erase(s[l]);
l++;
}
maxl=max(maxl,i-l+1);
mp.insert(s[i]);
}
return maxl;
}
};
567.字符串的排列
class Solution {
public:
bool checkInclusion(string s1, string s2) {
if(s1.size()>s2.size()) return false;
vector<int>v1(26,0);
vector<int>v2(26,0);
for(int i=0;i<s1.size();i++) //因为不要求顺序 所以只要统计字母的出现次数就可以
{
v1[s1[i]-'a']++;
v2[s2[i]-'a']++;
}
if(v1==v2) return true;
for(int i=s1.size();i<s2.size();i++)
{
v2[s2[i]-'a']++;
v2[s2[i-s1.size()]-'a']--; //保持窗口长度为s1的长度
if(v1==v2) return true;
}
return false;
}
};
DAY7. DFS/BFS
733.图像渲染
方法一:dfs
我们从给定的起点开始,进行深度优先搜索。
每次搜索到一个方格时,如果其与初始位置的方格颜色相同,就将该方格的颜色更新,以防止重复搜索;如果不相同,则进行回溯。
class Solution {
public:
int dx[4]={0,0,-1,1};
int dy[4]={1,-1,0,0};
void dfs(vector<vector<int>>&g,int x,int y,int cur,int color)
{
int m=g.size(),n=g[0].size();
if(g[x][y]==cur)
{
g[x][y]=color;
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a<0||a>m-1||b<0||b>n-1) continue;
dfs(g,a,b,cur,color);
}
}
}
vector<vector<int>> floodFill(vector<vector<int>>& g, int sr, int sc, int color) {
int cur=g[sr][sc];
if(cur!=color)
dfs(g,sr,sc,cur,color);
return g;
}
};
方法二:bfs
我们从给定的起点开始,进行广度优先搜索。
每次搜索到一个方格时,如果其与初始位置的方格颜色相同,就将该方格加入队列,并将该方格的颜色更新,以防止重复入队。
注意:因为初始位置的颜色会被修改,所以我们需要保存初始位置的颜色,以便于之后的更新操作。
typedef pair<int,int> PII;
class Solution {
public:
int dx[4]={0,0,-1,1};
int dy[4]={1,-1,0,0};
void bfs(vector<vector<int>>&g,int x,int y,int cur,int color)
{
int m=g.size(),n=g[0].size();
g[x][y]=color;
queue<PII>q;
q.push({x,y});
while(!q.empty())
{
auto t=q.front();
int a=t.first,b=t.second;
q.pop();
for(int i=0;i<4;i++)
{
int aa=a+dx[i],bb=b+dy[i];
if(aa<0||aa>m-1||bb<0||bb>n-1) continue;
if(g[aa][bb]==cur) g[aa][bb]=color,q.push({aa,bb});
}
}
}
vector<vector<int>> floodFill(vector<vector<int>>& g, int sr, int sc, int color) {
int cur=g[sr][sc];
if(cur!=color)
bfs(g,sr,sc,cur,color);
return g;
}
};
695.岛屿的最大面积
方法一:dfs
class Solution {
public:
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int dfs(vector<vector<int>>&g,int x,int y)
{
int m=g.size(),n=g[0].size();
if(x<0||x>m-1||y<0||y>n-1) return 0;
if(g[x][y]==0) return 0;
g[x][y]=0;
int res=1;
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
res+=dfs(g,a,b);
}
return res;
}
int maxAreaOfIsland(vector<vector<int>>& g) {
int m=g.size(),n=g[0].size();
int res=0;
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
res=max(res,dfs(g,i,j));
return res;
}
};
方法二:bfs
typedef pair<int,int> PII;
class Solution {
public:
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int bfs(vector<vector<int>>&g,int x,int y)
{
int m=g.size(),n=g[0].size();
queue<PII>q;
q.push({x,y});
g[x][y]=0;
int res=1;
while(!q.empty())
{
auto t=q.front();
q.pop();
int a=t.first,b=t.second;
for(int i=0;i<4;i++)
{
int aa=a+dx[i],bb=b+dy[i];
if(aa<0||aa>m-1||bb<0||bb>n-1) continue;
if(g[aa][bb]==1) q.push({aa,bb}),res++,g[aa][bb]=0;
}
}
return res;
}
int maxAreaOfIsland(vector<vector<int>>& g) {
int m=g.size(),n=g[0].size();
int res=0;
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(g[i][j]==1)
res=max(res,bfs(g,i,j));
return res;
}
};
DAY8.DFS/BFS
617.合并二叉树
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if(t1==null) return t2;
if(t2==null) return t1;
//如果都存在结点 则创建一个新结点储存两个结点值之和
TreeNode newroot=new TreeNode(t1.val+t2.val);
//递归合并左子树
newroot.left=mergeTrees(t1.left,t2.left);
//递归合并右子树
newroot.right=mergeTrees(t1.right,t2.right);
return newroot;
}
}
!116.填充每个节点的下一个右侧节点指针
- 如果结点为NULL或者左右子树均为NULL:没有需要做的,直接返回结点本身。
- 对于左右子树均存在的结点:
- 首先左子树的next一定不会指向NULL,直接设置指向右子树即可;
- 对于右子树,他的next是要指向父结点右兄弟的左子树的,可以通过root->next->left找到这个结点,但只有一种情况下例外,就是父结点没有右兄弟(root->next == NULL)
class Solution {
public Node connect(Node root) {
if(root==null||root.left==null) return root;
root.left.next=root.right; //左子树的next肯定不为空 直接指向右子树
if(root.next!=null) //如果父结点有右兄弟
root.right.next=root.next.left;//指向父节点右兄弟的左子树
connect(root.left);
connect(root.right);
return root;
}
}
DAY9. DFS/BFS
542.01矩阵
bfs:
首先把每个源点 0入队,然后从各个 0 同时开始一圈一圈的向 1 扩散(每个 1 都是被离它最近的 0 扩散到的 )
class Solution {
public int[][] updateMatrix(int[][] g) {
int m=g.length,n=g[0].length;
Queue<int[]> q=new LinkedList<>();
//将所有0入队,并将1的位置设成-1 标记为未访问
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(g[i][j]==0) q.offer(new int[] {i,j});
else g[i][j]=-1;
int[] dx=new int[]{0,0,-1,1},dy=new int[]{1,-1,0,0};
while(!q.isEmpty())
{
int[] t=q.poll();
int x=t[0],y=t[1];
for(int i=0;i<4;i++)
{
int a=dx[i]+x,b=dy[i]+y;
if(a>=0&&a<m&&b>=0&&b<n&&g[a][b]==-1)
{
g[a][b]=g[x][y]+1;
q.offer(new int[] {a,b});
}
}
}
return g;
}
}
994.腐烂的橘子
bfs:
让腐烂的橘子入队 遍历第一次腐烂的橘子 逐一感染
class Solution {
public int orangesRotting(int[][] g) {
int m=g.length,n=g[0].length;
int res=0;
Queue<int[]> q=new LinkedList<>();
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(g[i][j]==2) q.offer(new int[] {i,j});
int[] dx=new int[]{0,0,1,-1},dy=new int[]{1,-1,0,0};
while(!q.isEmpty())
{
int num=q.size();//统计本轮腐烂橘子的个数
for(int i=0;i<num;i++)
{
int[] t=q.poll();
int x=t[0],y=t[1];
for(int j=0;j<4;j++)
{
int a=dx[j]+x,b=dy[j]+y;
if(a<0||a>=m||b<0||b>=n) continue;
if(g[a][b]==1)
{
g[a][b]=2;
q.offer(new int[] {a,b});
}
}
}
if(!q.isEmpty()) res++;//如果一开始就感染完了 res=0
}
//检查还有没有新鲜橘子
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(g[i][j]==1) return -1;
return res;
}
}
DAY10.递归/回溯
21.合并两个有序链表
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head=new ListNode(-1);
ListNode p=head;
while(l1!=null&&l2!=null)
{
if(l1.val>l2.val)
{
p.next=l2;
l2=l2.next;
}else{
p.next=l1;
l1=l1.next;
}
p=p.next;
}
p.next= l1==null?l2:l1;
return head.next;
}
}
206.反转链表
class Solution {
public ListNode reverseList(ListNode head) {
ListNode p=null;
ListNode cur=head;
while(cur!=null)
{
ListNode t=cur.next;
cur.next=p;
p=cur;
cur=t;
}
return p;
}
}
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode cur=reverseList(head.next);
head.next.next=head;
head.next=null;
return cur;
}
}
DAY11.递归/回溯
77.组合
思路:
关于剪枝:
- 要组成大小为k的v,此时还需要k - v.size()个数,如果[i, n]这个区间内(有n - i + 1个数)不足这么多,则肯定没有结果,直接剪枝。
- 因此剪枝条件为:n - i + 1 < k - v.size(),推出来上界为i = n - k + v.size() + 1
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer> v=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
dfs(1,n,k);
return res;
}
public void dfs(int start,int n,int k)
{
if(v.size()==k) //如果一组已经存满了 则把结果存入res
{
res.add(new ArrayList<>(v));//深拷贝 对象中开辟一个新地址,存放的内容为list链表,所以后续不会被影响
return;
}
for(int i=start;i<=n-k+v.size()+1;i++)
{
v.add(i);
dfs(i+1,n,k);
v.remove(v.size()-1);//回溯操作(回退处理结点[1, 2] 撤销2 ,[1, 3] 撤销3),然后继续遍历
}
}
}
46.全排列
思路:
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> v=new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
int[] visited = new int[nums.length];
backtrack(nums,visited);
return res;
}
private void backtrack(int[] nums, int[] visited) {
if(v.size()==nums.length) {
res.add(new ArrayList<>(v));
return;
}
for(int i=0;i<nums.length;i++)
{
if(visited[i]==1) continue;
v.add(nums[i]);
visited[i]=1;
backtrack(nums,visited);
visited[i]=0;
v.remove(v.size()-1);
}
}
}
784.字母大小写全排列
class Solution {
List<String> res=new ArrayList<>();
public List<String> letterCasePermutation(String s) {
char[] ch=s.toCharArray();
int n=s.length();
dfs(ch,n,0);
return res;
}
private void dfs(char[] ch,int n,int idx)
{
res.add(new String(ch));
for(int i=idx;i<n;i++)
if(!Character.isDigit(ch[i]))
{
char t=ch[i];
ch[i]=(char)((ch[i]-'a'>=0)? ch[i]-32:ch[i]+32);
dfs(ch,n,i+1);
ch[i]=t; //恢复现场
}
}
}
DAY12.动态规划
70.爬楼梯
本问题其实常规解法可以分成多个子问题,爬第n阶楼梯的方法数量,等于 2 部分之和
- 爬上 n-1 阶楼梯的方法数量。因为再爬1阶就能到第n阶
- 爬上 n-2 阶楼梯的方法数量。因为再爬2阶就能到第n阶
所以我们得到公式 dp[n] = dp[n-1] + dp[n-2]
同时需要初始化 dp[0]=1和dp[1]=1第0阶台阶1种方法 第1阶台阶1种方法
class Solution {
public int climbStairs(int n) {
int[] dp=new int[n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++) dp[i]=dp[i-1]+dp[i-2];
return dp[n];
}
}
斐波那契数列
其实就是斐波那契数列
1 2 3 5 8……
class Solution {
public int climbStairs(int n) {
if(n<=2) return n;
int a=1,b=2,t;
for(int i=3;i<=n;i++)
{
t=a+b;
a=b;
b=t;
}
return b;
}
}
198.打家劫舍
dp[i] 第i间房可盗窃的最大值
因为不可以闯入相邻的房间 所以在第n间房可以盗窃的最大值=
- 不盗取第n间房,所以盗取第n-1间房 dp[n]=dp[n-1]
- 盗取第n间房,所以不能盗窃第n-1间房 dp[n]=dp[n-2]+当前房的值num
所以动态规划方程:dp[n] = max( dp[n-1],dp[n-2]+num )
eg:
1号房可盗窃最大值为3 dp[1]=3
2号房可盗窃最大值为4 dp[2]=4
3号房值为2 num=2 dp[3]=max(dp[2],dp[1]+num)=max(4,3+2)=5
class Solution {
public int rob(int[] nums) {
int n=nums.length;
int[] dp=new int[n+1]; //开辟的是0~n的空间
dp[0]=0;
dp[1]=nums[0];
for(int i=2;i<=n;i++)
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i-1]);
return dp[n];
}
}
120.三角形最小路径和
dp[i][j] 表示从点 (i, j) 到底边的最小路径和
自下而上算
dp[i][j]=min(下一行的左右数值)+自己的值
最后返回顶上的值就是最小路径和
class Solution {
public int minimumTotal(List<List<Integer>> t) {
int n=t.size();
int[][] dp=new int[n+1][n+1];
for(int i=n-1;i>=0;i--) // 最后一行跳过 从倒数第二行开始算
for(int j=0;j<=i;j++)
dp[i][j]=Math.min(dp[i+1][j],dp[i+1][j+1])+t.get(i).get(j);
return dp[0][0];
}
}
DAY13.位运算
231.2的幂
class Solution {
public boolean isPowerOfTwo(int n) {
return n>0 && (n&(n-1))==0;
}
}
191.位1的个数
public class Solution {
public int hammingWeight(int n) {
int res=0;
while(n!=0)
{
n&=n-1;
res++;
}
return res;
}
}
Integer.bitCount(n) 返回1的个数
public class Solution {
public int hammingWeight(int n) {
return Integer.bitCount(n);
}
}
DAY14.位运算
190.颠倒二进制位
public class Solution {
public int reverseBits(int n) {
int res=0;
for(int i=0;i<32;i++)
{
int t=n&1; //获取最后的数字
n>>=1;
res<<=1; //结果数字左移,为新到来的数字腾出空间
res|=t; //或运算,将新到来的数字加到原来的数字上
}
return res;
}
}
Integer.reverse(n); 反转
public class Solution {
public int reverseBits(int n) {
return Integer.reverse(n);
}
}
136.只出现一次的数字
class Solution {
public int singleNumber(int[] a) {
int res=0;
for(int x:a) res^=x;
return res;
}
}