算法_记忆知识点汇总
1.二分法中,left要0,right要length-1 但是mid是(left+right)/2,不需要减一
2.二分法_旋转数组,查找数组的旋转点:要right=mid,而不能像普通的二分法那样right=mid-1.因为mid-1可能会跳过旋转点。
3.二分法:搜索回旋数组,要找最小值,而不是最大值。为什么left=mid+1但是right=mid?因为,nums[mid]如果>nums[right],那么这个最小值,也就是我们最后要找的那个值,一定是在这个mid索引的右边,说白了, 这个mid索引是不可能的。 而如果要是nums[mid]<nums[right],那我们要找的那个值,可能是这个mid。如果贸然把这个mid-1传给right。很可能会错过的。
复制数组:Arrays.copyOfRange(nums,start,end); 左开右闭。
Arrays.copyOfRange(nums,0,0);不会报错,创建了一个空数组,但是Arrays.copyOfRange(nums,0,-1);就会报错了
对链表的排序是归并排序。
短路求值!
if(minStack.isEmpty() || val <= minStack.peek())
正常来说,这个minStack.peek()当minStack为空的时候,会报错。可是我们前面有个判空。如果为空,后面的条件会短路,因此跳过了。就不会出问题了。
public void pop() {if(stack.pop().equals(minStack.peek())) { // 这里已经执行了stack.pop()minStack.pop();}// 不需要再写stack.pop(),因为上面已经pop过了 }
每日温度那个题,最优算法用的是Stack栈
Stack线程安全,Dequeue线程不安全。Dequeue是用安全性换了性能。
堆:完全二叉树。有大根堆、小根堆
1.数组连续值最大
轮转数组的索引index是i-k。
//轮转数组的索引index是i-k。因为第零个位置,它对应的数字应该是某个数字前进了三个之后才到第零个位置的。所以,这个数字应该是从index 0 往前找三个位置。也就是0 -1 -2 -3也就是对应的:0 7 6 5.所以是i-k
//前缀积 后缀积 只需要前后遍历一遍就行。 遍历前缀积的时候,不需要for循环。只需要用当前元素的前一个*前一个元素的前缀积就行了
//O2n、、、O3n 也都是On
一般寻找第几大的元素,无论这个元素是否重复,都会用小根堆来解决问题。
因为直接遍历,一次只能找
关于时间复杂度
O2n是On。
Okn不是On
如果Okn中的k是固定的,像2一样,2是固定的,那么这个Okn就是On
减枝算法:用if排除掉某些情况,从而减少计算量。
sort方法的时间复杂度是Onlogn
如果在一个算法前面用了一次sort,后面又用了一次On的算法。那么这个算法的总时间复杂度是Onlogn
如果前面用了sort,后面用了On²的算法,那么这个算法的总时间复杂度是On²
索引越界不仅仅是往上越界,还可能是往下越界。
Set<List<Integer>> set = new HashSet<>(list); List<List<Integer>> res= new ArrayList<>(set);
不用add,直接成List 的方法:
Arrays.asList(a,b,c,d,e,f,g);
一个小小的else,可能导致整个算法的错误。上面代码有else,下面代码没有else。力扣:三数之和
class Solution {public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> res = new ArrayList();if(nums==null){return res;}Arrays.sort(nums);for(int i =0;i<nums.length;i++){if(i>0&&nums[i] == nums[i - 1]){continue;}int a = nums[i];int left =i+1;int right = nums.length-1;while(left<right){int sum = a + nums[left]+nums[right];if(sum==0){/*List<Integer> array = new ArrayList<>();array.add(nums[i]);array.add(nums[left]);array.add(nums[right]);res.add(array);*/res.add(Arrays.asList(nums[i],nums[left],nums[right]));/*if (nums[left]==nums[left-1]) {//这个循环中,左右指针没有变化,所以导致。每一次传进while循环里面都是相同的left right i。因此无限循环。//要加上left++才行continue;}*/while(left<right&&nums[left]==nums[left+1]){left++;}while(left<right&&nums[right]==nums[right-1]){right--;}left++;right--;}else if(sum<0){left++;}else{right --;}}}//有重复,需要对res去重//Set<List<Integer>> set = new HashSet<>(res);//List<List<Integer>> list = new ArrayList<>(set);return res;} }
class Solution {public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> res = new ArrayList();if(nums==null){return res;}Arrays.sort(nums);for(int i =0;i<nums.length;i++){if(i>0&&nums[i] == nums[i - 1]){continue;}int a = nums[i];int left =i+1;int right = nums.length-1;while(left<right){int sum = a + nums[left]+nums[right];if(sum==0){/*List<Integer> array = new ArrayList<>();array.add(nums[i]);array.add(nums[left]);array.add(nums[right]);res.add(array);*/res.add(Arrays.asList(nums[i],nums[left],nums[right]));/*if (nums[left]==nums[left-1]) {//这个循环中,左右指针没有变化,所以导致。每一次传进while循环里面都是相同的left right i。因此无限循环。//要加上left++才行continue;}*/while(left<right&&nums[left]==nums[left+1]){left++;}while(left<right&&nums[right]==nums[right-1]){right--;}left++;right--;}if(sum<0){left++;}else{right --;}}}//有重复,需要对res去重//Set<List<Integer>> set = new HashSet<>(res);//List<List<Integer>> list = new ArrayList<>(set);return res;} }
滑动窗口:特殊形式的双指针
二叉树和图的DFS用递归。需要在一条路径上跑完,确定这条路径到底行不行
二叉树和图的BFS用队列实现。一般用于感染。
回溯算法:是要回溯到传入这一层之前的状态。
递归和二叉树有什么不同呢?其实递归它像是:只在叶节点上存放return结果的二叉树。
而二叉树在每个节点都存放。
找零钱 算法 用的是DP,而不是贪心算法。
做的不错的地方:第一次看题目就想到了颜色分类算法的最有解,只遍历一遍数组且思路方向对。但是具体实施细节有误。
三路指针快速排序,三个指针实际上有点类似隐式复制了三组数组。只是这三组数组不用画出来,用指针就行。因此,当第一个元素和第一个元素交换的时候,其实是cur指针对应的数组和left指针对应的数组的交换。
如果算法看不懂,就带入几个测试用例看看。
dfs(n,++left,right,tmp);是先执行left++再执行函数
dfs(n,left++,right,tmp);是先执行函数再left++
力扣 T22 括号生成
class Solution {List<String> res = new ArrayList<>();public List<String> generateParenthesis(int n) {String tmp ="" ;dfs(n,0,0,tmp);return res;}public void dfs(int n , int left , int right , String tmp){if(left==n&&right==n){res.add(tmp); return;}if(left<n){//这里的left++了,但是我们没有给它回溯,因此这里正确的写法不是left++,而是left+1tmp = tmp+'(';dfs(n,++left,right,tmp);tmp = tmp.substring(0,tmp.length()-1);}if(right<left){tmp = tmp+')';dfs(n,left,++right,tmp);tmp = tmp.substring(0,tmp.length()-1);}} }
回溯是回溯到进入这层之前的状态
return 结束的仅仅是当前层
class Solution {public boolean exist(char[][] board, String word) {//recordint rows = board.length;int cols = board[0].length;boolean[][] barray = new boolean[rows][cols];for(int i = 0;i<rows;i++){for(int j =0;j<cols;j++){boolean res = dfs(i,j,0,board,word,barray);if(res == true){return true;}}}return false;}public boolean dfs(int row ,int col ,int index,char[][] board, String word,boolean[][] barray){//boardthif(index == word.length()){return true;}//recordint rows = board.length;int cols = board[0].length;//condition and judge itif(row<0 || row>=rows || col<0 || col>=cols || barray[row][col]==true || board[row][col]!=word.charAt(index)){return false;}//renew the barraybarray[row][col] = true;//recursionboolean res = false;res |= dfs(row+1,col,index+1,board,word,barray);res |= dfs(row,col+1,index+1,board,word,barray);res |= dfs(row-1,col,index+1,board,word,barray);res |= dfs(row,col-1,index+1,board,word,barray);//backtrackingbarray[row][col] = false;return res;} }
这个代码的一个思想是:index是当前元素索引,判断字符是否相等用的是index,为什么最后判断index和word.length()相等与否用的也是index而不是index+1呢?是因为res |= dfs(row-1,col,index+1,board,word,barray);的时候已经给index+1了。因此一定能够走到index+1的长度。也就是实际上这个inde一共走了length+1步。也就是索引从0到length。
可以用|=布尔值来代替用for + if 循环判断
res |= dfs(row+1,col,index+1,board,word,barray);
res |= dfs(row,col+1,index+1,board,word,barray);
res |= dfs(row-1,col,index+1,board,word,barray);
res |= dfs(row,col-1,index+1,board,word,barray);
判断是否为回文串:用的是左右双指针
多维动态规划:不同路径 核心思想:f(m*n)=f(m*(n-1))+f((m-1)*n)
用二维数组保存起来
一种递归中类似树的很重要的思想。一共六个空位,0+6,1+5,2+4,3+3,4+2,5+1,6+0互不包含,因此必定互斥,因此可以分别进行递归。
典型例题:力扣leetcode131 分割回文串
//所有动态规划的dp其实都有一个基准值,那么这个题的基准值不是每个币对应1,而是这个dp[0],且只有这个dp[0],其他包括 币对应的1 都是在这个基准值0的基础上迭代的。
class Solution {//动态规划中很有意思的现象是,我们把for循环时候,int i =1;i<=mount而不再是<,并且不再是int i = 0了public int coinChange(int[] coins, int amount) {int n = amount;int[] dp = new int[amount+1];for(int i =1;i<=n;i++){dp[i] = amount+1;}//所有动态规划的dp其实都有一个基准值,那么这个题的基准值不是每个币对应1,而是这个dp[0],且只有这个dp[0],其他包括 币对应的1 都是在这个基准值0的基础上迭代的。dp[0] = 0;for(int i = 1;i<=amount ; i++){for(int coin:coins){int tmp = i-coin;if(tmp>=0){dp[i] = Math.min(dp[tmp]+1,dp[i]);}}}return dp[amount]<=amount? dp[amount]:-1;} }