目录
先到这里吧,后面就好多是没学过的知识(比如前缀树),有时间精力再做吧
二维数组中的查找
- 方法1:Z字形查找
class Solution { public boolean findNumberIn2DArray(int[][] matrix, int target) { if(matrix.length==0 || matrix[0].length==0)return false; int n=0; int m=matrix[0].length-1; while(n<matrix.length && m>=0){ if(target>matrix[n][m]){ n++; } else if(target<matrix[n][m]){ m--; } else{ return true; } } return false; } }
重建二叉树
- 方法1:分治算法
我的代码过程展示:
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { int[] preorder; HashMap<Integer,Integer> map=new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { this.preorder=preorder; if(preorder.length==0)return null; for(int i=0;i<inorder.length;i++){ map.put(inorder[i],i); } return buildTree2(0,0,inorder.length-1); } public TreeNode buildTree2(int root,int left,int right){ if(left>right)return null; TreeNode node=new TreeNode(preorder[root]); int i=map.get(preorder[root]); node.left=buildTree2(root+1,left,i-1); node.right=buildTree2(i-left+root+1,i+1,right); return node; } }
矩阵中的路径
- 方法1:深度优先搜索(DFS)+剪枝
class Solution { public boolean exist(char[][] board, String word) { char[] words=word.toCharArray(); for(int i=0;i<board.length;i++){ for(int j=0;j<board[0].length;j++){ if(dfs(board,words,i,j,0))return true; } } return false; } public boolean dfs(char[][]board,char[] words,int i,int j,int k){ //这个是终止条件(边界) if(i<0 || i>=board.length || j<0 || j>=board[0].length || board[i][j]!=words[k])return false; //如果k已经是单词的长度,那么说明单词存在于网格中 if(k==words.length-1)return true; //给他做上标记,此坐标中的字母已经使用过了 board[i][j]='0'; //找下一个字母 boolean res=dfs(board,words,i-1,j,k+1)||dfs(board,words,i+1,j,k+1)||dfs(board,words,i,j-1,k+1)||dfs(board,words,i,j+1,k+1); //到这一步就已经是递归完成了,因此我们需要把网格中的字母还原,以备后面查找使用 board[i][j]=words[k]; return res; } }
剪绳子
方法1:动态规划
思路
class Solution { public int cuttingRope(int n) { int[] dp=new int[n+1]; dp[2]=1; for(int i=3;i<n+1;i++){ for(int j=2;j<i;j++){ dp[i]=Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j])); } } return dp[n]; } }
方法2:贪心算法
class Solution { public int cuttingRope(int n) { if(n < 4){ return n - 1; } int res = 1; while(n > 4){ res *= 3; n -= 3; } return res * n; } }
剪绳子②
- 方法1:贪心算法
class Solution { public int cuttingRope(int n) { if(n<4){ return n-1; } long res=1; while(n>4){ res=res*3%1000000007; n-=3; } return (int)(res*n%1000000007); } }
数值的整数次方
- 方法1:快速幂(使用二分法+二进制方法实现)
class Solution { public double myPow(double x, int n) { if(x==0)return 0; if(n==0)return 1; long b=n; double res=1.0; int zhengfu=1; if(b<0){ zhengfu*=-1; b=-b; } while(b>0){ if((b&1)==1){ res*=x; } x*=x; b>>=1; } if(zhengfu==-1){ res=1/res; } return res; } }
表示数值的字符串
class Solution { public boolean isNumber(String s) { //去掉首位空格 s = s.trim(); //是否出现数字 boolean numFlag = false; //是否出现小数点 boolean dotFlag = false; boolean eFlag = false; for (int i = 0; i < s.length(); i++) { //判定为数字,则标记numFlag if (s.charAt(i) >= '0' && s.charAt(i) <= '9') { numFlag = true; //小数点只可以出现再e之前,且只能出现一次.num num.num num.都是被允许的 } else if (s.charAt(i) == '.' && !dotFlag && !eFlag) { dotFlag = true; //判定为e,需要没出现过e,并且出过数字了 } else if ((s.charAt(i) == 'e' || s.charAt(i) == 'E') && !eFlag && numFlag) { eFlag = true; //避免e以后没有出现数字 numFlag = false; //判定为+-符号,只能出现在第一位或者紧接e后面 } else if ((s.charAt(i) == '+' || s.charAt(i) == '-') && (i == 0 || s.charAt(i - 1) == 'e' || s.charAt(i - 1) == 'E')) { //其他情况,都是非法的 } else { return false; } } //是否出现了数字 return numFlag; } }
树的子结构
- 方法1:先序遍历
class Solution { public boolean isSubStructure(TreeNode A, TreeNode B) { return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B)); } boolean recur(TreeNode A, TreeNode B) { if(B == null) return true; if(A == null || A.val != B.val) return false; return recur(A.left, B.left) && recur(A.right, B.right); } }
栈的压入、弹出序列
- 方法1:模拟栈
class Solution { public boolean validateStackSequences(int[] pushed, int[] popped) { Stack<Integer> stack=new Stack<>(); int i=0; for(int num:pushed){ stack.push(num); while(!stack.isEmpty() && stack.peek()==popped[i]){ stack.pop(); i++; } } return stack.isEmpty(); } }
从上到下打印二叉树①
- 方法1:借助队列实现
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public int[] levelOrder(TreeNode root) { if(root==null)return new int[0]; Queue<TreeNode> queue=new LinkedList<>(); queue.offer(root); List<Integer> res=new ArrayList<>(); while(!queue.isEmpty()){ TreeNode tmp=queue.poll(); res.add(tmp.val); if(tmp.left!=null){ queue.offer(tmp.left); } if(tmp.right!=null){ queue.offer(tmp.right); } } int[] arr=new int[res.size()]; for(int i=0;i<arr.length;i++){ arr[i]=res.get(i); } return arr; } }
从上到下打印二叉树③
剑指 Offer 32 - III. 从上到下打印二叉树 III
- 方法1:借助双端队列实现
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res=new ArrayList<>(); Deque<TreeNode> de=new LinkedList<>(); if(root!=null)de.offer(root); while(!de.isEmpty()){ List<Integer> list=new ArrayList<>(); for(int i=de.size();i>0;i--){ TreeNode tmp=de.pollFirst(); list.add(tmp.val); if(tmp.left!=null)de.offerLast(tmp.left); if(tmp.right!=null)de.offerLast(tmp.right); } res.add(list); if(de.isEmpty())break; list=new ArrayList<>(); for(int i=de.size();i>0;i--){ TreeNode tmp=de.pollLast(); list.add(tmp.val); if(tmp.right!=null)de.offerFirst(tmp.right); if(tmp.left!=null)de.offerFirst(tmp.left); } res.add(list); } return res; } }
二叉搜索树的后序遍历序列
- 方法1:递归分治
class Solution { public boolean verifyPostorder(int[] postorder) { if(postorder.length==0)return true; return postOrder(postorder,0,postorder.length-1); } public boolean postOrder(int[] postorder,int start,int end){ if(start>=end)return true; int i=start; for(;i<end;i++){ if(postorder[i]>postorder[end]){ break; } } for(int j=i;j<end;j++){ if(postorder[j]<postorder[end]){ return false; } } return postOrder(postorder,start,i-1) && postOrder(postorder,i,end-1); } }
二叉树中和为某一值的路径
- 方法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 { List<List<Integer>> res=new ArrayList<>(); List<Integer> list=new ArrayList<>(); public List<List<Integer>> pathSum(TreeNode root, int target) { return dfs(root,target); } public void dfs(TreeNode root,int target){ if(root==null)return; target-=root.val; list.add(root.val); if(target==0 && root.left==null && root.right==null){ res.add(list); } dfs(root.left,target); dfs(root.right,target); list.remove(list.size()-1); } }
复杂链表的复制
- 方法1:哈希表
/* // 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) { if(head==null)return null; Map<Node,Node> map=new HashMap<>(); Node cur=head; while(cur!=null){ map.put(cur,new Node(cur.val)); cur=cur.next; } cur=head; while(cur!=null){ map.get(cur).next=map.get(cur.next); map.get(cur).random=map.get(cur.random); cur=cur.next; } return map.get(head); } }
二叉搜索树与双向链表
- 方法1:中序遍历
自己写出来的!!!
/* // Definition for a Node. class Node { public int val; public Node left; public Node right; public Node() {} public Node(int _val) { val = _val; } public Node(int _val,Node _left,Node _right) { val = _val; left = _left; right = _right; } }; */ class Solution { Node tmp=new Node(-1); Node pre=tmp; public Node treeToDoublyList(Node root) { if(root==null)return null; Node cur=root; inOrder(cur); tmp.right.left=pre; pre.right=tmp.right; return tmp.right; } public void inOrder(Node cur){ if(cur==null)return; inOrder(cur.left); pre.right=cur; cur.left=pre; pre=cur; inOrder(cur.right); } }
字符串的排列
- 方法1:深度优先搜索(dfs)
class Solution { List<String> res=new ArrayList<>(); char[] c; public String[] permutation(String s) { c=s.toCharArray(); dfs(0); return res.toArray(new String[res.size()]); } public void dfs(int x){ if(x==c.length-1){ res.add(String.valueOf(c)); } Set<Character> set=new HashSet<>(); for(int i=x;i<c.length;i++){ if(set.contains(c[i]))continue; set.add(c[i]); swap(i,x); dfs(x+1); swap(i,x); } } public void swap(int i,int x){ char tmp=c[i]; c[i]=c[x]; c[x]=tmp; } }
数字序列中某一位的数字
- 方法1:思路
目前做过可能两遍了,但是靠自己想只能想出一半,还有不懂的地方,下次做的时候看看能不能想通:
1.题目说从下标0开始计数,这个条件在代码的哪里应用了?
2.关于算num和算最终返回值的思路
class Solution { public int findNthDigit(int n) { int weishu=1; long start=1; long count=9; while(n>count){ n-=count; weishu+=1; start*=10; count=weishu*start*9; } long num=start+(n-1)/weishu; return Long.toString(num).charAt((n-1)%weishu)-'0'; } }
把数字翻译成字符串
- 方法1:动归(通过字符串遍历的方式实现动归)
class Solution { public int translateNum(int num) { String s=String.valueOf(num); int a=1; int b=1; for(int i=2;i<=s.length();i++){ String tmp=s.substring(i-2,i); int c=tmp.compareTo("10")>=0 && tmp.compareTo("25")<=0?a+b:a; b=a; a=c; } return a; } }
礼物的最大价值
- 方法1:动态规划
class Solution { public int maxValue(int[][] grid) { int[][] dp=new int[grid.length][grid[0].length]; dp[0][0]=grid[0][0]; for(int i=1;i<grid.length;i++){ dp[i][0]=dp[i-1][0]+grid[i][0]; } for(int j=1;j<grid[0].length;j++){ dp[0][j]=dp[0][j-1]+grid[0][j]; } for(int i=1;i<grid.length;i++){ for(int j=1;j<grid[0].length;j++){ dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])+grid[i][j]; } } return dp[grid.length-1][grid[0].length-1]; } }
最长不含重复字符的子字符串
- 方法1:动态规划+哈希表
这道题下次还得再做啊
class Solution { public int lengthOfLongestSubstring(String s) { int tmp=0; int res=0; Map<Character,Integer> map=new HashMap<>(); for(int j=0;j<s.length();j++){ int i=map.getOrDefault(s.charAt(j),-1); map.put(s.charAt(j),j); tmp=tmp<j-i?tmp+1:j-i; res=Math.max(res,tmp); } return res; } }
丑数
- 方法1:动态规划
class Solution { public int nthUglyNumber(int n) { int[] dp=new int[n]; dp[0]=1; int a=0; int b=0; int c=0; for(int i=1;i<dp.length;i++){ dp[i]=Math.min(Math.min(dp[a]*2,dp[b]*3),dp[c]*5); if(dp[a]*2==dp[i])a++; if(dp[b]*3==dp[i])b++; if(dp[c]*5==dp[i])c++; } return dp[dp.length-1]; } }
数组中数字出现的次数
一开始我想的是用HashMap,虽然能通过,但是题目要求时间复杂度是O(n),空间复杂度是O(1),如果使用HashMap空间复杂度就超了
所以看了大佬的思路,用下面这种方法做的
- 方法1:位运算
class Solution { public int[] singleNumbers(int[] nums) { int x=0;//x为其中一个只出现一次的数字 int y=0;//y为另外一个只出现一次的数字 int n=0;//n为遍历数组各数字异或运算的结果 int m=1;//m为x^y二进制位首位出现1时的对应的数字 for(int num:nums){ n^=num; } while((n & m)==0){ m<<=1; } for(int num:nums){ if((num & m)==0){ x^=num; } } return new int[]{x,x^n}; } }
数组中数字出现的次数②
剑指 Offer 56 - II. 数组中数字出现的次数 II
- 方法1:位运算(利用遍历统计法实现)
class Solution { public int singleNumber(int[] nums) { int[] counts=new int[32]; for(int num:nums){ for(int j=0;j<counts.length;j++){ counts[j]+=num&1; num>>=1; } } int res=0; for(int i=0;i<counts.length;i++){ res<<=1; res|=counts[31-i]%3; } return res; } }
写的过程中这里大意写错了,报了这样的错误,就去了解下这个错误啥意思
n个骰子的点数
- 方法1:动态规划
这道题我真的还是没看懂,也就是刚看完题解自己依旧做不出来的程度,所以这里不放代码提醒自己这道题相当于还没做,大家可以直接看上面的思路,思路里有大佬写的代码哦
股票的最大利润
- 方法1:动态规划
class Solution { public int maxProfit(int[] prices) { int cost=Integer.MAX_VALUE;//cost将来用来存放当前股票的最小价格 int profit=0;//profit将来用来存放当前最大利润 for(int price:prices){ cost=Math.min(cost,price); profit=Math.max(profit,price-cost); } return profit; } }
求1+2+...+n
- 方法1:逻辑符短路
class Solution { public int sumNums(int n) { boolean x=n>1 && (n+=sumNums(n-1))>0; return n; } }
我一开始想的是二分法+位运算,但是失败了,然后去看题解看有没有和自己想法一样的,位运算解法之类的,官方题解里到时有位运算(快速乘)的,但是...emmmm,算了还是先放弃位运算的方法吧
构建乘积数组
- 方法1:动态规划
我先看的这个思路,但代码没有按这个大佬的写,而是看的下面评论区的另一位大佬用动态规划的思路写的
class Solution { public int[] constructArr(int[] a) { if(a==null || a.length==0)return new int[0]; int len=a.length; int[] leftOfI=new int[len]; int[] rightOfI=new int[len]; leftOfI[0]=1; rightOfI[len-1]=1; for(int i=1;i<len;i++){ leftOfI[i]=leftOfI[i-1]*a[i-1]; } for(int i=len-2;i>=0;i--){ rightOfI[i]=rightOfI[i+1]*a[i+1]; } int[] res=new int[len]; for(int i=0;i<len;i++){ res[i]=leftOfI[i]*rightOfI[i]; } return res; } }
只出现一次的数字
这道题和之前做的这道题题目不一样,提示不一样,但做法代码一模一样,你会数组中数字出现的次数②这道题,那么此题你也就会,不过我把这道题还是贴在这儿了,大家可以找找不同,同时要建立一个警报系统,对于做过的题要有条件反射,拥有这种意识和能力,这对未来大家面试做没做过或相似的题的时候有帮助~大家想看此题代码,去数组中数字出现的次数②这道题中看代码就行(就在这篇博客里),我这里就不放了
单词长度的最大乘积
- 方法1:位运算模拟
这道题我最终搞懂是靠的这三位大佬
首先我先看的大佬1思路,然后对于或运算和左移那一行代码不是很明白,因此我就又看了大佬2的图以及大佬3的视频讲解 最终明白了或运算和左移那一行代码的意思,最终自己了一遍代码(思路还是大佬1的)
class Solution { public int maxProduct(String[] words) { int[] word=new int[words.length]; int index=0; for(String w:words){ int res=0; for(int i=0;i<w.length();i++){ int tmp=w.charAt(i)-'a'; res|=(1<<tmp); } word[index++]=res; } int max=0; for(int i=0;i<word.length;i++){ for(int j=0;j<i;j++){ if((word[i]&word[j])==0){ max=Math.max(max,words[i].length()*words[j].length()); } } } return max; } }
数组中和为0的三个数
方法1:排序+双指针
使用HashSet进行去重
class Solution { public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); Set<List<Integer>> set=new HashSet<>(); for(int i=0;i<nums.length-2;i++){ if(i>0 && nums[i]==nums[i-1])continue; int target=-nums[i]; int left=i+1; int right=nums.length-1; while(left<right){ int sum=nums[left]+nums[right]; if(sum==target){ set.add(Arrays.asList(nums[i],nums[left],nums[right])); left++; right--; } else if(sum>target){ right--; } else{ left++; } } } return new ArrayList<>(set); } }
优化:(是上面思路里面评论区赵四儿大佬的优化思路)
class Solution { public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); List<List<Integer>> res=new ArrayList<>(); for(int i=0;i<nums.length-2;i++){ if(nums[i]>0)break; if(i>0 && nums[i]==nums[i-1])continue; int target=-nums[i]; int left=i+1; int right=nums.length-1; while(left<right){ int sum=nums[left]+nums[right]; if(sum==target){ res.add(Arrays.asList(nums[i],nums[left],nums[right])); left++; right--; while(left<right && nums[left]==nums[left-1]){ left++; } while(left<right && nums[right]==nums[right+1]){ right--; } } else if(sum<target){ left++; } else{ right--; } } } return res; } }
和大于等于target的最短子数组
剑指 Offer II 008. 和大于等于 target 的最短子数组
- 方法1:滑动窗口
class Solution { public int minSubArrayLen(int target, int[] nums) { int left=0; int sum=0; int res=Integer.MAX_VALUE; for(int right=0;right<nums.length;right++){ sum+=nums[right]; while(sum>=target){ res=Math.min(res,right-left+1); sum-=nums[left]; left++; } } return res>target?0:res; } }
乘积小于K的子数组
- 方法1:滑动窗口
class Solution { public int numSubarrayProductLessThanK(int[] nums, int k) { int left=0; int sum=1; int count=0; for(int right=0;right<nums.length;right++){ sum*=nums[right]; while(left<=right && sum>=k){ sum/=nums[left++]; } if(left<=right){ count+=right-left+1; } } return count; } }
和为k的子数组
这题还得再看再做!!!
- 方法1:前缀和+哈希表
这道题如果nums[i]的取值范围不包括负数的话,那么就可以用滑动窗口
但是这样看来,这道题不能用滑动窗口
因此我们要找一个其他的方法——前缀和
看完相信你应该懂得差不多了
class Solution { public int subarraySum(int[] nums, int k) { int sum=0;//记录前缀和 int count=0;//记录个数 Map<Integer,Integer> map=new HashMap<>(); map.put(0,1); for(int i:nums){ sum+=i; count+=map.getOrDefault(sum-k,0); map.put(sum,map.getOrDefault(sum,0)+1); } return count; } }
0和1个数相同的子数组
剑指 Offer II 011. 0 和 1 个数相同的子数组
这题还得再看再做!!!
- 方法1:前缀和+哈希表
class Solution { public int findMaxLength(int[] nums) { int sum=0;//记录前缀和 int count=0;//记录个数 Map<Integer,Integer> map=new HashMap<>(); map.put(0,-1); for(int i=0;i<nums.length;i++){ sum+=nums[i]==0?-1:1; if(map.containsKey(sum)){ count=Math.max(count,i-map.get(sum)); } else{ map.put(sum,i); } } return count; } }
二维子矩阵的和
没懂没懂没懂!!!
- 方法1:前缀和
class NumMatrix { int[][] mat; public NumMatrix(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; mat = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { mat[i][1] = matrix[i-1][0]; } for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { mat[i][j] = mat[i][j-1] + matrix[i-1][j-1]; } } for (int i = 2; i <= m; i++) { for (int j = 1; j <= n; j++) { mat[i][j] += mat[i-1][j]; } } } public int sumRegion(int row1, int col1, int row2, int col2) { return mat[row2 + 1][col2 + 1] - mat[row1][col2 + 1] - mat[row2 + 1][col1] + mat[row1][col1]; } }
字符串中的变位词
- 方法1:滑动窗口
class Solution { public boolean checkInclusion(String s1, String s2) { if(s1.length()>s2.length())return false; int[] arr1=new int[26]; int[] arr2=new int[26]; for(int i=0;i<s1.length();i++){ arr1[s1.charAt(i)-'a']++; arr2[s2.charAt(i)-'a']++; } for(int i=s1.length();i<s2.length();i++){ if(Arrays.equals(arr1,arr2)){ return true; } arr2[s2.charAt(i-s1.length())-'a']--; arr2[s2.charAt(i)-'a']++; } return Arrays.equals(arr1,arr2); } }
字符串中的所有变位词
- 方法1:滑动窗口
思路:有了这道题上面的题——字符串中的变位词的基础
这道题不是手到擒来嘛哇哈哈哈哈哈哈
class Solution { public List<Integer> findAnagrams(String s, String p) { List<Integer> list=new ArrayList<>(); if(p.length()>s.length())return list; int[] arrs=new int[26]; int[] arrp=new int[26]; for(int i=0;i<p.length();i++){ arrs[s.charAt(i)-'a']++; arrp[p.charAt(i)-'a']++; } for(int i=p.length();i<s.length();i++){ if(Arrays.equals(arrs,arrp)){ list.add(i-p.length()); } arrs[s.charAt(i-p.length())-'a']--; arrs[s.charAt(i)-'a']++; } if(Arrays.equals(arrs,arrp)){ list.add(s.length()-p.length()); } return list; } }
不含重复字符的最长子字符串
剑指 Offer II 016. 不含重复字符的最长子字符串
还得再做!!!
- 方法1:滑动窗口
class Solution { public int lengthOfLongestSubstring(String s) { int left=0; int count=0; if(s.length()<=1)return s.length(); Map<Character,Integer> map=new HashMap<>(); int right=0; for(;right<s.length();right++){ if(map.containsKey(s.charAt(right))){ count=Math.max(count,right-left); left=Math.max(left,map.get(s.charAt(right))+1); } map.put(s.charAt(right),right); } count=Math.max(count,right-left); return count; } }
回文子字符串的个数
还得再做!!!
- 方法1:中心扩展
class Solution { public int countSubstrings(String s) { int count = 0; //字符串的每个字符都作为回文中心进行判断,中心是一个字符或两个字符 for (int i = 0; i < s.length(); ++i) { count += countPalindrome(s, i, i); count += countPalindrome(s, i, i+1); } return count; } //从字符串的第start位置向左,end位置向右,比较是否为回文并计数 private int countPalindrome(String s, int start, int end) { int count = 0; while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) { count++; start--; end++; } return count; } }
删除链表的倒数第n个结点
剑指 Offer II 021. 删除链表的倒数第 n 个结点
- 方法1:双指针+傀儡节点
/** * 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 pre=new ListNode(-1); pre.next=head; ListNode fast=pre; ListNode slow=pre; while(n!=0){ fast=fast.next; n--; } while(fast.next!=null){ fast=fast.next; slow=slow.next; } slow.next=slow.next.next; return pre.next; } }
链表中环的入口节点
- 方法1:快慢指针
/** * 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) { ListNode fast=head; ListNode slow=head; while(fast!=null && fast.next!=null){ fast=fast.next.next; slow=slow.next; if(fast==slow)break; } if(fast==null || fast.next==null)return null; slow=head; while(fast!=slow){ fast=fast.next; slow=slow.next; } return slow; } }
链表中的两数相加
- 方法1:反转链表
/** * 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) { l1=reserveList(l1); l2=reserveList(l2); ListNode pre=new ListNode(-1); int sum=0; int jinwei=0; int num1=0,num2=0; while(l1!=null || l2!=null || jinwei!=0){ if(l1!=null){ num1=l1.val; l1=l1.next; } else{ num1=0; } if(l2!=null){ num2=l2.val; l2=l2.next; } else{ num2=0; } sum=num1+num2+jinwei; jinwei=sum/10; ListNode node=new ListNode(sum%10); node.next=pre.next; pre.next=node; } return pre.next; } public ListNode reserveList(ListNode head){ ListNode pre=null; while(head!=null){ ListNode headN=head.next; head.next=pre; pre=head; head=headN; } return pre; } }
重排链表
- 方法1:快慢指针+反转链表+链表合并
class Solution { public void reorderList(ListNode head) { // 找到中间节点 ListNode mid = findMid(head); // 通过中间节点,将链表分为两部分,左边一部分不动,将右边一部分翻转 ListNode curA = head; ListNode curB = reverse(mid.next);// 中间节点划分到左边部分 mid.next = null;// 将链表一分为二 // 将两个链表合并 merge(curA, curB); } private ListNode findMid(ListNode head){ // 创建一个辅助头节点 ListNode tmp = new ListNode(0); tmp.next = head; // 从辅助头节点开始,快指针一次走2个单位,慢指针一次走1个单位 // 当快指针.next == null 或者 快指针 == null 则表明,slow 指向了中间节点。 ListNode slow = tmp, fast = tmp; while (fast != null && fast.next != null){ slow = slow.next; fast = fast.next.next; } return slow; } private ListNode reverse(ListNode head){ ListNode reversedList = null; while (head != null){ ListNode next = head.next; head.next = reversedList; reversedList = head; head = next; } return reversedList; } private ListNode merge(ListNode l1, ListNode l2){ int flag = 1; // 设定一个标记,辅助交叉遍历两个链表 // flag 为奇数则插入 l1 的节点, flag 为偶数则插入 l2 的节点 ListNode head = new ListNode(0); ListNode cur=head; while (l1 != null || l2 != null){ if (flag % 2 == 0){ cur.next = l2; l2 = l2.next; }else{ cur.next = l1; l1 = l1.next; } flag++; cur = cur.next; } return head.next; } }
展平多级双向链表
- 方法1:深度优先搜索
/* // Definition for a Node. class Node { public int val; public Node prev; public Node next; public Node child; }; */ class Solution { public Node flatten(Node head) { Node last=dfs(head); return head; } public Node dfs(Node node){ Node cur=node; Node last=null;// 记录链表的最后一个节点 while(cur!=null){ Node next=cur.next; if(cur.child!=null){ Node childLast=dfs(cur.child); cur.next=cur.child; cur.child.prev=cur; // 如果 next 不为空,就将 last 与 next 相连 if(next!=null){ childLast.next=next; next.prev=childLast; } cur.child=null;// 将 child 置为空 last=childLast;//这一步也很重要,想想为什么 } else{ last=cur; } cur=next; } return last; } }
排序的循环链表
还得再写!!!
- 方法1:链表模拟
我是先看的上面这个思路然后自己写的代码,我感觉写的没问题,但是可能有很多代码的冗余,因此导致运行超时
这时回头去看思路当中的代码,发现大佬写的代码很妙~
class Solution { public Node insert(Node he, int x) { Node t = new Node(x); t.next = t; if (he == null) return t; Node ans = he; int min = he.val, max = he.val; while (he.next != ans) { he = he.next; min = Math.min(min, he.val); max = Math.max(max, he.val); } if (min == max) { t.next = ans.next; ans.next = t; } else { while (!(he.val == max && he.next.val == min)) he = he.next; while (!(x <= min || x >= max) && !(he.val <= x && x <= he.next.val)) he = he.next; t.next = he.next; he.next = t; } return ans; } }
插入、删除和随机访问都是O(1)的容器
剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器
还需要再练练!!!
- 方法1:变长数组+哈希表
class RandomizedSet { List<Integer> list; Map<Integer,Integer> map; Random random; /** Initialize your data structure here. */ public RandomizedSet() { list=new ArrayList<>(); map=new HashMap<>(); random=new Random(); } /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */ public boolean insert(int val) { if(!map.containsKey(val)){ int listIndex=list.size(); list.add(val); map.put(val,listIndex); return true; } return false; } /** Removes a value from the set. Returns true if the set contained the specified element. */ public boolean remove(int val) { if(map.containsKey(val)){ int mapIndex=map.get(val); int last=list.get(list.size()-1); list.set(mapIndex,last); map.put(last,mapIndex); list.remove(list.size()-1); map.remove(val); return true; } return false; } /** Get a random element from the set. */ public int getRandom() { int rr=random.nextInt(list.size()); return list.get(rr); } } /** * Your RandomizedSet object will be instantiated and called as such: * RandomizedSet obj = new RandomizedSet(); * boolean param_1 = obj.insert(val); * boolean param_2 = obj.remove(val); * int param_3 = obj.getRandom(); */
最近最少使用缓存
还要再做!!!
- 方法1:哈希表+双向链表
class LRUCache { class DLinkedNode { int key; //初始化key int value; //初始化value //初始化双向链表前后联系指针 DLinkedNode prev; DLinkedNode next; //初始化双向链表 public DLinkedNode() { } public DLinkedNode(int _key, int _value) { key = _key; value = _value; } } //创建哈希表 HashMap<Integer, DLinkedNode> map = new HashMap<>(); //链表当前大小 int size; //链表当前容量 int capacity; //链表头部尾部节点 DLinkedNode head; DLinkedNode tail; public LRUCache(int capacity) { //大小初始化 this.size = 0; //容量初始化 this.capacity = capacity; //使用伪头部和伪尾部节点 head = new DLinkedNode(); tail = new DLinkedNode(); head.next = tail; tail.prev = head; } public int get(int key) { //在哈希表中找到key所对应的节点 DLinkedNode node = map.get(key); //如果这个节点不存在则返回-1 if (node == null) { return -1; } //将这个节点移到头部,再返回这个节点所对应的值 movetohead(node); return node.value; } public void put(int key, int value) { //在哈希表中找到key所对应的节点 DLinkedNode node = map.get(key); //如果这个节点不存在,则创建一个新的节点进行添加 if (node == null) { //创建新节点 DLinkedNode newnode = new DLinkedNode(key, value); //将节点加入哈希表 map.put(key, newnode); //将这个节点添加到双向链表头部 addtohead(newnode); //双向链表容量+1 size++; //如果容量超过最大容量,则需将双向链表尾部的节点移除 if (size > capacity) { //在链表中删去尾部节点,同时哈希表中也移除掉这个节点,并且双向链表容量-1 DLinkedNode tail = removetail(); map.remove(tail.key); size--; } } else { //如果这个节点存在,则直接修改value node.value = value; //将这个节点在双向链表中移到头部 movetohead(node); } } //在头部添加节点的方法 public void addtohead(DLinkedNode node) { //head为虚拟头结点,由于是双向链表,添加一个节点需要建立四个连接关系 node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } //移除节点方法 public void removenode(DLinkedNode node) { //跳过这个节点重新建立双向连接关系 node.prev.next = node.next; node.next.prev = node.prev; } //将节点移到头部的方法 public void movetohead(DLinkedNode node) { removenode(node); addtohead(node); } //将节点从尾部移除的方法 public DLinkedNode removetail() { //尾部的待删除节点即为虚拟尾结点的前一个节点 DLinkedNode res = tail.prev; //将这个节点删除 removenode(res); return res; } }
变位词组
方法1:哈希表+排序
class Solution { public List<List<String>> groupAnagrams(String[] strs) { Map<String,List<String>> map=new HashMap<>(); for(String str:strs){ char[] cc=str.toCharArray(); Arrays.sort(cc); String key=new String(cc); List<String> list=map.getOrDefault(key,new ArrayList<String>());//妙啊妙啊这思路 list.add(str); map.put(key,list); } return new ArrayList<>(map.values()); } }
最小时间差
这道题代码看懂了,但自己没有单独写一遍,有时间自己要写一遍!!!
- 方法1:排序
解释下1440怎么来的
class Solution { public int findMinDifference(List<String> timePoints) { Collections.sort(timePoints); int ans = Integer.MAX_VALUE; int t0Minutes = getMinutes(timePoints.get(0)); int preMinutes = t0Minutes; for (int i = 1; i < timePoints.size(); ++i) { int minutes = getMinutes(timePoints.get(i)); ans = Math.min(ans, minutes - preMinutes); // 相邻时间的时间差 preMinutes = minutes; } ans = Math.min(ans, t0Minutes + 1440 - preMinutes); // 首尾时间的时间差 return ans; } public int getMinutes(String t) { return ((t.charAt(0) - '0') * 10 + (t.charAt(1) - '0')) * 60 + (t.charAt(3) - '0') * 10 + (t.charAt(4) - '0'); } }
后缀表达式
- 方法1:栈
class Solution { public int evalRPN(String[] tokens) { Deque<Integer> stack = new LinkedList<Integer>(); int n = tokens.length; for (int i = 0; i < n; i++) { String token = tokens[i]; if (isNumber(token)) { stack.push(Integer.parseInt(token)); } else { int num2 = stack.pop(); int num1 = stack.pop(); switch (token) { case "+": stack.push(num1 + num2); break; case "-": stack.push(num1 - num2); break; case "*": stack.push(num1 * num2); break; case "/": stack.push(num1 / num2); break; default: } } } return stack.pop(); } public boolean isNumber(String token) { return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token)); } }
小行星碰撞
- 方法1:栈
class Solution { public int[] asteroidCollision(int[] asteroids) { Stack<Integer> s=new Stack<>(); int p=0; while(p<asteroids.length){ if(s.empty() || s.peek()<0 || asteroids[p]>0){ s.push(asteroids[p]); } else if(s.peek()<=-asteroids[p]){ if(s.pop()<-asteroids[p]){ continue; } } p++; } int[] arr=new int[s.size()]; for(int i=arr.length-1;i>=0;i--){ arr[i]=s.pop(); } return arr; } }
每日温度
- 方法1:栈
class Solution { public int[] dailyTemperatures(int[] temperatures) { Stack<Integer> stack = new Stack<>(); int[] ret = new int[temperatures.length]; for (int i = 0; i < temperatures.length; i++) { while (!stack.empty() && temperatures[stack.peek()] < temperatures[i]) { int index = stack.pop(); ret[index] = i - index; } stack.push(i); } return ret; } }
往完全二叉树添加节点
- 方法1:层序遍历+队列
class CBTInserter { private Queue<TreeNode> queue; private TreeNode root; public CBTInserter(TreeNode root) { this.root = root; queue = new LinkedList<>(); queue.offer(root); //在初始化树时就层序遍历到第一个没有左或右子树的节点,即为待插入位置的父节点,在队列头部 while (queue.peek().left != null && queue.peek().right != null) { TreeNode node = queue.poll(); queue.offer(node.left); queue.offer(node.right); } } public int insert(int v) { //队列头部节点即为待插入位置的父节点 TreeNode parent = queue.peek(); TreeNode node = new TreeNode(v); //插入左子树,父节点仍无右子树,父节点不变 //插入右子树,左右子树入列,并将该父节点出列,待插入位置更改为下一个 if (parent.left == null) { parent.left = node; } else { parent.right = node; queue.poll(); queue.offer(parent.left); queue.offer(parent.right); } return parent.val; } public TreeNode get_root() { return root; } }
二叉树每层的最大值
这道题完完全全是自己写出来的!!呱唧呱唧!当然也是因为自己之前做题思路的积累,在这基础上结合题目进行匹配~
- 方法1:队列
下面这个未通过的例子帮助我修改代码,最终AC
/** * 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> largestValues(TreeNode root) { List<Integer> list=new ArrayList<>(); if(root==null)return list; Queue<TreeNode> queue=new LinkedList<>(); queue.offer(root); list.add(root.val); while(!queue.isEmpty()){ int curSize=queue.size(); int ss=curSize; int curCengMax=Integer.MIN_VALUE; int flag=0; while(curSize!=0){ TreeNode tmp=queue.poll(); if(tmp.left!=null && tmp.right!=null){ curCengMax=Math.max(curCengMax,Math.max(tmp.left.val,tmp.right.val)); queue.offer(tmp.left); queue.offer(tmp.right); flag+=0; } else{ if(tmp.left!=null){ curCengMax=Math.max(curCengMax,tmp.left.val); queue.offer(tmp.left); flag+=0; } else if(tmp.right!=null){ curCengMax=Math.max(curCengMax,tmp.right.val); queue.offer(tmp.right); flag+=0; } else{ flag+=1; } } curSize--; } if(flag<ss){ list.add(curCengMax); } } return list; } }
二叉树最底层最左边的值
- 方法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 findBottomLeftValue(TreeNode root) { Queue<TreeNode> queue=new LinkedList<>(); queue.offer(root); int res=0; while(!queue.isEmpty()){ int curSize=queue.size(); for(int i=0;i<curSize;i++){ TreeNode curRoot=queue.poll(); if(i==0){ res=curRoot.val; } if(curRoot.left!=null){ queue.offer(curRoot.left); } if(curRoot.right!=null){ queue.offer(curRoot.right); } } } return res; } }
二叉树的右侧视图
- 方法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<Integer> rightSideView(TreeNode root) { Queue<TreeNode> queue=new LinkedList<>(); List<Integer> list=new ArrayList<>(); if(root!=null){ queue.offer(root); } while(!queue.isEmpty()){ int curSize=queue.size(); for(int i=0;i<curSize;i++){ TreeNode curRoot=queue.poll(); if(i==curSize-1){ list.add(curRoot.val); } if(curRoot.left!=null){ queue.offer(curRoot.left); } if(curRoot.right!=null){ queue.offer(curRoot.right); } } } return list; } }
二叉树剪枝
有时间可以再做做,这个思路需要自己看到就能想到才算真的成功
- 方法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 pruneTree(TreeNode root) { if(root==null)return null; root.left=pruneTree(root.left); root.right=pruneTree(root.right); if(root.val==0 && root.left==null && root.right==null){ root=null; } return root; } }
从根节点到叶节点的路径数字之和
剑指 Offer II 049. 从根节点到叶节点的路径数字之和
这道题还得再写,思路还得自己想出来才行啊啊啊啊啊
- 方法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 sumNumbers(TreeNode root) { return dfs(root,0); } public int dfs(TreeNode node,int val){ if(node==null)return 0; val=val*10+node.val; if(node.left==null && node.right==null){ return val; } return dfs(node.left,val)+dfs(node.right,val); } }
向下的路径节点之和
两个思路的代码都是直接拿过来的,自己没写呢,还得再看这道题!!这种题怎么这么难啊对我来说...
- 方法1:深度优先搜索
class Solution { public int pathSum(TreeNode root, int targetSum) { if (root == null) { return 0; } int ret = rootSum(root, targetSum); ret += pathSum(root.left, targetSum); ret += pathSum(root.right, targetSum); return ret; } public int rootSum(TreeNode root, int targetSum) { int ret = 0; if (root == null) { return 0; } int val = root.val; if (val == targetSum) { ret++; } ret += rootSum(root.left, targetSum - val); ret += rootSum(root.right, targetSum - val); return ret; } }
- 方法2:前缀和
class Solution { public int pathSum(TreeNode root, int targetSum) { HashMap<Long, Integer> prefix = new HashMap<>(); prefix.put(0L, 1); return dfs(root, prefix, 0, targetSum); } public int dfs(TreeNode root, Map<Long, Integer> prefix, long curr, int targetSum) { if (root == null) { return 0; } int ret = 0; curr += root.val; ret = prefix.getOrDefault(curr - targetSum, 0); prefix.put(curr, prefix.getOrDefault(curr, 0) + 1); ret += dfs(root.left, prefix, curr, targetSum); ret += dfs(root.right, prefix, curr, targetSum); prefix.put(curr, prefix.getOrDefault(curr, 0) - 1); return ret; } }
二叉搜索树中的中序后继
还得再做!!思路得是自己想出来的才行!!
- 方法1:中序遍历迭代解法(使用栈)
class Solution { public TreeNode inorderSuccessor(TreeNode root, TreeNode p) { Deque<TreeNode> stack = new ArrayDeque<TreeNode>(); TreeNode prev = null, curr = root; while (!stack.isEmpty() || curr != null) { while (curr != null) { stack.push(curr); curr = curr.left; } curr = stack.pop(); if (prev == p) { return curr; } prev = curr; curr = curr.right; } return null; } }
- 方法2:利用二叉搜索树特点
class Solution { public TreeNode inorderSuccessor(TreeNode root, TreeNode p) { TreeNode successor = null; if (p.right != null) { successor = p.right; while (successor.left != null) { successor = successor.left; } return successor; } TreeNode node = root; while (node != null) { if (node.val > p.val) { successor = node; node = node.left; } else { node = node.right; } } return successor; } }
所有大于等于节点的值之和
还得再做
- 方法1:反序中序遍历
class Solution { int sum = 0; public TreeNode convertBST(TreeNode root) { if (root != null) { convertBST(root.right); sum += root.val; root.val = sum; convertBST(root.left); } return root; } }
二叉搜索树迭代器
两个方法都看懂了,会做了,但是自己没有写,只是把代码贴上来了,下次自己写
- 方法1:递归
class BSTIterator { private int idx; private List<Integer> arr; public BSTIterator(TreeNode root) { idx = 0; arr = new ArrayList<Integer>(); inorderTraversal(root, arr); } public int next() { return arr.get(idx++); } public boolean hasNext() { return idx < arr.size(); } private void inorderTraversal(TreeNode root, List<Integer> arr) { if (root == null) { return; } inorderTraversal(root.left, arr); arr.add(root.val); inorderTraversal(root.right, arr); } }
- 方法2:迭代
class BSTIterator { private TreeNode cur; private Deque<TreeNode> stack; public BSTIterator(TreeNode root) { cur = root; stack = new LinkedList<TreeNode>(); } public int next() { while (cur != null) { stack.push(cur); cur = cur.left; } cur = stack.pop(); int ret = cur.val; cur = cur.right; return ret; } public boolean hasNext() { return cur != null || !stack.isEmpty(); } }
值和下标之差都在给定的范围内
剑指 Offer II 057. 值和下标之差都在给定的范围内
没看懂,所以还没写
日程表
- 方法1:直接遍历
class MyCalendar { List<int[]> booked; public MyCalendar() { booked = new ArrayList<int[]>(); } public boolean book(int start, int end) { for (int[] arr : booked) { int l = arr[0], r = arr[1]; if (l < end && start < r) { return false; } } booked.add(new int[]{start, end}); return true; } }
- 方法2:平衡二叉树
class MyCalendar { private TreeMap<Integer, Integer> map; public MyCalendar() { this.map = new TreeMap<>(); } public boolean book(int start, int end) { Map.Entry<Integer, Integer> entry1 = map.floorEntry(start); Map.Entry<Integer, Integer> entry2 = map.ceilingEntry(start); if (entry1 != null && entry1.getValue() > start) { return false; } if (entry2 != null && entry2.getKey() < end) { return false; } map.put(start, end); return true; } } /** * Your MyCalendar object will be instantiated and called as such: * MyCalendar obj = new MyCalendar(); * boolean param_1 = obj.book(start,end); */
出现频率最高的k个数字
剑指 Offer II 060. 出现频率最高的 k 个数字
- 方法1:堆(优先级队列)
class Solution { public int[] topKFrequent(int[] nums, int k) { Map<Integer,Integer> map=new HashMap<>(); for(int n: nums){ map.put(n,map.getOrDefault(n,0)+1); } PriorityQueue<int[]> pq=new PriorityQueue<>(new Comparator<int[]>(){ public int compare(int[] m,int[] n){ return m[1]-n[1]; } }); for(Map.Entry<Integer,Integer> entry:map.entrySet()){ int kk=entry.getKey(); int vv=entry.getValue(); if(pq.size()==k){ if(pq.peek()[1]<vv){ pq.poll(); pq.offer(new int[]{kk,vv}); } } else{ pq.offer(new int[]{kk,vv}); } } int[] res=new int[k]; for(int i=0;i<k;i++){ res[i]=pq.poll()[0]; } return res; } }
和最小的k个数对
代码自己没写,但差不多看懂了,还得自己写出来才行
- 方法1:优先级队列
class Solution { public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) { PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> { return (nums1[o1[0]] + nums2[o1[1]]) - (nums1[o2[0]] + nums2[o2[1]]); }); // Math.min(nums1.length, k)加快处理速度 for(int i = 0; i < Math.min(nums1.length, k); i ++) { pq.offer(new int[] {i, 0}); } List<List<Integer>> list = new ArrayList<>(); while(k -- > 0 && !pq.isEmpty()) { int[] arr = pq.poll(); list.add(Arrays.asList(nums1[arr[0]], nums2[arr[1]])); // 上一个循环已经考虑完所有nums1的数,这个循环考虑nums2中的数 if(++arr[1] < nums2.length) { pq.offer(new int[] {arr[0], arr[1]}); } } return list; } }