hot100
文章目录
- hot100
哈希
两数之和

思路 :
考虑到的点是一定有一个有效答案,并且是两数之和,返回的是num的序号,于是想到采取hashmap存放值和序号的映射
从前向后遍历,先存在当前的i,判断target-num[i]是否存在于数组即可
解法
class Solution {
public int[] twoSum(int[] nums, int target) {
int[]res=new int[2];
Map<Integer,Integer>map=new HashMap<>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
res[0]=map.get(target-nums[i]);
res[1]=i;
break;
}
else{
map.put(nums[i],i);
}
}
return res;
}
}
字母异位词分组

思路 :
此题的难点,在于如何判断两个单词是否是同样数量和同样类型的字母组成的,但是题目限制了只有小写字母,于是我们可以考虑采取字符串重新排序,再组成一个新的字符串,如果存在map中说明就已经有了
解法
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>>res=new HashMap<>();
String s;
for(int i=0;i<strs.length;i++){
char[] str1 = strs[i].toCharArray();
Arrays.sort(str1);
s=String.valueOf(str1);
if(res.containsKey(s)){
List<String>tes=res.get(s);
tes.add(strs[i]);
res.put(s,tes);
}
else{
List<String>tes=new ArrayList<>();
tes.add(strs[i]);
res.put(s,tes);
}
}
return new ArrayList<>(res.values());
}
}
最长连续序列

思路 :
首先采取set去重,遍历set,我们的思想是只遍历头部元素,其他元素不遍历
解法:
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer>set=new HashSet<>();
for(int i=0;i<nums.length;i++){
set.add(nums[i]);
}
int maxs=0;
for(int num:set){
if(!set.contains(num-1)){
int now=num;
int len=1;
while(set.contains(now+1)){
now++;
len++;
}
maxs=Math.max(len,maxs);
}
}
return maxs;
}
}
双指针
移动零

思路 :
一个left,一个right,每次right为0时就和left交换,left每次都会指向下一个0
解法:
public static void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
public static void moveZeroes(int[] nums) {
int n = nums.length, left = 0, right = 0;//
while (right < n) {
if (nums[right] != 0) {//left一直指向上一个交换后的0
swap(nums, left, right);
left++;
}
right++;
}
}
盛最多水的容器

解法 :
直接计算就好,每次移动较小的一边水槽就行。
public int maxArea(int[] height) {
int max=0;
int left=0,right=height.length-1,temp=0;
while(left<right){
temp=Math.min(height[left],height[right])*(right-left);
if(temp>max){
max=temp;
}
if(height[left]<height[right]){
left++;
}
else{
right--;
}
}
return max;
}
三数之和

思路:
解法
public static List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);//排序
List<List<Integer>> res = new ArrayList<>();
int x,y,z,target;
for(x=0;x<nums.length;x++){//每次确定一个最小的值
if(x>0&&nums[x]==nums[x-1]){//去重
continue;
}
target=-nums[x];//作为目标
z=nums.length-1;//将z作为最大的值
for(y=x+1;y<nums.length;y++){//确定y
if(y>x+1&&nums[y]==nums[y-1]){//去重
continue;
}
while (y<z&&nums[y]+nums[z]>target){//移动z,找到第一个不大于target,也就是0或者负数
z--;
}
if(y==z){//说明不存在
break;
}
if(nums[y]+nums[z]==target){
List<Integer> tmp = new ArrayList<>();
tmp.add(nums[x]);
tmp.add(nums[y]);
tmp.add(nums[z]);
res.add(tmp);
}
}
}
return res;
}
接雨水

解法
public static int trap(int[] height) {
// 步骤1:边界处理:空数组直接返回0(接不到任何水)
int n = height.length;
if (n == 0) {
return 0;
}
// 步骤2:预处理left数组 → left[i] 表示位置i「左侧(包括i)」的最大高度
int[] left = new int[n];
left[0] = height[0]; // 第一个位置左侧只有自己,最大高度就是自身
for (int i = 1; i < n; ++i) {
// 每个位置的left值 = 前一个位置的left值(左侧最大) 和 当前高度的较大值
left[i] = Math.max(left[i - 1], height[i]);
}
// 步骤3:预处理right数组 → right[i] 表示位置i「右侧(包括i)」的最大高度
int[] right = new int[n];
right[n - 1] = height[n - 1]; // 最后一个位置右侧只有自己,最大高度就是自身
for (int i = n - 2; i >= 0; --i) {
// 每个位置的right值 = 后一个位置的right值(右侧最大) 和 当前高度的较大值
right[i] = Math.max(right[i + 1], height[i]);
}
// 步骤4:累加每个位置的接水量,得到总水量
int ans = 0;
for (int i = 0; i < n; ++i) {
// 核心公式:左右最大高度的较小值 - 自身高度(结果≥0才有效)
ans += Math.min(left[i], right[i]) - height[i];
}
return ans;
}
滑动窗口
无重复字符的最长子串

思路 :
采取滑动窗口 的方法,用HashMap记录字符和字符的索引
如果字符不存在于HashMap 中,就加入HashMap中,
如果字符存在于HashMap 中,就更新beg为重复字符的索引+1,并删除HashMap中重复字符之前的所有字符。
解法
public int lengthOfLongestSubstring(String s) {
if(s.length() == 0){
return 0;
}
int max=1;
char[] charArray = s.toCharArray();
HashMap<Character, Integer> map = new HashMap<>();
int beg=0;
for (int i = 0; i < charArray.length; i++) {
if(!map.containsKey(charArray[i])){
map.put(charArray[i],i);
}
else{
if(max<i-beg){
max=i-beg;
}
for(int j= beg;j<map.get(charArray[i]);j++){
map.remove(charArray[j]);
}
beg = map.get(charArray[i])+1;
map.put(charArray[i],i);
}
}
if(max<map.size()){
max=map.size();
}
return max;
}
找到字符串中所有字母异位词

思路
采取hash的思想 ,对比数组是否相等判断是否为子串
解法
public static List<Integer> findAnagrams(String s, String p) {
List<Integer>res=new ArrayList<>();
if(p.length()>s.length()){
return res;
}
int[] pl = new int[26];
int[] sl = new int[26];
for (int i = 0; i < p.length(); i++) {
pl[p.charAt(i) - 'a']++;
sl[s.charAt(i) - 'a']++;
}
if(Arrays.equals(pl,sl)){
res.add(0);
}
for (int i = 0; i < s.length()-p.length(); i++) {
sl[s.charAt(i) - 'a']--;
sl[s.charAt(i+p.length()) - 'a']++;
if(Arrays.equals(pl,sl)){
res.add(i+1);
}
}
return res;
}
子串
和为k的子数组

思路
对于每个前缀和 s [i],只要统计前面有多少个前缀和 s [j] 等于 s [i]-k,就有多少个以 i 结尾、和为 k 的子数组
解法
public static int subarraySum(int[] nums, int k) {
// 步骤1:初始化基础变量
int n = nums.length;
// 前缀和数组s,长度n+1(s[0]~s[n]),s[0]=0(默认初始化)
int[] s = new int[n + 1];
// 构建前缀和数组:s[i+1] = 前i个元素的和 = s[i] + nums[i]
for (int i = 0; i < n; i++) {
s[i + 1] = s[i] + nums[i];
}
// 步骤2:用哈希表统计前缀和出现的次数,快速计算答案
// cnt: key=前缀和值,value=该前缀和出现的次数;预分配空间减少扩容开销
Map<Integer, Integer> cnt = new HashMap<>(n + 1, 1);
int ans = 0; // 最终结果:和为k的子数组数量
// 遍历所有前缀和s[j](j从0到n)
for (int sj : s) {
// 核心:找前面有多少个前缀和等于 sj - k → 这就是当前能新增的符合条件的子数组数量
ans += cnt.getOrDefault(sj - k, 0);
// 将当前前缀和sj的出现次数+1(merge等价于 cnt.put(sj, cnt.getOrDefault(sj,0)+1))
cnt.merge(sj, 1, Integer::sum);
}
return ans;
}
滑动窗口最大值

思路
单调队列,具体思路见,主要就是考虑
-
新进来的元素是窗口中最大
-
考虑最大的元素出窗口
解法public static int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] ans = new int[n - k + 1]; // 窗口个数
Deque<Integer> q = new ArrayDeque<>(); // 更快的写法见【Java 数组】
for (int i = 0; i < n; i++) {
// 1. 右边入
while (!q.isEmpty() && nums[q.getLast()] <= nums[i]) {//如果说队列非空并且新添加的元素比最小的元素大,就移除最小的元素,这里是while连续删
q.removeLast(); // 维护 q 的单调性
}
q.addLast(i); // 添加到队列(如果执行了上面的删除 队列中只有一个最大的元素 如果没有执行,就插在队列后面)
// 2. 左边出
int left = i - k + 1; // 窗口左端点 判断当前节点作为右端点 此时的左端点序号
if (q.getFirst() < left) { // 队首离开窗口,如果说此时的最大值对应序号在这个left之前 就抛出
q.removeFirst();
}
// 3. 在窗口左端点处记录答案
if (left >= 0) {
// 由于队首到队尾单调递减,所以窗口最大值就在队首
ans[left] = nums[q.getFirst()];
}
}
return ans;
}
最小覆盖子串

思路
对于此题 我们要考虑的点是
-
如何找到最小的
-
如何快速判断left到right中是否包含子串
这里采取了tmap存放了子串的每个字符出现的次数,tmap的长度就是子串的不同字符个数total
smap用来快速判断left和right之间是否存在子串,移动right,如果出现了t中的字符并且数量和t中对应字符相同,now加1
当now和total相同时,说明left和right之间存在子串
就移动left缩短长度,如果减少了t中的字符,导致now不等于total就跳出缩短过程,继续扩大right
对于子串的保存使用了一个数组ans[3]
ans[0]保存的是子串长度
ans[1]保存的是子串的起始left
ans[2]保存的子串结束right
解法
public static String minWindow(String s, String t) {
if (s == null || t == null || s.length() == 0 || t.length() == 0 || s.length() < t.length()) {
return "";
}
HashMap<Character, Integer> tmap = new HashMap<>();
HashMap<Character, Integer> smap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
tmap.put(t.charAt(i), tmap.getOrDefault(t.charAt(i), 0) + 1);//记录子串中每个元素的出现次数
}
int total=tmap.size();//记录不同的字符数量
int now=0;
int left,right;
int[]ans=new int[]{-1,0,0};
left=right=0;
while(left<=right&&right<s.length()){
smap.put(s.charAt(right),smap.getOrDefault(s.charAt(right),0) + 1);
if(tmap.containsKey(s.charAt(right))&&tmap.get(s.charAt(right)).equals(smap.get(s.charAt(right)))){
//如果当前字符在子串中同时数量满足要求
now++;//符合要求的字符加1
}
if(now==total){
//说明left到right之间存在子串
//通过缩短left找到最小的子串
while(left<=right&&now==total){
char c = s.charAt(left);
if(tmap.containsKey(c)){//是在子串中的元素
if(ans[0]==-1||ans[0]>right-left+1) {
ans[0] = right - left + 1;
ans[1] = left;
ans[2] = right;
}
}
smap.put(s.charAt(left),smap.get(s.charAt(left))-1);
if(tmap.containsKey(s.charAt(left))&&smap.get(s.charAt(left))<tmap.get(s.charAt(left))){
now--;
}
left++;
}
}
right++;
}
return ans[0] == -1 ? "" : s.substring(ans[1], ans[2] + 1);
}
普通数组
最大子数组和

解法
public static int maxSubArray(int[] nums) {
int be=nums[0];
int max=nums[0];
for(int i=1;i<nums.length;i++){
int x=be+nums[i];
if(x>nums[i]){//只要该元素的前缀和比该元素大就保留
be=x;
}
else{
be=nums[i];//如果这个元素比前缀和大就把这个元素当作前缀的起点
}
max=Math.max(max,be);
}
return max;
}
合并区间

解法:
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);//按起始位置的升序排序
List<int[]> mergedIntervals = new ArrayList<>();
int beg=intervals[0][0];
int end=intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if(intervals[i][0]<=end) {//起始位置在end之前就是可以合并的
end=Math.max(end, intervals[i][1]);
}
else{//此时说明起始位置已经大于end了,那么就应该新开一个合并集合了
mergedIntervals.add(new int[]{beg,end});//将之前的beg和end存进去
beg=intervals[i][0];
end=intervals[i][1];
}
}
mergedIntervals.add(new int[]{beg,end});
return mergedIntervals.toArray(new int[mergedIntervals.size()][]);
轮转数组

class Solution {
public void reverse(int[]nums,int beg,int end){
int x;
for(int i=beg;i<=(end+beg)/2;i++){
x=nums[i];
nums[i]=nums[beg+end-i];
nums[beg+end-i]=x;
}
}
public void rotate(int[] nums, int k) {
k=k%nums.length;
reverse(nums,0,nums.length-1-k);
reverse(nums,nums.length-k,nums.length-1);
reverse(nums,0,nums.length-1);
}
}
除了自身以外数组的乘积

class Solution {
public int[] productExceptSelf(int[] nums) {
int n,i,j;
n=nums.length;
int[]qian=new int[n];
int[]hou=new int[n];
qian[0]=1;
hou[n-1]=1;
for(i=1;i<n;i++){
qian[i]=qian[i-1]*nums[i-1];
}
for(j=n-2;j>=0;j--){
hou[j]=hou[j+1]*nums[j+1];
}
int[]answer=new int[n];
for(i=0;i<n;i++){
answer[i]=qian[i]*hou[i];
}
return answer;
}
}
缺失的第一个正数

思路 :
使用占位的思想,对于一个位置num[i],他按照值来说,正常位次应该在num[num[i]-1]的位置,我们将所有的值放在该在的位置,
然后遍历数组,哪个位置的值不满足要求,就返回这个位置的值就行
如果都满足要求,就返回数组长度+1就行
样例:
3,4,-1,1
执行完操作后应该变为
1,-1,3,4
就可以很快找出2是缺失的正数
解法
class Solution {
public int firstMissingPositive(int[] nums) {
for(int i=0;i<nums.length;i++){
while(nums[i]>=1&&nums[i]<=nums.length&&nums[i]!=nums[nums[i]-1]){
int a=nums[nums[i]-1];
nums[nums[i]-1]=nums[i];
nums[i]=a;
}
}
for(int i=0;i<nums.length;++i){
if(nums[i]!=i+1){
return i+1;
}
}
return nums.length+1;
}
}
矩阵
矩阵置零

解法
class Solution {
public void setZeroes(int[][] matrix) {
//采取第一行和第一列作为行和列矩阵,(0,0)会被使用两次,单独使用遍历row来表示行,数组中标识列
int row=1;
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[i].length;j++){
if(matrix[i][j]==0){
if(i==0){
row=0;
}
else{
matrix[i][0]=0;
}
matrix[0][j]=0;
}
}
}
for(int i=1;i<matrix.length;i++){
if(matrix[i][0]==0){
for(int j=1;j<matrix[i].length;j++){
matrix[i][j]=0;
}
}
}
for(int i=0;i<matrix[0].length;i++){
if(matrix[0][i]==0){
for(int j=1;j<matrix.length;j++){
matrix[j][i]=0;
}
}
}
if(row==0){
for(int i=0;i<matrix[0].length;i++){
matrix[0][i]=0;
}
}
}
}
螺旋数组

解法
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
int m,n;
List res=new ArrayList<>();
m=matrix.length;//行数
n=matrix[0].length;//列数
int i=0,j=0;
int x=0;
int minx=0,miny=0;
while(n!=0&&m!=0){
if(x==0){
while(j<=n-1){
res.add(matrix[i][j]);
j++;
}
j=n-1;
i++;
if(i>=m){
break;
}
x=1;
}
else if(x==1){
while(i<=m-1){
res.add(matrix[i][j]);
i++;
}
i=m-1;
j--;
if(j<minx){
break;
}
x=2;
}
else if(x==2){
while(j>=minx){
res.add(matrix[i][j]);
j--;
}
j=minx;
i--;
x=3;
}
else{
while(i>miny){
res.add(matrix[i][j]);
i--;
}
j++;
x=0;
minx++;
miny++;
i=miny;
m--;
n--;
if(i>=m||j>=n){
break;
}
}
}
return res;
}
}
旋转图像

解法
class Solution {
public void rotate(int[][] matrix) {
//分为两步 矩阵转置,将列倒序
int x=0;
int n=matrix.length;
//转置
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
x = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = x;
}
}
//列数倒序
for (int i = 0; i < n/2; i++) {
for (int j = 0; j < n; j++) {
x=matrix[j][i];
matrix[j][i]=matrix[j][n-1-i];
matrix[j][n-1-i]=x;
}
}
}
}
搜索二维矩阵 II

思路 :
根据样例而言,我们将行和列连起来看,即第一行+最后一列,按照右上角的元素来看,比这个元素大的就在列上,小的就在行上,基于这个发现,引申出来的做法:
初始化为整个二维数组最右上角的元素
如果target等于这个元素,直接返回true
如果说target小于这个元素,说明不可能存在于这一列上,那么就把这一列向前移动
如果说target大于这个元素,说明不可能存在于这一行上,那么就把这一行向下移动
解法
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int x=0;
int y=matrix[x].length-1;//从右上角的元素开始比较
while(x<matrix.length&&y>=0){
if(matrix[x][y]==target){
return true;
}
else if(matrix[x][y]<target){//右上角元素小于target,说明target存在于下一列之后
++x;
}
else{//右上角元素大于target,说明target存在于上一行之前
--y;
}
}
return false;
}
}
链表
相交链表

解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lena,lenb;
lena=lenb=0;
ListNode head1=headA;
while(head1!=null){
head1=head1.next;
lena++;
}
ListNode head2=headB;
while(head2!=null){
head2=head2.next;
lenb++;
}//找到两个链表的长度
head1=headA;
head2=headB;
while(lena>lenb){
head1=head1.next;
lena--;
}
while(lena<lenb){
head2=head2.next;
lenb--;
}//对齐两个链表
while(head2!=head1&&head1!=null&&head2!=null){
head1=head1.next;
head2=head2.next;
}
if(head1!=null&&head2!=null){
return head1;
}
return null;
}
}
反转链表

解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode before=null;
ListNode now=head;
while(now!=null){
ListNode a=now.next;//原来的后面那个
now.next=before;//要反向指向的节点
before=now;//更新
now=a;//更新
}
return before;
}
}
回文链表

解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
boolean res=true;
if(head==null){
return false;
}
if(head.next==null){
return true;
}
int sum=0;
ListNode beg=head;
ListNode end=head;
while(end!=null){
beg=beg.next;
end=end.next;
if(end!=null){
end=end.next;
}
}
ListNode before=null;
ListNode now=beg;
while(now!=null){
ListNode a= now.next;
now.next=before;
before=now;
now=a;
}//后半反转
ListNode hou=before;
ListNode qian=head;
while(qian!=null&&hou!=null){
if(qian.val!=hou.val){
res=false;
break;
}
qian=qian.next;
hou=hou.next;
}
return res;
}
}
环形链表

思路
快慢指针 :如果当块指针遇到慢指针就有环,遇不到能走到末尾就没环。
解法
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
// 空链表、单节点链表一定不会有环
while (fast != null && fast.next != null) {
fast = fast.next.next; // 快指针,一次移动两步
slow = slow.next; // 慢指针,一次移动一步
if (fast == slow) { // 快慢指针相遇,表明有环
return true;
}
}
return false; // 正常走到链表末尾,表明没有环
}
}
环形链表 II

思路
pos不是参数,只是评测内部的判断是否有环的参数
所以此题的目的是找到环的开始位置

我们使用快慢指针,慢指针一次走1,快指针一次走2
如上图所示,在紫色点相遇(快慢)
此时快指针 走过的距离为:fast=a+b+n*(b+c)[n为快指针在环中走的圈数]
由此可知慢指针 走的距离是fast/2 =a+b
由此整理可知道 2a+2b=a+b+nb+nc
a=c+(n-1)(b+c)
由此我们找相遇点,此时慢指针在紫色点,距离相遇点距离为c,我们让一个节点从头结点向前走(每次1个),他们会在相遇点相遇
原因在于从头结点走到相遇点,慢指针走了n-1圈+c,正好相遇 。
解法
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null){
return null;
}
ListNode slow=head;
ListNode fast=head;
while(slow!=null&&fast!=null){
slow=slow.next;
fast=fast.next;
if(fast==null){
return null;
}
else{
fast=fast.next;
}
if(fast!=null&&slow!=null){
if(fast==slow){//相遇
break;
}
}
else{//任意一个为null就无环
return null;
}
}
ListNode beg=head;
while(beg!=slow){//
beg=beg.next;
slow=slow.next;
}
return beg;
}
}
合并两个有序链表

解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode res=new ListNode();
ListNode x=res;
while(list1!=null&&list2!=null){
if(list1.val<=list2.val){
x.next=list1;
list1=list1.next;
}
else{
x.next=list2;
list2=list2.next;
}
x=x.next;
}
if(list1==null&&list2!=null){
x.next=list2;
}
if(list2==null&&list1!=null){
x.next=list1;
}
return res.next;
}
}
两数相加

解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode res=new ListNode();
ListNode head=res;
int num=0;//模拟进位
while(l1!=null&&l2!=null){
int x=l1.val+l2.val+num;//对应位相加
if(x>=10){//有进位
num=1;
res.next=new ListNode(x%10);
res=res.next;
}
else{//无进位
num=0;
res.next=new ListNode(x);
res=res.next;
}
l1=l1.next;
l2=l2.next;//后移一位
}//说明有一个到最后了
if(l1==null){//到达表1的最后了
while(l2!=null){//使用进位来继续相加
int x=num+l2.val;
if(x>=10){
num=1;
res.next=new ListNode(x%10);
res=res.next;
}
else{
num=0;
res.next=new ListNode(x);
res=res.next;
}
l2=l2.next;
}
}
else{//到表2 到最后了 模拟上面的逻辑
while(l1!=null){
int x=num+l1.val;
if(x>=10){
num=1;
res.next=new ListNode(x%10);
res=res.next;
}
else{
num=0;
res.next=new ListNode(x);
res=res.next;
}
l1=l1.next;
}
}
if(num==1)//最后判断进位还存在就需要多一位了
{
res.next=new ListNode(1);
res=res.next;
}
return head.next;
}
}
删除链表的倒数第N个节点

解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fake=new ListNode();
fake.next=head;
ListNode fakehead=fake;
ListNode x=head;
ListNode y=head;
int i=1;
while(i!=n){//快指针后移n位
y=y.next;
i++;
}
while(y.next!=null){//快慢一起移动,快指针到最后一个节点,就到达了倒数第n个节点了
x=x.next;
y=y.next;
fakehead=fakehead.next;//倒数第n+1个元素 方便删除
}
fakehead.next=x.next;
return fake.next;
}
}
两两交换链表中的节点

思路
采取一个空节点作为头结点(方便操作)
before保存两两交换的前一个节点
a为两两交换的第一个节点
be为两两交换的第二个节点
next保存下一组的第一个节点
next=b.next;
before.next=b;
b.next=a;
before=b.next;
a=next;
b=a.next;
解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode first=new ListNode();//空头结点
first.next=head;
ListNode be=first;//前一组的最后一个节点(初始为空头结点)
ListNode mid=head;//该组第一个节点
ListNode end=head.next;//该组的第二个节点
ListNode cun=new ListNode();//存放下一组的第一个节点
while(true){
be.next=end;//上一组的最后一个节点指向该组第二个节点
cun=end.next;//记录下一组的第一个节点
end.next=mid;//交换
mid.next=cun;//指向下一组的第一个节点
be=mid;//更换be
mid=cun;//换组
if(mid==null){//下一组开始为null结束
break;
}
end=cun.next;
if(end==null){下一组第二个为null
break;
}
}
return first.next;//返回真实的头结点
}
}
K个一组翻转链表

思路
类似于上题的思路
解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if(k==1){
return head;
}
ListNode fakehead=new ListNode();
fakehead.next=head;
ListNode pre=fakehead;
ListNode beg=head;
ListNode end=head;
while(beg!=null&&end!=null){
for(int i=0;i<k-1;i++){
end=end.next;//记录该组最后一个元素
if(end==null){
return fakehead.next;//还没到就为null直接返回了,构不成一组
}
}
ListNode m=new ListNode();
ListNode beg1=beg;
ListNode nextbeg=end.next;
while(beg1!=end){//依次反转直到最后一个
m=beg1.next;
beg1.next=end.next;
end.next=beg1;
beg1=m;
}
pre.next=end;//最后一个单独处理
pre=beg;
beg=beg.next;
end=beg;
}
return fakehead.next;
}
}
随机链表的复制

解法
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
Map<Node,Integer>map=new HashMap<>();
List<Node> list=new ArrayList<>();
int i=0;
Node beg=head;
while(beg!=null){//将每个节点序号映射到map
map.put(beg,i);
i++;
beg=beg.next;
}
Node res=new Node(0);
Node x=res;
beg=head;
while(beg!=null){//拷贝正常链表
Node tes=new Node(beg.val);
x.next=tes;
x=x.next;
beg=beg.next;
list.add(tes);
}
beg=head;
x=res.next;
int num;
while(beg!=null){//处理随机指针
if(beg.random!=null){
num=map.get(beg.random);
x.random=list.get(num);
}
else{
x.random=null;
}
beg=beg.next;
x=x.next;
}
return res.next;
}
}
排序链表

解法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
// 递归终止条件
if (head == null || head.next == null) {
return head;
}
// 找到中间节点并分割链表
ListNode mid = getMiddle(head);
ListNode rightHead = mid.next;
mid.next = null; // 断开左右链表
// 递归排序左右两部分
ListNode left = sortList(head);
ListNode right = sortList(rightHead);
// 合并有序链表
return merge(left, right);
}
// 使用快慢指针找到链表的中间节点
private ListNode getMiddle(ListNode head) {
if (head == null) return null;
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
// 合并两个有序链表
private ListNode merge(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
tail.next = l1;
l1 = l1.next;
} else {
tail.next = l2;
l2 = l2.next;
}
tail = tail.next;
}
// 处理剩余节点
if (l1 != null) tail.next = l1;
if (l2 != null) tail.next = l2;
return dummy.next;
}
}
合并K个升序链表

思路
lists 一分为二(尽量均分),先合并前一半的链表,再合并后一半的链表,然后把这两个链表合并成最终的链表。
解法
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists, 0, lists.length - 1);
}
// 合并从 lists[i] 到 lists[j-1] 的链表
public ListNode merge(ListNode[] lists, int l, int r) {
if (l == r) {
return lists[l];
}
if (l > r) {
return null;
}
int mid = (l + r) /2;
return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
}
public ListNode mergeTwoLists(ListNode a, ListNode b) {
if (a == null || b == null) {
return a != null ? a : b;
}
ListNode head = new ListNode(0);
ListNode tail = head, aPtr = a, bPtr = b;
while (aPtr != null && bPtr != null) {
if (aPtr.val < bPtr.val) {
tail.next = aPtr;
aPtr = aPtr.next;
} else {
tail.next = bPtr;
bPtr = bPtr.next;
}
tail = tail.next;
}
tail.next = (aPtr != null ? aPtr : bPtr);
return head.next;
}
}
LRU缓存

思路
直接使用LinkedHashMap,它本身就实现了LRU
get 之前先把map中的key删了再插入
put 之前先判断是否存在,存在就删除再插入,返回就行,如果不存在就判断size,如果size不满,插入返回就好
如果size满了,就用迭代器取出key的第一个(最久未使用的),删除
解法
class LRUCache {
private final int capacity;
private final Map<Integer, Integer> cache = new LinkedHashMap<>(); // 内置 LRU
public LRUCache(int capacity) {
this.capacity = capacity;
}
public int get(int key) {
// 删除 key,并利用返回值判断 key 是否在 cache 中
Integer value = cache.remove(key);
if (value != null) { // key 在 cache 中
cache.put(key, value);
return value;
}
// key 不在 cache 中
return -1;
}
public void put(int key, int value) {
// 删除 key,并利用返回值判断 key 是否在 cache 中
if (cache.remove(key) != null) { // key 在 cache 中
cache.put(key, value);
return;
}
// key 不在 cache 中,那么就把 key 插入 cache,插入前判断 cache 是否满了
if (cache.size() == capacity) { // cache 满了
Integer eldestKey = cache.keySet().iterator().next();
cache.remove(eldestKey); // 移除最久未使用 key
}
cache.put(key, value);
}
}
二叉树
二叉树的中序遍历

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
find(root,res);
return res;
}
public void find(TreeNode root,List<Integer>res){
if(root==null){
return;
}
find(root.left,res);
res.add(root.val);
find(root.right,res);
}
}
二叉树的最大深度

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int max=0;
public int maxDepth(TreeNode root) {
findmax(root,1);
return max;
}
public void findmax(TreeNode root,int len){
if(root==null){
return;
}
if(len>max){
max=len;
}
findmax(root.left,len+1);
findmax(root.right,len+1);
}
}
翻转二叉树

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode invertTree(TreeNode root) {
reverse(root);
return root;
}
public void reverse(TreeNode root){
if(root==null){
return;
}
if(root.left!=null||root.right!=null){
TreeNode s=root.left;
root.left=root.right;
root.right=s;
}
reverse(root.left);
reverse(root.right);
}
}
对称二叉树

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
boolean res=true;
public void check(TreeNode r1,TreeNode r2){
if(res==false||(r1==null&&r2==null)){
return;
}
if((r1==null&&r2!=null)||(r1!=null&&r2==null)){
res=false;
return;
}
if(r1.val!=r2.val){
res=false;
return;
}
check(r1.left,r2.right);
check(r1.right,r2.left);
}
public boolean isSymmetric(TreeNode root) {
TreeNode left=root.left;
TreeNode right=root.right;
check(left,right);
return res;
}
}
二叉树的直径

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int ans;
public int diameterOfBinaryTree(TreeNode root) {
ans = 1;
depth(root);
return ans - 1;
}
public int depth(TreeNode node) {
if (node == null) {
return 0; // 访问到空节点了,返回0
}
int L = depth(node.left); // 左儿子为根的子树的深度
int R = depth(node.right); // 右儿子为根的子树的深度
ans = Math.max(ans, L+R+1); // 计算d_node即L+R+1 并更新ans
return Math.max(L, R) + 1; // 返回该节点为根的子树的深度
}
}
二叉树的层序遍历

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> res;
public List<List<Integer>> levelOrder(TreeNode root) {
res=new ArrayList<>();
levelOrderleft(root,0);
return res;
}
public void levelOrderleft(TreeNode root,int height){//前序遍历是从左到右的正好符合题目要求
if(root==null){
return;
}
if(res.size()-1<height){//如果不存在这一层的数组就创建
List<Integer>tes =new ArrayList<>();
res.add(tes);
}
res.get(height).add(root.val);//添加
if(root.left!=null){
levelOrderleft(root.left,height+1);
}
if(root.right!=null){
levelOrderleft(root.right,height+1);
}
return;
}
}
将有序数组转换为二叉搜索树

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return helper(nums, 0, nums.length - 1);
}
public TreeNode helper(int[] nums, int left, int right) {
if (left > right) {
return null;
}
// 总是选择中间位置左边的数字作为根节点
int mid = (left + right) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = helper(nums, left, mid - 1);
root.right = helper(nums, mid + 1, right);
return root;
}
}
验证二叉搜索数

解法
class Solution {
public boolean isValidBST(TreeNode root) {
Deque<TreeNode> stack = new LinkedList<TreeNode>();
double inorder = -Double.MAX_VALUE;//原因范围int要找一个范围外的值
while (!stack.isEmpty() || root != null) {
while (root != null) {//批量插入左节点
stack.push(root);
root = root.left;
}
root = stack.pop();
// 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if (root.val <= inorder) {
return false;
}
inorder = root.val;//更新值
root = root.right;//右节点
}
return true;
}
}
二叉搜索数中第K小的元素

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int x=1;//记录当前为第几个元素 直接中序遍历找就行
int num;
public void mid(TreeNode root,int k){
if(root==null||x>k){
return;
}
mid(root.left,k);
if(x==k){
num=root.val;
}
x++;
mid(root.right,k);
}
public int kthSmallest(TreeNode root, int k) {
mid(root,k);
return num;
}
}
二叉树的右视图

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer>res=new ArrayList<>();
find(root,res,1);
return res;
}
public void find(TreeNode root,List<Integer>res,int heigh){//根右左就行
if(root==null){
return;
}
if(res.size()<heigh){
res.add(root.val);
}
find(root.right,res,heigh+1);
find(root.left,res,heigh+1);
}
}
二叉树展开为链表

思路
-
如果当前节点为空,返回。
-
递归右子树。
-
递归左子树。
-
把 root.left 置为空。
-
头插法,把 root 插在 head 的前面,也就是 root.right=head。
-
现在 root 是链表的头节点,把 head 更新为 root。
解法class Solution {
private TreeNode head;public void flatten(TreeNode root) { if (root == null) { return; } flatten(root.right); flatten(root.left);//此时第一轮找到的就是前序遍历的最后一个元素 root.left = null;//将这个节点的左节点置为null root.right = head; // 头插法,相当于链表的 root.next = head head = root; // 现在链表头节点是 root }}
从前序与中序遍历序列构造二叉树

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public Map<Integer, Integer> indexMap;
public TreeNode findroot(int[]preorder,int[]inorder,int preleft,int preright,int inleft,int inright){
if(preleft>preright){
return null;
}
//前序遍历第一个元素就是根
int root_num=preleft;
int in=indexMap.get(preorder[root_num]);
TreeNode root=new TreeNode(preorder[root_num]);
root.left=findroot(preorder,inorder,preleft+1,preleft+in-inleft,inleft,in-1);//取root的左子树
root.right=findroot(preorder,inorder,preleft+in-inleft+1,preright,in+1,inright);//取root的右子树
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
//做法就是找根做法
int n=inorder.length;
indexMap = new HashMap<Integer, Integer>();
for(int i=0;i<n;i++){
indexMap.put(inorder[i],i);
}
TreeNode root=findroot(preorder,inorder,0,n-1,0,n-1);
return root;
}
}
路径总和 III

思路 :
前缀和:
使用map,key为到当前节点前的前缀和,value为个数
dfs()包含的参数,只介绍下sum,为到这个节点前的所有前缀和,其他的都知道
到一个节点,先前缀和+这个节点值sum
判断这个sum-目标值后是否在map中存在并且大于0 满足就将res+(map中的值)
然后将这个sum放入map中
遍历左右
出来后将这个map(sum)减1
为什么要判断map中存在并且大于0:
原因在于我没有remove对应的key,而是减少,如果为负说明就不存在这个前缀和
解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int res=0;
public int pathSum(TreeNode root, int targetSum) {
if(root==null){
return 0;
}
Map<Long,Integer>map=new HashMap<>();
map.put(0L,1);
dfs(root,targetSum,map,0L);
return res;
}
public void dfs(TreeNode root,int targetSum,Map<Long,Integer>map,Long sum){
if(root==null){
return;
}
sum+=root.val;
if(map.containsKey(sum-targetSum)&&map.get(sum-targetSum)>0){
res+=map.get(sum-targetSum);
}
map.put(sum,map.getOrDefault(sum, 0) + 1);
dfs(root.left,targetSum,map,sum);
dfs(root.right,targetSum,map,sum);
map.put(sum,map.get(sum)-1);
}
}
二叉树的最近公共祖先

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {//使用前序遍历找到p和q的父节点保存在list1和list2中
public List<TreeNode>list1=new ArrayList<>();
public List<TreeNode>list2=new ArrayList<>();
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
List<TreeNode>res=new ArrayList<>();
dfs(root,p,q,res);
TreeNode result=root;
for(int i=list1.size()-1;i>=0;i--){
TreeNode s=list1.get(i);
if (list2.contains(s)) {
return s; // 找到第一个公共节点直接返回
}
}
return result;
}
public void dfs(TreeNode root,TreeNode p,TreeNode q,List<TreeNode>res){
if(root==null||(list1.size()!=0&&list2.size()!=0)){//
return;
}
res.add(root);
if(root==p){
list1=new ArrayList<>(res);
}
if(root==q){
list2=new ArrayList<>(res);
}
dfs(root.left,p,q,res);
dfs(root.right,p,q,res);
res.remove(res.size()-1);
}
}
二叉树的最大路径和

解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int max=Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
findmax(root);
return max;
}
public int findmax(TreeNode root){
if(root==null){
return 0;
}
int left=Math.max(findmax(root.left),0);//是否取左子树的路径
int right=Math.max(findmax(root.right),0);//是否取右子树的路径
int sum=root.val+left+right;//当前路径长度
max=Math.max(sum,max);//更新最大值
return root.val+Math.max(left,right);//返回当前节点是左子树+自身和右子树+自身的最大值
}
}
图论
岛屿数量

简单的深搜
解法
class Solution {
public int sum=0;
public int numIslands(char[][] grid) {
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(grid[i][j]=='1'){
find(grid,i,j);
sum++;
}
}
}
return sum;
}
public void find(char[][] grid,int i,int j){
if(i<0||i>grid.length-1||j<0||j>grid[0].length-1||grid[i][j]!='1'){
return;
}
grid[i][j]='0';
find(grid,i-1,j);
find(grid,i+1,j);
find(grid,i,j-1);
find(grid,i,j+1);
}
}
腐烂的橘子

思路
1 遍历网格中所有腐烂橘子(值≥2)的位置,对每个腐烂橘子的上下左右四个方向格子执行 DFS,DFS 中先判断边界、空格子(值为 0)、非新鲜且代际≤当前传入代际的情况,满足则直接返回,否则将当前格子标记为当前代际值(代表该时间点腐烂),并递归处理该格子的四个方向(代际 + 1);
2 二次遍历网格,先检查是否存在未腐烂的新鲜橘子(值为 1),若有则返回 - 1,同时计算每个腐烂橘子代际值与初始腐烂值(2)的差值,更新最大差值到 max 变量;
3 最终返回 max,该值即为所有新鲜橘子被腐烂的最大时间(无新鲜橘子时返回 0)。
解法
class Solution {
public int max=0;
public int orangesRotting(int[][] grid) {
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[i].length;j++){
if(grid[i][j]>=2){
dfs(grid,i-1,j,grid[i][j]+1);
dfs(grid,i+1,j,grid[i][j]+1);
dfs(grid,i,j-1,grid[i][j]+1);
dfs(grid,i,j+1,grid[i][j]+1);
}
}
}
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[i].length;j++){
if(grid[i][j]==1){
return -1;
}
max=Math.max(max,grid[i][j]-2);
}
}
return max;
}
public void dfs(int[][]grid,int i,int j,int gen){
if(i<0||i>grid.length-1||j<0||j>grid[0].length-1||grid[i][j]==0||(grid[i][j]!=1&&grid[i][j]<=gen)){
return;
}
grid[i][j]=gen;
dfs(grid,i-1,j,gen+1);
dfs(grid,i+1,j,gen+1);
dfs(grid,i,j-1,gen+1);
dfs(grid,i,j+1,gen+1);
}
}
课程表

宽搜思想 :
将出度信息保存在一个grid
使用一个队列
每次放入新的起点
解法
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[]tes=new int[numCourses];
List<List<Integer>>grid=new ArrayList<>();
for(int i=0;i<numCourses;i++){
grid.add(new ArrayList<Integer>());
}
for(int i=0;i<prerequisites.length;i++){
grid.get(prerequisites[i][1]).add(prerequisites[i][0]);
tes[prerequisites[i][0]]++;
}
Queue<Integer> queue = new LinkedList<Integer>();
for(int i=0;i<numCourses;i++){
if(tes[i]==0){
queue.offer(i);
}
}
int len=0;
while(!queue.isEmpty()){
len++;
int beg=queue.poll();
for(int i=0;i<grid.get(beg).size();i++){
tes[grid.get(beg).get(i)]--;
if(tes[grid.get(beg).get(i)]==0){
queue.offer(grid.get(beg).get(i));
}
}
}
return len==numCourses;
}
}
实现前缀树

class Trie {
public Trie[]child;
public boolean isend;
public Trie() {
child=new Trie[26];//由于全部都是由小写字母组成,这里使用26的数组标识不同的字母
isend=false;//用于标识是否为一个字符串的结束
}
public void insert(String word) {
Trie node=this;//取根节点
for(int i=0;i<word.length();i++){
char a=word.charAt(i);
if(node.child[a-'a']==null){//不存在这个字符就创建子节点
node.child[a-'a']=new Trie();
}
node=node.child[a-'a'];//移动到子节点
}
node.isend=true;//将字符串的结尾置为true
}
public boolean search(String word) {
Trie node=this;//取根节点
for(int i=0;i<word.length();i++){
char a=word.charAt(i);
if(node.child[a-'a']==null){//不存在该字母对应的子节点就说明已经不存在了
return false;
}
node=node.child[a-'a'];
}
if(node.isend==false){//查到最后结束符不为true,就说明这是某个单词的前缀,而不是这个单词的完整
return false;
}
return true;
}
public boolean startsWith(String prefix) {//查前缀逻辑类似
Trie node=this;
for(int i=0;i<prefix.length();i++){
char a=prefix.charAt(i);
if(node.child[a-'a']==null){
return false;
}
node=node.child[a-'a'];
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
回溯
全排列

解法
class Solution {
public List<List<Integer>>res;
public List<Integer>tes;
public List<List<Integer>> permute(int[] nums) {
res=new ArrayList<>();
tes=new ArrayList<>();
int[]zai=new int[nums.length];
dfs(nums,zai);
return res;
}
public void dfs(int[]nums,int[]zai){//回溯模板
if(tes.size()==zai.length){//满足的条件
res.add(new ArrayList<>(tes));//不能直接存放res,原因这里直接存的话就是同一个res的引用
return;
}
for(int i=0;i<nums.length;i++){
if(zai[i]==0){//判断对应位的值是否使用过
zai[i]=1;//这两步就是标记这个位置已经使用了
tes.add(nums[i]);
dfs(nums,zai);
zai[i]=0;//这两步就是回溯
tes.remove(tes.size()-1);
}
}
}
}
子集

仿照上题的思路
解法
class Solution {
public List<List<Integer>>res;
public List<List<Integer>> subsets(int[] nums) {
int[]zai=new int[nums.length];
List<Integer>tes=new ArrayList<>();
res=new ArrayList<>();
for(int i=0;i<=nums.length;i++){//注意这里限制的len
dfs(nums,tes,i,0);
}
return res;
}
public void dfs(int[]nums,List<Integer>tes,int len,int beg){
if(tes.size()==len){
res.add(new ArrayList<>(tes));
return;
}
for(int i=beg;i<nums.length;i++){
tes.add(nums[i]);
dfs(nums,tes,len,i+1);
tes.remove(tes.size()-1);
}
}
}
电话号码的字母组合

解法
class Solution {
public List<String> letterCombinations(String digits) {
List<String>res=new ArrayList<String>();
if(digits.length()==0){
return res;
}
Map<Character,String>num=new HashMap<>();//存放不同号码的对应字符串
num.put('2', "abc");
num.put('3', "def");
num.put('4', "ghi");
num.put('5', "jkl");
num.put('6', "mno");
num.put('7', "pqrs");
num.put('8', "tuv");
num.put('9', "wxyz");
find(res,num,digits,0,new StringBuffer());
return res;
}
public void find(List<String>res,Map<Character,String>num,String digits,int beg,StringBuffer tes){
if(beg==digits.length()){//结束条件是等于点击的号码总数
res.add(tes.toString());
return;
}
String a=num.get(digits.charAt(beg));//获取目前访问的号码
for(int j=0;j<a.length();j++){//对于这个号码的字符串每个字符依次插入和回溯
tes.append(a.charAt(j));
find(res,num,digits,beg+1,tes);
tes.deleteCharAt(beg);
}
}
}
组合总数

解法
class Solution {
public List<List<Integer>>res;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<Integer>tes=new ArrayList<>();
res=new ArrayList<>();
find(candidates,target,tes,0,0);
return res;
}
public void find(int[]candidates,int target,List<Integer>tes,int sum,int beg){
if(sum==target){//和为目标值就结束
res.add(new ArrayList<>(tes));
return;
}
for(int i=beg;i<candidates.length;i++){
if(sum+candidates[i]<=target){//由于题目限制了全为正数,所以大于了就不要尝试了
tes.add(candidates[i]);
//这里必须从前到后,不然会序列重复,从i重新尝试是因为每一位可以重复使用
find(candidates,target,tes,sum+candidates[i],i);
tes.remove(tes.size()-1);
}
}
}
}
括号生成

解法
class Solution {
public List<String> res;
public List<String> generateParenthesis(int n) {
res=new ArrayList<>();
StringBuffer s=new StringBuffer();
int[]zai=new int[2];
zai[0]=n;//标识(的个数
zai[1]=n;//标识)的个数
find(zai,s);
return res;
}
public void find(int[]zai,StringBuffer s){
if(zai[0]==0&&zai[1]==0){//结束条件是()分别用完
res.add(s.toString());
return;
}
for(int i=0;i<=1;i++){
if(i==0&&zai[0]>0){//对于(可以随便放,只要不超过数量就行
s.append('(');
zai[0]--;
find(zai,s);
zai[0]++;
s.deleteCharAt(s.length()-1);
}
if(i==1&&zai[1]>0&&zai[0]<zai[1]){//对于右括号,由于要求合法,那就必须限制)的个数一直等于或者大于(的个数
s.append(')');
zai[1]--;
find(zai,s);
zai[1]++;
s.deleteCharAt(s.length()-1);
}
}
}
}
单词搜索

解法
class Solution {
boolean isexist=false;
public boolean exist(char[][] board, String word) {
int[][]zai=new int[board.length][board[0].length];
for(int i=0;i<board.length;i++){
for(int j=0;j<board[i].length;j++){
if(board[i][j]==word.charAt(0)){//找到起点
zai[i][j]=1;
find(board,i,j,word,1,zai);
zai[i][j]=0;
}
if(isexist){
return isexist;
}
}
}
return isexist;
}
public void find(char[][] board,int i,int j ,String word,int index,int[][]zai){//传入的index是要对比的word的哪一位字符
if(index==word.length()||isexist){//如果确定存在了就没必要做了或者找到单词就结束(index知道word的外面就是结束了)
isexist=true;
return;
}
//以下都差不多,首先判断该点是否合法,同时判断是否和对应字符相等
if(i-1>=0&&zai[i-1][j]!=1&&board[i-1][j]==word.charAt(index)){
zai[i-1][j]=1;
find(board,i-1,j,word,index+1,zai);
zai[i-1][j]=0;
}
if(i+1<board.length&&zai[i+1][j]!=1&&board[i+1][j]==word.charAt(index)){
zai[i+1][j]=1;
find(board,i+1,j,word,index+1,zai);
zai[i+1][j]=0;
}
if(j-1>=0&&zai[i][j-1]!=1&&board[i][j-1]==word.charAt(index)){
zai[i][j-1]=1;
find(board,i,j-1,word,index+1,zai);
zai[i][j-1]=0;
}
if(j+1<board[i].length&&zai[i][j+1]!=1&&board[i][j+1]==word.charAt(index)){
zai[i][j+1]=1;
find(board,i,j+1,word,index+1,zai);
zai[i][j+1]=0;
}
}
}
分割回文串

解法
class Solution {
public List<List<String>> partition(String s) {
List<List<String>>res=new ArrayList<>();
int[][]dp=new int[s.length()][s.length()];
dfs(s,0,res,dp,new ArrayList<>());
return res;
}
public void dfs(String s,int index,List<List<String>>res,int[][]dp,List<String>tes){
if(index==s.length()){//结束条件
res.add(new ArrayList<>(tes));
return;
}
for(int i=index;i<s.length();++i){
if(ishui(index,i,s,dp)){//是回文串
tes.add(s.substring(index,i+1));//回溯逻辑
dfs(s,i+1,res,dp,tes);
tes.remove(tes.size()-1);
}
}
}
public boolean ishui(int left,int right,String s,int[][]dp){//dp快速判断是否为回文串
if(dp[left][right]==1){//标志从left到right之间的串是回文串
return true;
}
if(dp[left][right]==-1){
return false;
}
while(left!=right&&left<right){//正常判断
if(s.charAt(left)!=s.charAt(right)){
dp[left][right]=-1;
return false;
}
left++;
right--;
}
dp[left][right]=1;
return true;
}
}
N皇后

解法
class Solution {
public List<List<String>> solveNQueens(int n) {
String[][]dp=new String[n][n];
int[]col=new int[n];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
dp[i][j]=".";
}
}
List<List<String>>res=new ArrayList<>();
find(0,n,dp,res,col);
return res;
}
public void find(int index,int n,String[][]dp,List<List<String>>res,int[]col){
if(index==n){
List<String>tes=new ArrayList<>();
for(int i=0;i<n;i++){
String s="";
for(int j=0;j<n;j++){
s+=dp[i][j];
}
tes.add(s);
s=null;
}
res.add(tes);
return;
}
for(int i=0;i<n;i++){//n皇后,那么每个皇后必定在不同的行,所以从每行考虑在哪添加皇后
if(isok(dp,index,i,n,col)||index==0){
dp[index][i]="Q";
col[i]=1;
find(index+1,n,dp,res,col);
col[i]=0;
dp[index][i]=".";
}
}
}
public boolean isok(String[][]dp,int x,int y,int n,int[]col){//判断该点的列,左上,右上对角线是否有重复
if(col[y]==1){
return false;
}
int i=x-1;
int j=y-1;
while(i>=0&&j>=0){//左上对角线
if(dp[i][j]=="Q"){
return false;
}
i--;
j--;
}
i=x-1;
j=y+1;
while(i>=0&&j<n){//右上对角线
if(dp[i][j]=="Q"){
return false;
}
i--;
j++;
}
return true;
}
}
二分查找
搜索插入位置

解法
class Solution {
public int searchInsert(int[] nums, int target) {
int beg=0;
int end=nums.length-1;
int mid;
while(beg<=end){
mid=beg+((end-beg)/2);
if(target==nums[mid]){
return mid;
}
else if(target<nums[mid]){
end=mid-1;
}
else{
beg=mid+1;
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right + 1
// 目标值在数组所有元素之后的情况 [left, right],这是右闭区间,所以 return right + 1
return end+1;
}
}
搜索二维矩阵

解法
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int row=-1;
for(int i=0;i<matrix.length;i++){
if(target<=matrix[i][matrix[i].length-1]){
row=i;
break;
}
}
if(row==-1||target<matrix[row][0]){//target比所有行的最后一个值都大或者比该行最小的值小,肯定找不到
return false;
}
int left=0;
int right=matrix[row].length-1;
int mid=0;
while(left<=right){
mid=(left+right)/2;
if(matrix[row][mid]==target){
return true;
}
else if(matrix[row][mid]<target){
left=mid+1;
}
else{
right=mid-1;
}
}
return false;
}
}
在排序数组中查找元素的第一个和最后一个位置

解法
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] res = new int[]{-1, -1};
if (nums == null || nums.length == 0) {
return res;
}
// 查找第一个出现位置
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
//主要获取最左边的点就在这里,如果nums[mid]与target相等,缩小right的范围
//这样就把区间一直左移
right = mid - 1;
}
}
// 检查是否越界或值不匹配
if (left >= nums.length || nums[left] != target) {
return res;
}
res[0] = left;
// 查找最后一个出现位置
left = 0;
right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
//同理,这样会让区间右移
left = mid + 1;
} else {
right = mid - 1;
}
}
// 检查是否越界
if (right < 0 || nums[right] != target) {
return res;
}
res[1] = right;
return res;
}
}
搜索旋转排序数组(需要二刷)

解法
class Solution {
public int search(int[] nums, int target) {
int beg=0;
int end=nums.length-1;
while(beg<=end){
int mid=beg+(end-beg)/2;
if(target==nums[mid]){//找到就结束
return mid;
}
if(nums[beg]<=nums[mid]){//我们只考虑有序的区间(二分出来至多有一个区间是无序的)
//如果确定值在这个有序区间就是正常在这个区间二分查找
if(target<=nums[mid]&&target>=nums[beg]){
end=mid-1;
}
//如果这个有序区间不存在,那肯定就在另外一个区间中
else{
beg=mid+1;
}
}
else{
//同理
if(target>nums[mid]&&target<=nums[end]){
beg=mid+1;
}
else{
end=mid-1;
}
}
}
return -1;
}
}
寻找旋转排序数组中的最小值(需要二刷)

解法
class Solution {
public int findMin(int[] nums) {
int beg=0;
int end=nums.length-1;
int min=nums[nums.length-1];
if(nums[nums.length-1]>=nums[0]){
return nums[0];//说明没有旋转
}
while (beg < end) {
int mid = beg + (end - beg) / 2;
if (nums[mid] > nums[end]) { // 左半部分(旋转后的前半段)
beg = mid + 1;
}
else { // 右半部分(旋转后的后半段)
end = mid;
}
}
return nums[beg];
}
}
寻找两个正序数组的中位数(需要二刷)

解法

class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int[]res=nums1;
if(nums1.length>nums2.length){//确保nums1为长度最小的数组
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int m=nums1.length;
int n=nums2.length;
int left=0,right=m;//我们限定头部在较短的数组上,这样效率高点
while(left<=right){
int i=(left+right)/2;//头部的位置
int j=(m+n+1)/2-i;//尾部的位置,头和尾之间的长度固定是两个数组的长度一半
int left1;
if(i==0){
left1=Integer.MIN_VALUE;//以下置为最小值或者最大值的原因是防止越界,同时也是为了解决头尾在一个数组上的情况
}
else{
left1=nums1[i-1];//如上图num[i-1]即为头部的位置
}
int right1;
if(i==m){
right1=Integer.MAX_VALUE;
}
else{
right1=nums1[i];//头部的下一个元素
}
int left2;
if(j==0){
left2=Integer.MIN_VALUE;
}
else{
left2=nums2[j-1];//尾部的位置
}
int right2;
if(j==n){
right2=Integer.MAX_VALUE;
}
else{
right2=nums2[j];//尾部的下一个元素的位置
}
if(left1<=right2&&left2<=right1){//已经找到了中位数 即头部的元素大于尾部的元素同时小于尾部的下一个元素并且尾部的元素小于头部的下一个元素
if((m+n)%2==0){
return (Integer.max(left1,left2)+Integer.min(right1,right2))/2.0;
}
else{
return Integer.max(left1,left2)+0.0;//返回较大的那个
}
}
else if(left1>right2){//不满足条件同时头部的元素大于尾部的后一个元素,说明头部过大了,需要左移头部
right=i-1;
}
else{//右移头部
left=i+1;
}
}
return 0;
}
}
栈
有效的括号

解法
class Solution {
public boolean isValid(String s) {
Stack<Character> left = new Stack<>();
char c;
for (int i = 0; i < s.length(); i++) {
c = s.charAt(i);
if (c == '(' || c == '{' || c == '[') {//左括号随便入左栈
left.push(c);
}
else if (c == ')'){//右括号入右栈,左括号栈的栈顶必须是对应的左括号
if (!left.isEmpty()&&left.peek() == '(') {
left.pop();
}
else{
return false;
}
}
else if (c == '}'){
if (!left.isEmpty()&&left.peek() == '{') {
left.pop();
}
else{
return false;
}
}
else{
if (!left.isEmpty()&&left.peek() == '[') {
left.pop();
}
else{
return false;
}
}
}
if(left.isEmpty()){
return true;
}
else return false;
}
}
最小栈

解法
class MinStack {
Stack<Integer> stack;
Stack<Integer> minStack;
public MinStack() {
stack=new Stack<Integer>();//正常存放的栈
minStack = new Stack<Integer>();//存放当前栈中的最小值的栈
minStack.push(Integer.MAX_VALUE);//初始化栈底为最大元素
}
public void push(int val) {
stack.push(val);//正常入栈
if(val<minStack.peek()){//判断与当前栈中的最小值哪个小
minStack.push(val);//小的话入栈
}
else{
minStack.push(minStack.peek());//如果此时栈中依旧为最小的,重复入栈,这样做的好处是,即使弹出当前元素,最小栈不受影响
}
}
public void pop() {//同步弹出就好
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
字符串解码

class Solution {
public String decodeString(String s) {
Stack<Integer> num = new Stack<>();//存放左括号前的系数
Stack<String> value = new Stack<>();//存放值
StringBuilder sb = new StringBuilder();//一直维护这最后的结果
int i = 0;
while (i < s.length()) {
char a = s.charAt(i);
if (a == '[') {//碰到左括号说明要开始保存值了
value.push(sb.toString());
sb.setLength(0);
i++;
}
else if (Character.isDigit(a)) {//保存[前的系数
int x = a - '0';
i++;
while (i < s.length() && Character.isDigit(s.charAt(i))) {
x = x * 10 + (s.charAt(i) - '0');
i++;
}
num.push(x); // 直接压入数字
}
else if (a == ']') {//碰到右括号说明需要弹出来了
int x = num.isEmpty() ? 1 : num.pop(); // 默认倍数为1
String res = sb.toString();
sb.setLength(0);
for (int j = 0; j < x; j++) {//按照倍数扩大
sb.append(res);
}
if (!value.isEmpty()) {//value非空说明前面是字符串也就是aa2[ab]的这种情况
sb.insert(0, value.pop());
}
i++;
}
else {//就是正常的字符 加上去就行
sb.append(a);
i++;
}
}
return sb.toString();
}
}
每日温度

class Solution {
public int[] dailyTemperatures(int[] temperatures) {
Deque<Integer>xu=new LinkedList<Integer>();//用这个比Stack快,建议所有的stack都用这个 原因在于stack是基于vector的,都被syn包了,导致性能差但是安全
int n = temperatures.length;
int[]res=new int[n];
for(int i=0;i<n;i++){
while(!xu.isEmpty()&&temperatures[i]>temperatures[xu.peek()]){//对于栈顶元素而言 此时就是升高了
int num=xu.pop();//弹出第几天
res[num]=i-num;
}
xu.push(i);
}
return res;
}
}
柱状图中的最大的矩形
解法


class Solution {
public int largestRectangleArea(int[] heights) {
int[]left=new int[heights.length];//记录以heights[i]为高度的矩形左起点
int[]right=new int[heights.length];//记录以heights[i]为高度的矩形右终点
Deque<Integer>stack=new LinkedList<>();
for(int i=0;i<left.length;i++){
left[i]=-1;//初始化的原因见以下公式
right[i]=heights.length;
}
for(int i=0;i<heights.length;i++){
while(!stack.isEmpty()&&heights[i]<heights[stack.peek()]){//出现了比栈顶元素低的高,需要一直循环到比新来的高或者相等的位置
right[stack.peek()]=i;//设置为右终点
stack.pop();//弹出
}
if(!stack.isEmpty()){//说明栈中还存在比这个点高或者相等的高度
left[i]=stack.peek();//现在的元素的左起点找到了
}
stack.push(i);
}
int max=0;
for(int i=0;i<heights.length;i++){
max=Math.max(max,(right[i]-left[i]-1)*heights[i]);//计算
}
return max;
}
}
堆
数组中的第K个最大元素

解法
class Solution {
//快排思想
int quickselect(int[] nums, int l, int r, int k) {
if (l == r) return nums[k];
int x = nums[l], i = l - 1, j = r + 1;
while (i < j) {
do i++; while (nums[i] < x);
do j--; while (nums[j] > x);
if (i < j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
if (k <= j) return quickselect(nums, l, j, k);
else return quickselect(nums, j + 1, r, k);
}
public int findKthLargest(int[] _nums, int k) {
int n = _nums.length;
return quickselect(_nums, 0, n - 1, n - k);
//我们取一个值,将大的放在该值后面 小的放在该值前面
//最后这个值所在的位置m,如果为k说明他就是第k大的元素
//如果这个值所在的位置小于n-k,说明为第k大的元素在他的后面
//如果这个值所在的位置大于k,说明为第k大的元素在他的前面
}
}
前K个高频元素

解法
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
// int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] m, int[] n) {
return m[1] - n[1];
}
});
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
if (queue.size() == k) {
if (queue.peek()[1] < count) {
queue.poll();
queue.offer(new int[]{num, count});
}
} else {
queue.offer(new int[]{num, count});
}
}
int[] ret = new int[k];
for (int i = 0; i < k; ++i) {
ret[i] = queue.poll()[0];
}
return ret;
}
}
数据流的中位数

解法
class MedianFinder {
PriorityQueue<Integer> queMin;
PriorityQueue<Integer> queMax;
public MedianFinder() {
queMin = new PriorityQueue<Integer>((a, b) -> (b - a));//小于等于中位数的数
queMax = new PriorityQueue<Integer>((a, b) -> (a - b));//大于中位数的数
}
public void addNum(int num) {
if (queMin.isEmpty() || num <= queMin.peek()) {
queMin.offer(num);
if (queMax.size() + 1 < queMin.size()) {//如果大于中位数的数量比于等于中位数的数还小 就移动
queMax.offer(queMin.poll());
}
}
else {
queMax.offer(num);
if (queMax.size() > queMin.size()) {
queMin.offer(queMax.poll());
}
}
}
public double findMedian() {
if (queMin.size() > queMax.size()) {
return queMin.peek();
}
return (queMin.peek() + queMax.peek()) / 2.0;
}
}
贪心算法
买卖股票的最佳时机

解法
class Solution {
public int maxProfit(int[] prices) {
int max,min,i,j;
min=prices[prices.length-1];//最后一个元素
max=0;
for(i=prices.length-2;i>=0;i--){//从后向前遍历
if(prices[i]>min){//如果当前比后面的高,就更新
min=prices[i];
}else{
if(max<min-prices[i]){//判断能不能卖出
max=min-prices[i];
}
}
}
return max;
}
}
跳跃游戏

解法
class Solution {
public boolean canJump(int[] nums) {
int i,j,end;
end=nums.length-1;
for(i=nums.length-2;i>=0;i--){
//从后向前不断更新end点,这个点表示可以跳出去的点或者能到终点的前n个点
if(i+nums[i]>=end){
end=i;
}
}
if(end==0){
return true;
}
else{
return false;
}
}
}
跳跃游戏 II

解法
class Solution {
public int jump(int[] nums) {
int i,x,min,max,maxs;
max=0;
min=0;
maxs=0;
for(i=0;i<nums.length-1;i++){
x=nums[i]+i;//当前节点最多能跳到哪里
if(x>max){
//如果这个能跳的最远比目前能挑到的最远远就更新
max=x;
}
if(maxs==i){//达到边界,说明前面最远到这就是目前步数+1
maxs=max;//更新新的边界值
min++;
}
}
return min;
}
}
划分字母区间

解法
class Solution {
public List<Integer> partitionLabels(String s) {
int[]map=new int[26];
for(int i=0;i<s.length();i++){
map[s.charAt(i)-'a']++;
}
List<Integer>res=new ArrayList<>();
int[]find=new int[26];
int sum=0;//记录子串中不同字符的个数
int total=0;//记录已经子串中出现字符已经出现完的个数
StringBuffer sb=new StringBuffer();
for(int i=0;i<s.length();i++){
char a=s.charAt(i);//获取当前遍历的字符
sb.append(a);
if(find[a-'a']==0){//说明这是子串中新出现的字符,sum++
sum++;
}
find[a-'a']++;//对应hash增加
if(find[a-'a']==map[a-'a']){//说明这个字符已经出现完了
total++;//子串中完成出现的加1
}
if(total==sum){//可以划分了 说明所有子串中的不重复的的字符后面不会有了
res.add(sb.length());
total=0;
sum=0;
sb.setLength(0);
}
}
return res;
}
}
动态规划
走楼梯

解法
class Solution {
public int climbStairs(int n) {
int[]dp=new int[1001];
return find(dp,n);
}
public int find(int[] dp,int n){
if(n==1){
return 1;
}
if(n==0){
return 1;
}
if(dp[n]!=0){
return dp[n];
}
dp[n]=find(dp,n-1)+find(dp,n-2);
return dp[n];
}
}
杨辉三角

解法
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>>res=new ArrayList<>();
for(int i=0;i<numRows;i++){
List<Integer>tes=new ArrayList<Integer>();
tes.add(1);
res.add(tes);
}
for(int i=1;i<numRows;i++){
for(int j=1;j<=i;j++){
if(j==i){
res.get(i).add(1);
}
else{
int a=res.get(i-1).get(j-1);
int b=res.get(i-1).get(j);
res.get(i).add(a+b);
}
}
}
return res;
}
}
打家劫舍

解法
class Solution {
public int rob(int[] nums) {
if(nums.length==0){
return 0;
}
int[] dp=new int[nums.length];//到i位置能偷到的最多钱
if(nums.length==1){
return nums[0];
}
if(nums.length==2){
return Math.max(nums[0],nums[1]);//只有两个的话就选最大的就行
}
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<nums.length;i++){
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);//转移公式
}
return dp[nums.length-1];
}
}
完全平方数

解法
class Solution {
public int numSquares(int n) {
int[] dp=new int[n+1];
for(int i=0;i<=n;i++){
dp[i]=i;
for(int j=0;j*j<=i;j++){
dp[i]=Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
}
零钱兑换

解法
class Solution {
public int coinChange(int[] coins, int amount) {
int[] f = new int[amount + 1];
Arrays.fill(f, Integer.MAX_VALUE / 2);
f[0] = 0;
for(int i = 0; i < coins.length; i++){
for(int j = coins[i]; j <= amount; j++){
f[j] = Math.min(f[j], f[j - coins[i]] + 1);
}
}
return f[amount] < Integer.MAX_VALUE / 2 ? f[amount] : -1;
}
}
单词拆分

解法
public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];//[1,i]能否被worddict表示出来
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {//终点
for (int j = 0; j < i; j++) {//起点
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
//前[1,j]的字符串可以被表示出来同时划分出来的区间能被表示
dp[i] = true;//说明[1,i]可以被表示
break;
}
}
}
return dp[s.length()];
}
}
最长递增子序列

解法
class Solution {
public int lengthOfLIS(int[] nums) {
// res数组定义:res[i] 表示「以nums[i]作为起始元素」的最长递增子序列的长度
int[]res=new int[nums.length];
// 初始化最后一个元素:以最后一个元素开头的递增子序列只有它自己,长度固定为1
res[nums.length-1]=1;
int max=1;
// 从倒数第二个元素开始,从后往前遍历每个元素i(保证计算i时,i之后的res[j]已确定)
for(int i=nums.length-2;i>=0;i--){
for(int j=i+1;j<nums.length;j++){
// 如果nums[j] > nums[i],说明nums[i]可以接在nums[j]的递增子序列前面
if(nums[j]>nums[i]){
if(res[i]<res[j]+1){
// res[j]+1 含义:以nums[j]开头的长度 + nums[i]自身(作为新的起始元素)
res[i]=res[j]+1;
}
}
}
if(res[i]==0){
res[i]=1;
}
if(res[i]>max){
max=res[i];
}
}
return max;
}
}
乘积最大子数组

解法
class Solution {
public int maxProduct(int[] nums) {
if(nums.length==0){
return 0;
}
int[]max=new int[nums.length];//记录到i为止最大的乘积
int[]min=new int[nums.length];//记录到i为止最小的乘积
for(int i=0;i<nums.length;i++){
max[i]=nums[i];
min[i]=nums[i];
}
for(int i=1;i<nums.length;i++){
max[i]=Math.max(max[i-1]*nums[i],Math.max(nums[i],min[i-1]*nums[i]));//考虑负负得正的情况
min[i]=Math.min(min[i-1]*nums[i],Math.min(nums[i],max[i-1]*nums[i]));
}
int ans=max[0];
for (int i = 1; i <nums.length; ++i) {
ans = Math.max(ans, max[i]);
}
return ans;
}
}
分割等和子集

解法
class Solution {
public boolean canPartition(int[] nums) { // 判断是否能分割成两个和相等的子集
int sum = 0; // sum 用来统计数组总和
int n = nums.length; // n 是数组长度
for (int num : nums) // 遍历每个元素 num
sum = sum + num; // 把 num 加到总和 sum 上
if (sum % 2 == 1) // 如果总和是奇数
return false; // 不可能分成两个相等的整数和子集
int target = sum / 2; // 目标:找到子集和为 target
int[] dp = new int[target + 1]; // dp[j] 表示:容量为 j 时能凑出的"最大子集和"
for (int i = 0; i < n; i++) // 枚举第 i 个数(每个数只能用一次)
for (int j = target; j >= nums[i]; j--) { // 0/1 背包:j 必须倒序,防止同一个 nums[i] 被用多次
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); // 不选 nums[i] vs 选 nums[i](选则和增加 nums[i])
} // 内层循环结束(处理完"使用 nums[i]"对 dp 的影响)
return dp[target] == target; // 如果恰好能凑到 target,则可以等分;否则不行
}
}
最长有效括号

解法
class Solution {
public int longestValidParentheses(String s) {
int left = 0, right = 0, maxlength = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * right);
} else if (right > left) {//右括号过多清空 防止出现)(的情况
left = right = 0;
}
}
//无法处理「左括号过多」的情况(比如"(()")------ 遍历结束时left=2、right=1,永远不会触发left==right,导致漏判有效子串
//所有从右向左再来一次
left = right = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * left);
} else if (left > right) {
left = right = 0;
}
}
return maxlength;
}
}
多维动态规划
不同路径

解法
class Solution {//采取了压缩dp的方式
public int uniquePaths(int m, int n) {
int[] f = new int[n];//初始化一维DP数组:f[j] 表示到达当前行第j列位置的路径数
for (int i = 0; i < n; ++i) {
//初始化第一行:从左上角到第一行任意位置只有1条路径(只能一直向右走)
f[i] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
//原二维DP:dp[i][j] =dp[i-1][j](上方来的路径数)+ dp[i][j-1](左方来的路径数)
//压缩后:f[j]==dp[i-1][j] f[j-1]==dp[i][j-1]
f[j] += f[j - 1];
}
}
return f[n - 1];
}
}
最小路径和

解法
class Solution {
public int minPathSum(int[][] grid) {
int m=grid.length;
int n=grid[0].length;
if(m==1 && n==1){
return grid[0][0];
}
//初始化第一行,到达grid[0][i]只能从左向右过去
if(n>1){
for(int i=1;i<n;i++){
grid[0][i]+=grid[0][i-1];
}
}
////初始化第一列,到达grid[i][0]只能从上向下过去
if(m>1){
for(int i=1;i<m;i++){
grid[i][0]+=grid[i-1][0];
}
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
//到达一个grid[i][j],要么从上要么从左,这个点的值就记录最小值就行
grid[i][j]=Math.min(grid[i][j]+grid[i-1][j],grid[i][j-1]+grid[i][j]);
}
}
return grid[m-1][n-1];
}
}
最长回文子串

解法
class Solution {//中心法就行
public String longestPalindrome(String s) {
int max=1;
int beg=0;
int left,right;
int n=s.length();
for(int i=0;i<n;i++){//依次让每个元素当中心元素
//aba奇数的情况
left=i-1;//left指向左边第一个
right=i+1;//right指向右边第一个
while(left>=0&&right<n&&s.charAt(left)==s.charAt(right)){
if(max<right-left+1){
max=right-left+1;
beg=left;
}
left--;
right++;
}
//baab的偶数情况
left=i;//元素自身作为左边那个
right=i+1;//元素下一个作为右边那个
while(left>=0&&right<n&&s.charAt(left)==s.charAt(right)){
if(max<right-left+1){
max=right-left+1;
beg=left;
}
left--;
right++;
}
}
return s.substring(beg,beg+max);
}
}
最长公共子序列

解法
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
char[] t = text2.toCharArray();
int m = t.length;
//f[i] 表示text1遍历到当前字符时,text2前i个字符的最长公共子序列
int[] f = new int[m + 1];
for (char x : text1.toCharArray()) {
int pre = 0; //以当前text1的x为起点,初始化序列长为0
for (int j = 0; j < m; j++) {
//初始为0,对应二维DP中dp[i-1][0](空字符串的LCS长度)
int tmp = f[j + 1];
if(x == t[j]){//如果说和这个字符相等
//等价于二维DP:dp[i][j+1] = dp[i-1][j] + 1
f[j + 1] = pre + 1;
}
else{//字符不相等时
//等价于二维DP:dp[i][j+1] = max(dp[i-1][j+1], dp[i][j])
f[j + 1] = Math.max(f[j + 1], f[j]);
}
// 更新pre为当前轮的tmp,作为下一次循环的「左上角值」(即下一个j对应的dp[i-1][j])
pre = tmp;
}
}
return f[m];
}
}
编辑距离

解法
class Solution {
public int minDistance(String word1, String word2) {
int n,m;
n=word1.length();
m=word2.length();
// 二维DP数组定义:dp[i][j] 表示「word1前i个字符」转换为「word2前j个字符」的最少操作数
int[][]dp=new int[n+1][m+1];
// 初始化第一行(i=0):word1为空字符串,转成word2前j个字符需要「插入j次」
for(int i=0;i<=m;i++){
dp[0][i]=i;
}
// 初始化第一列(j=0):word2为空字符串,word1前i个字符转成空需要「删除i次」
for(int j=0;j<=n;j++){
dp[j][0]=j;
}
for(int i=1;i<n+1;i++){
for(int j=1;j<m+1;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
//相等的话
// dp[i-1][j]+1:删除word1的第i个字符(操作数+1)
// dp[i][j-1]+1:在word1插入word2的第j个字符(操作数+1)
dp[i][j]=Math.min(dp[i-1][j]+1,dp[i][j-1]+1);
// dp[i-1][j-1]:无操作(字符匹配,无需额外操作),若更小则更新
if(dp[i-1][j-1]<dp[i][j]){
dp[i][j]=dp[i-1][j-1];
}
}
else{
// 字符不匹配:需比较「删除」「插入」「替换」三种情况的最小值
// dp[i-1][j]+1:删除word1的第i个字符
// dp[i][j-1]+1:在word1插入word2的第j个字符
dp[i][j]=Math.min(dp[i-1][j]+1,dp[i][j-1]+1);
// dp[i-1][j-1]+1:将word1的第i个字符替换为word2的第j个字符(操作数+1)
if(dp[i-1][j-1]+1<dp[i][j]){
dp[i][j]=dp[i-1][j-1]+1;
}
}
}
}
return dp[n][m];
}
}
技巧
只出现一次的数字

解法:两两异或,相同的会变成0,剩下的就是出现一次的
class Solution {
public int singleNumber(int[] nums) {
int x=nums[0];
if(nums.length==0){
return x;
}
for(int i=1;i<nums.length;i++){
x=x^nums[i];
}
return x;
}
}
多数元素

解法
class Solution {
public int majorityElement(int[] nums) {//直接找众数
int ms=nums[0];//选定一个值
int count=1;
for(int i=1;i<nums.length;i++){
if(nums[i]==ms){//多出现一次,次数加1
count++;
}
else{//没出现就减1
count--;
if(count==0){
ms=nums[i];
count=1;
}
}
}
return ms;//由于多数元素出现大于n/2次,肯定count不会被减到0
}
}
颜色分类

解法
class Solution {
public void sortColors(int[] nums) {
int len=nums.length;
if(len<2){
return;
}
int beg=0;//作为0的终点
int end=len-1;//作为2的起点
int i=0;
while(i<=end){
if(nums[i]==0){/如果说这个点的值为0
swap(nums,beg,i);//交换该点和beg对应的值
beg++;//终点后移一位
i++;
//此时交换完可以自增的原因是
//首先终点点那里换过去的是0
//换过来的值必然是在前面,有可能是1 但不可能是2
}
else if(nums[i]==1){//1就不管
i++;
}
else{//2
swap(nums,i,end);//交换该点和2的起点的位置
end--;
//起点前移 此时不自增i的原因是,换过来的这个点可能为0,他可能需要交换
}
}
}
public void swap(int[]nums,int i,int j){
int tmp=nums[i];
nums[i]=nums[j];
nums[j]=tmp;
}
}
下一个排列

解法
class Solution {
public void nextPermutation(int[] nums) {
int len=nums.length;
for(int i=len-1;i>=1;i--){
if(nums[i-1]<nums[i]){//找到第一个降序
//i到len-1是从大到小的,需要反转成从小到大的
reverse(nums,i,len-1);
for(int j=i;j<len;j++){
//从i到n-1中找到第一个大于i-1的
if(nums[j]>nums[i-1]){
swap(nums,j,i-1);
break;
}
}
break;
}
if(i==1){//说明需要重置为初始状态 5 4 3 2 1这种
reverse(nums,0,len-1);
}
}
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
public void reverse(int[] nums, int left, int right) {//反转数组的一个区间
while (left < right) {
swap(nums, left++, right--);
}
}
}
寻找重复数

解法 :
快慢指针的做法,根据条件,元素的大小是不超过数组的长度的,那么也就会,出现重复数意味着出现环 ,那么就是采取回环链表的做法

class Solution {
public int findDuplicate(int[] nums) {
int slow=0;
int fast=0;
while(true){
slow=nums[slow];
fast=nums[nums[fast]];
if(fast==slow){
break;
}
}
fast=0;
while(true){
slow=nums[slow];
fast=nums[fast];
if(slow==fast){
return slow;
}
}
}
}