[TOC]
算法训练--数组
数组基本理论
-
数组(Array)是有序的元素序列,数组是存放在连续内存空间上的相同类型数据的集合,数组可以方便的通过下标索引的方式获取到下标下对应的数据
-
正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址
- 数组下标都是从0开始的
- 数组内存空间的地址是连续的
-
数组的元素是不能删的,只能覆盖
相关题目练习
283. 移动零
-
题目描述
-
题解
class Solution { public void moveZeroes(int[] nums) { //记录非零元素下标 int i=0; for(int j=0;j<nums.length;j++){ if(nums[j]!=0){ nums[i]=nums[j]; if(j!=i){ nums[j]=0; } i++; } } } }
26. 删除有序数组中的重复项
-
题目描述
-
题解
class Solution { public int removeDuplicates(int[] nums) { if(nums==null){ return 0; } int i=1; for(int j=1;j<nums.length;j++){ if(nums[j]!=nums[j-1]){ nums[i]=nums[j]; i++; } } return i; } }
70. 爬楼梯
-
题目描述
-
题解
class Solution { public int climbStairs(int n) { if(n<=2){ return n; } int f1=1,f2=2,f3=3; for(int i=3;i<n+1;i++){ f3=f2+f1; f1=f2; f2=f3; } return f3; // if(n<=1) return n; // int[] nums=new int[n+1]; // nums[1]=1;nums[2]=2; // for(int i=3;i<n+1;i++){ // nums[i]=nums[i-1]+nums[i-2]; // } // return nums[n]; } }
15. 三数之和
-
题目描述
-
题解
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++){ int j=i+1; int k=nums.length-1; //去除肯定不成立分支 if(nums[i]>0) break; //过滤相同值 if(i>0 && nums[i]==nums[i-1]) continue; while(j<k){ int sum=nums[i]+nums[j]+nums[k]; if(sum==0){ res.add(Arrays.asList(nums[i],nums[j],nums[k])); while(j<k && nums[j]==nums[j+1]) j++; while(j<k && nums[k]==nums[k-1]) k--; j++; k--; }else if(sum>0){ k--; }else{ j++; } } } return res; } }
11. 盛最多水的容器
-
题目描述
-
题解
class Solution { public int maxArea(int[] height) { //双指针 int i=0,j=height.length-1,area=0; while(i<j){ int h=Math.min(height[i],height[j]); area=Math.max(area,(j-i)*h); if(height[i]>height[j]){ j--; }else{ i++; } } return area; } }
844. 比较含退格的字符串
-
题目描述
-
题解
class Solution { public boolean backspaceCompare(String s, String t) { int sSkipNum=0; //记录s的#数量 int tSkipNum=0; //记录t的#数量 int i=s.length()-1; int j=t.length()-1; while(true){ while(i>=0){ //从后往前,消除s的# if(s.charAt(i)=='#') sSkipNum++; else{ if(sSkipNum>0){ sSkipNum--; }else{ //不存在#就跳出循环 break; } } i--; } while(j>=0){ //从后往前,消除t的# if(t.charAt(j)=='#'){ tSkipNum++; }else{ if(tSkipNum>0){ tSkipNum--; }else{ break; } } j--; } //后半部分#消除完了,接下来比较 if(i<0 || j<0) break;//s或t遍历到头了 if(s.charAt(i)!=t.charAt(j)){ return false; } i--;j--; } if(i==-1 && j==-1){ //同时遍历完成 return true; }else{ return false; } } }
977. 有序数组的平方
-
题目描述
-
题解
/** 数组其实是有序的, 只不过负数平方之后可能成为最大数了 那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间 */ class Solution { public int[] sortedSquares(int[] nums) { int r=nums.length-1; int l=0; int[] res=new int[nums.length]; int index=nums.length-1; while(l<=r){ if(nums[l]*nums[l]<nums[r]*nums[r]){ res[index--]=nums[r]*nums[r]; r--; }else{ res[index--]=nums[l]*nums[l]; l++; } } return res; } }
189. 轮转数组
-
题目描述
-
题解
/** 三次反转,常考题 */ class Solution { public void rotate(int[] nums, int k) { int len=nums.length; //注意:k取模len k%=len; //整体反转 [7,6,5,4,3,2,1] reverse(nums,0,len-1); //再次反转前k个数据 [5,6,7,4,3,2,1] reverse(nums,0,k-1); //再次反转后len-k个数据 [5,6,7,1,2,3,4] reverse(nums,k,len-1); } public void reverse(int[] nums,int start,int end){ while(start<end){ int temp=nums[end]; nums[end]=nums[start]; nums[start]=temp; start++; end--; } } }
88. 合并两个有序数组
-
题目描述
-
题解
利用num1、num2已结排好序这一性质,我们可以使用双指针方法。这一方法将两个数组看作队列,每次从两个数组头部取出比较小的数字放到结果中。如下面的动画所示
class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int p1=0,p2=0,curr=0; int[] sorted=new int[m+n]; while(p1<m || p2<n){ if(p1==m){ curr=nums2[p2++]; }else if(p2==n){ curr=nums1[p1++]; }else if(nums1[p1]<nums2[p2]){ curr=nums1[p1++]; }else{ curr=nums2[p2++]; } sorted[p1+p2-1]=curr; } for(int i=0;i<m+n;i++){ nums1[i]=sorted[i]; } } }
66. 加一
-
题目描述
-
题解
/** 当我们对数组digits加一时,我们只需要关注digits的末尾出现了多少个9即可 只需要对数组digits进行一次逆序遍历,找出第一个不为9的元素,将其加一并将后续所有元素置零即可 如果digits中所有的元素均为9,我们需要返回一个新的数组 */ class Solution { public int[] plusOne(int[] digits) { for(int i=digits.length-1;i>=0;i--){ digits[i]=(digits[i]+1)%10; if(digits[i]!=0){ return digits; } } int[] res=new int[digits.length+1]; res[0]=1; return res; } }
二分查找
- 这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了
704. 二分查找
-
题目描述
-
题解
class Solution { public int search(int[] nums, int target) { int left=0,right=nums.length-1; //left==right 因此mid-1或者mid+1 while(left<=right){ int mid=(right-left)/2+left; if(nums[mid]==target){ return mid; }else if(nums[mid]>target){ right=mid-1; }else{ left=mid+1; } } return -1; } }
35. 搜索插入位置
-
题目描述
-
题解
class Solution { public int searchInsert(int[] nums, int target) { int n=nums.length; int left=0,right=n-1,ans=n; while(left<=right){ int mid=(right-left)/2+left; if(target<=nums[mid]){ ans=mid; right=mid-1; }else{ left=mid+1; } } return ans; } }
69. x 的平方根 (*)
-
题目描述
-
题解
/** 找到某个值的平方等于或者没找到则返回最大的一个平方小于x的即退出循环的right */ class Solution { public int mySqrt(int x) { int left=1,right=x; while(left<=right){ int mid=(right-left)/2+left; //用x/m<m而不是m*m>x防止整数溢出 if(mid>x/mid){ right=mid-1; }else if(mid<x/mid){ left=mid+1; }else{ return mid; } } return right; } } -
变种:二分法 求浮点数的平方根 保留n位小数
public class Solution { //1乘10的负15次方 private static double epsilon = 1e-15; public static void main(String[] args){ double ans = mySqrt(x,epsilon); //精确到n为小数输出 System.out.printf(String.format("%.3f",ans)); } public static double mySqrt(double x , double epsilon){ double left = 0 , right = x; if(x == 0 || x == 1){ return x; } while(left <= right){ double mid = left + (right - left) / 2; //绝对值小于 epsilon if(Math.abs(mid * mid - x) < epsilon){ return mid; }else if(mid * mid < x){ left = mid+1; }else{ right = mid-1; } } return right; } }
367. 有效的完全平方数
-
题目描述
-
题解
class Solution { public boolean isPerfectSquare(int num) { int left=1,right=num; while(left<=right){ int mid=(right-left)/2+left; long square=(long)mid*mid; if(num<square){ right=mid-1; }else if(num>square){ left=mid+1; }else{ return true; } } return false; } }
33. 搜索旋转排序数组
-
题目描述
-
题解
class Solution { public int search(int[] nums, int target) { if(nums.length==0) return -1; if(nums.length==1) return nums[0]==target?0:-1; int left=0,right=nums.length-1; while(left<=right){ int mid=(left+right)/2; if(nums[mid]==target) return mid; else if(nums[mid]<nums[right]){ if(nums[mid]<target && target<=nums[right]){ left=mid+1; }else{ right=mid-1; } }else{ if(nums[left]<=target && target<nums[mid]){ right=mid-1; }else{ left=mid+1; } } } return -1; } }
209. 长度最小的子数组
-
题目描述
-
题解
class Solution { public int minSubArrayLen(int target, int[] nums) { int res=Integer.MAX_VALUE; int left=0; int sum=0; for(int right=left;right<nums.length;right++){ sum+=nums[right]; while(sum>=target){ res=Math.min(res,right-left+1); sum-=nums[left]; left++; } } return res==Integer.MAX_VALUE?0:res; } }
4. 寻找两个正序数组的中位数
-
题目描述
-
题解
class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int length1 = nums1.length, length2 = nums2.length; int totalLength = length1 + length2; if (totalLength % 2 == 1) { int midIndex = totalLength / 2; double median = getKthElement(nums1, nums2, midIndex + 1); return median; } else { int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2; double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0; return median; } } public int getKthElement(int[] nums1, int[] nums2, int k) { int length1 = nums1.length, length2 = nums2.length; int index1 = 0, index2 = 0; int kthElement = 0; while (true) { // 边界情况 if (index1 == length1) { return nums2[index2 + k - 1]; } if (index2 == length2) { return nums1[index1 + k - 1]; } if (k == 1) { return Math.min(nums1[index1], nums2[index2]); } // 正常情况 int half = k / 2; int newIndex1 = Math.min(index1 + half, length1) - 1; int newIndex2 = Math.min(index2 + half, length2) - 1; int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2]; if (pivot1 <= pivot2) { k -= (newIndex1 - index1 + 1); index1 = newIndex1 + 1; } else { k -= (newIndex2 - index2 + 1); index2 = newIndex2 + 1; } } } }
162. 寻找峰值
-
题目描述
-
题解
class Solution { public int findPeakElement(int[] nums) { int left = 0, right = nums.length - 1; while(left < right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[mid + 1]) { right = mid; } else { left = mid + 1; } } return left; } }
74. 搜索二维矩阵
-
题目描述
-
题解
若将矩阵每一行拼接在上一行的末尾,则会得到一个升序数组,我们可以在该数组上二分找到目标元素。
代码实现时,可以二分升序数组的下标,将其映射到原矩阵的行和列上
class Solution { public boolean searchMatrix(int[][] matrix, int target) { int m = matrix.length, n = matrix[0].length; int low = 0, high = m * n - 1; while (low <= high) { int mid = (high - low) / 2 + low; int x = matrix[mid / n][mid % n]; if (x < target) { low = mid + 1; } else if (x > target) { high = mid - 1; } else { return true; } } return false; } }
240. 搜索二维矩阵 II
-
题目描述
-
题解
class Solution { public boolean searchMatrix(int[][] matrix, int target) { int m = matrix.length, n = matrix[0].length; int x = 0, y = n - 1; while (x < m && y >= 0) { if (matrix[x][y] == target) { return true; } if (matrix[x][y] > target) { --y; } else { ++x; } } return false; } }
34. 在排序数组中查找元素的第一个和最后一个位置
-
题目描述
-
题解
class Solution { public int[] searchRange(int[] nums, int target) { int[] res=new int[]{-1,-1}; if(nums.length==0) return res; res[0]=searchBorder(nums,target,true); res[1]=searchBorder(nums,target,false); return res; } public int searchBorder(int[] nums,int target,boolean leftOrRight){ int temp=-1; int left=0,right=nums.length-1; while(left<=right){ int mid=(left+right)/2; if(nums[mid]<target){ left=mid+1; }else if(nums[mid]>target){ right=mid-1; }else{ temp=mid; if(leftOrRight){ right=mid-1; }else{ left=mid+1; } } } return temp; } }
50. Pow(x, n)
-
题目描述
-
题解
class Solution { public double myPow(double x, int n) { double res=1.0; for(int i=n;i!=0;i/=2){ if(i%2!=0){ res*=x; } x*=x; } return n<0?1/res:res; } }
287. 寻找重复数
-
题目描述
-
题解
class Solution { public int findDuplicate(int[] nums) { int slow = 0, fast = 0; do { slow = nums[slow]; fast = nums[nums[fast]]; } while (slow != fast); slow = 0; while (slow != fast) { slow = nums[slow]; fast = nums[fast]; } return slow; } }
153. 寻找旋转排序数组中的最小值
-
题目描述
-
题解
前面一堆0,后面一堆1,然后寻找第一个1的二分问题
class Solution { public int findMin(int[] nums) { int low = 0; int high = nums.length - 1; while (low <high) { int mid = low + (high - low) / 2; if (nums[mid] < nums[high]) { high = mid; } else { low = mid + 1; } } return nums[low]; } }
滑动窗口
-
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果
209. 长度最小的子数组
-
题目描述
-
题解
这里以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:
其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动
主要确定如下三点:
- 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组
- 窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)
- 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了
class Solution { public int minSubArrayLen(int target, int[] nums) { //滑动窗口 left窗口起始位置 right窗口结束位置 int sum=0,left=0,result=Integer.MAX_VALUE; for(int right=0;right<nums.length;right++){ sum+=nums[right]; while(sum>=target){ result=Math.min(result,right-left+1); //减去left位的值,并且left后移一位 sum-=nums[left++]; } } return result==Integer.MAX_VALUE?0:result; } }
904. 水果成篮
-
题目描述
-
题解
/** 最长的包含两种不同“类型”的子序列 */ class Solution { public int totalFruit(int[] fruits) { //两个篮子==两种水果类型 int res=0,left=0; // key:水果类型 value:水果数量 Map<Integer,Integer> map=new HashMap(); for(int right=0;right<fruits.length;right++){ map.put(fruits[right],map.getOrDefault(fruits[right],0)+1); while(map.size()>2){ //超过两种类型,需要移除一种类型,通过移动left指针 map.put(fruits[left],map.get(fruits[left])-1); if(map.get(fruits[left])==0){ map.remove(fruits[left]); } left++; } res=Math.max(res,right-left+1); } return res; } }
76. 最小覆盖子串
-
题目描述
-
题解
class Solution { public String minWindow(String s, String t) { int len=s.length(),left=0,right=0,vailded=0; int min=Integer.MAX_VALUE,start=0; Map<Character,Integer> need=new HashMap(); Map<Character,Integer> window=new HashMap(); char[] tArr=t.toCharArray(); char[] sArr=s.toCharArray(); for(char ch:tArr){ need.put(ch,need.getOrDefault(ch,0)+1); } while(right<len){ //for(int right=0;right<len;right++){ //更新窗口状态 char cright=sArr[right]; right++; if(need.containsKey(cright)){ window.put(cright,window.getOrDefault(cright,0)+1); if(need.get(cright).equals(window.get(cright))) vailded++; } //判断左侧窗口是否要收缩 while(vailded==need.size()){ //更新长度最小的子串 if(right-left<min){ min=right-left; start=left; } //要移除的字符 char cleft=sArr[left]; left++; //更新滑动窗口 if(need.containsKey(cleft)){ //取消满足条件 if(need.get(cleft).equals(window.get(cleft))) vailded--; window.put(cleft,window.getOrDefault(cleft,0)-1); } } } min=min==Integer.MAX_VALUE?0:min; return s.substring(start,start+min); } }
螺旋矩阵
59. 螺旋矩阵 II
-
题目描述
-
题解
class Solution { public int[][] generateMatrix(int n) { int[][] res=new int[n][n]; int startX=0,startY=0;//定义每循环一个圈的起始位置 int count=1;//用来给矩阵中每一个空格赋值 int offset=1;//每一圈循环,需要控制每一条边遍历的长度 int mid=n/2;//矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2) int loop=n/2;//每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理 while(loop>0){ int i=startX; int j=startY; // 下面开始的四个for就是模拟转了一圈 // 模拟填充上行从左到右(左闭右开) for(;j<startY+n-offset;j++){ res[i][j]=count++; } // 模拟填充右列从上到下 for(;i<startX+n-offset;i++){ res[i][j]=count++; } // 模拟填充下行从右到左 for(;j>startY;j--){ res[i][j]=count++; } // 模拟填充左列从下到上 for(;i>startX;i--){ res[i][j]=count++; } loop--; // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1) startX++; startY++; // offset 控制每一圈里每一条边遍历的长度 offset+=2; } // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值 if(n%2!=0){ res[mid][mid]=count; } return res; } }
54. 螺旋矩阵
-
题目描述
-
题解
class Solution { public List<Integer> spiralOrder(int[][] matrix) { int startX=0,stratY=0; int m=matrix.length,n=matrix[0].length; List<Integer> list=new ArrayList(); int loop=Math.min(m,n)/2; //行列中选择小的进行循环 int offset=1; while(loop>0){ int i=startX; int j=stratY; //上边 从左到右 for(;j<stratY+n-offset;j++){ list.add(matrix[i][j]); } //右边 从上到下 for(;i<startX+m-offset;i++){ list.add(matrix[i][j]); } // 下边 从右到左 for(;j>stratY;j--){ list.add(matrix[i][j]); } // 左边 从下到上 for(;i>startX;i--){ list.add(matrix[i][j]); } startX++; stratY++; offset+=2; loop--; } //行列相同,但为奇数需单独处理中间的值 if(m==n && n%2!=0){ list.add(matrix[m/2][n/2]); } //行大于列,且列为奇数 if(m>n && n%2!=0){ for(int i=n/2;i<m-n/2;i++){ list.add(matrix[i][n/2]); } } //列大于行,且行为奇数 if(n>m && m%2!=0){ for(int i=m/2;i<n-m/2;i++){ list.add(matrix[m/2][i]); } } return list; } }
剑指 Offer 29. 顺时针打印矩阵
-
题目描述(此题同54)
-
题解
class Solution { public int[] spiralOrder(int[][] matrix) { if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return new int[0]; } int startX=0,stratY=0,offset=1; int m=matrix.length,n=matrix[0].length; int loop=Math.min(m,n)/2; int[] res=new int[m*n]; int count=0; while(loop>0){ int i=startX; int j=stratY; for(;j<stratY+n-offset;j++){ res[count++]=matrix[i][j]; } for(;i<startX+m-offset;i++){ res[count++]=matrix[i][j]; } for(;j>stratY;j--){ res[count++]=matrix[i][j]; } for(;i>startX;i--){ res[count++]=matrix[i][j]; } startX++; stratY++; offset+=2; loop--; } if(m==n && m%2!=0){ res[count]=matrix[m/2][n/2]; } if(m>n && n%2!=0){ for(int i=n/2;i<m-n/2;i++){ res[count++]=matrix[i][n/2]; } } if(n>m && m%2!=0){ for(int i=m/2;i<n-m/2;i++){ res[count++]=matrix[m/2][i]; } } return res; } }
CodeTop系列
数组
283. 移动零
-
题目描述
-
题解
class Solution { public void moveZeroes(int[] nums) { //记录非零元素 int i=0; for(int j=0;j<nums.length;j++){ if(nums[j]!=0){ nums[i]=nums[j]; if(i!=j){ nums[j]=0; } i++; } } } }
560. 和为 K 的子数组
-
题目描述
-
题解
/** 哈希表 */ class Solution { public int subarraySum(int[] nums, int k) { //扫描一遍数组,使用map记录同样的和的次数 //对每个i计算累加sum并判断map内是否有sum-k Map<Integer,Integer> map=new HashMap<>(); map.put(0,1); int sum=0,res=0; for(int i=0;i<nums.length;i++){ sum+=nums[i]; if(map.containsKey(sum-k)){ res+=map.get(sum-k); } map.put(sum,map.getOrDefault(sum,0)+1); } return res; } }
42. 接雨水
-
题目描述
-
题解
class Solution { public int trap(int[] height) { Deque<Integer> stack=new LinkedList<>(); int area=0; for(int i=0;i<height.length;i++){ while(!stack.isEmpty() && height[stack.peek()]<height[i]){ int preIndex=stack.pop(); if(!stack.isEmpty()){ int h=Math.min(height[i],height[stack.peek()])-height[preIndex]; int w=i-stack.peek()-1; area+=w*h; } } stack.push(i); } return area; } }
4. 寻找两个正序数组的中位数
-
题目描述
-
题解
class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int length1 = nums1.length, length2 = nums2.length; int totalLength = length1 + length2; if (totalLength % 2 == 1) { int midIndex = totalLength / 2; double median = getKthElement(nums1, nums2, midIndex + 1); return median; } else { int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2; double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0; return median; } } public int getKthElement(int[] nums1, int[] nums2, int k) { int length1 = nums1.length, length2 = nums2.length; int index1 = 0, index2 = 0; int kthElement = 0; while (true) { // 边界情况 if (index1 == length1) { return nums2[index2 + k - 1]; } if (index2 == length2) { return nums1[index1 + k - 1]; } if (k == 1) { return Math.min(nums1[index1], nums2[index2]); } // 正常情况 int half = k / 2; int newIndex1 = Math.min(index1 + half, length1) - 1; int newIndex2 = Math.min(index2 + half, length2) - 1; int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2]; if (pivot1 <= pivot2) { k -= (newIndex1 - index1 + 1); index1 = newIndex1 + 1; } else { k -= (newIndex2 - index2 + 1); index2 = newIndex2 + 1; } } } }
162. 寻找峰值
-
题目描述
-
题解
O(logN)一般考虑二分搜索。有如下规律:
规律一:如果nums[i] > nums[i+1],则在i之前一定存在峰值元素
规律二:如果nums[i] < nums[i+1],则在i+1之后一定存在峰值元素
class Solution { public int findPeakElement(int[] nums) { int left = 0, right = nums.length - 1; while(left < right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[mid + 1]) { right = mid; } else { left = mid + 1; } } return left; } }
78. 子集
-
题目描述
-
题解
class Solution { List<List<Integer>> res=new ArrayList<>(); List<Integer> temp=new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { backTracking(nums,0); return res; } public void backTracking(int[] nums,int index){ //收集子集,要放在终止添加的上面,否则会漏掉自己 res.add(new ArrayList(temp)); if(index>=nums.length){ return; } for(int i=index;i<nums.length;i++){ temp.add(nums[i]); backTracking(nums,i+1); temp.remove(temp.size()-1); } } }
11. 盛最多水的容器
-
题目描述
-
题解
class Solution { public int maxArea(int[] height) { int max=0; int left=0,right=height.length-1; while(left<right){ int w=right-left; int h=Math.min(height[left],height[right]); max=Math.max(max,h*w); if(height[left]>height[right]){ right--; }else{ left++; } } return max; } }
79. 单词搜索
-
题目描述
-
题解
/** * 回溯法:相比于DFS,多了一步『撤销修改节点状态』 */ class Solution { //定义为成员变量,方便以下两个成员方法使用和修改 private boolean find; public boolean exist(char[][] board, String word) { if (board == null) return false; int m = board.length, n = board[0].length; boolean[][] visited = new boolean[m][n]; find = false; for (int i = 0; i < m; i++){ for (int j = 0; j < n; j++){ //从左上角开始遍历棋盘每个格子 backtracking(i, j, board, word, visited, 0); } } return find; } /** * i,j,board:棋盘格及当前元素的坐标 * word: 要搜索的目标单词 * visited:记录当前格子是否已被访问过 * pos: 记录目标单词的字符索引,只有棋盘格字符和pos指向的字符一致时, * 才有机会继续搜索接下来的字符;如果pos已经过了目标单词的尾部了,那么便说明找到目标单词了 */ public void backtracking(int i, int j, char[][] board, String word, boolean[][] visited, int pos){ // 超出边界、已经访问过、已找到目标单词、棋盘格中当前字符已经和目标字符不一致了 if(i<0 || i>= board.length || j<0 || j >= board[0].length || visited[i][j] || find || board[i][j]!=word.charAt(pos)) return; if(pos == word.length()-1){ find = true; return; } //修改当前节点状态 visited[i][j] = true; //遍历子节点 backtracking(i+1, j, board, word, visited, pos+1); backtracking(i-1, j, board, word, visited, pos+1); backtracking(i, j+1, board, word, visited, pos+1); backtracking(i, j-1, board, word, visited, pos+1); //撤销修改 visited[i][j] = false; } }
64. 最小路径和
-
题目描述
-
题解
class Solution { public int minPathSum(int[][] grid) { if (grid == null || grid.length == 0 || grid[0].length == 0) { return 0; } int rows = grid.length, columns = grid[0].length; int[][] dp = new int[rows][columns]; dp[0][0] = grid[0][0]; for (int i = 1; i < rows; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } for (int j = 1; j < columns; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } for (int i = 1; i < rows; i++) { for (int j = 1; j < columns; j++) { dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; } } return dp[rows - 1][columns - 1]; } }
双指针
3. 无重复字符的最长子串
-
题目描述
-
题解
class Solution { public int lengthOfLongestSubstring(String s) { if(s.length()==0) return 0; Map<Character,Integer> map=new HashMap<>(); int left=0,max=Integer.MIN_VALUE; for(int right=0;right<s.length();right++){ char cRight=s.charAt(right); map.put(cRight,map.getOrDefault(cRight,0)+1); while(map.get(cRight)>1){ //出现重复 char cLeft=s.charAt(left); map.put(cLeft,map.get(cLeft)-1); left++; } max=Math.max(max,right-left+1); } return max; } }
141. 环形链表
-
题目描述
-
题解
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(slow==fast){ return true; } } return false; } }
19. 删除链表的倒数第 N 个结点
-
题目描述
-
题解
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummy=new ListNode(-1); dummy.next=head; ListNode fast=dummy; ListNode slow=dummy; while(n>=0){ fast=fast.next; n--; } while(fast!=null){ fast=fast.next; slow=slow.next; } slow.next=slow.next.next; return dummy.next; } }
209. 长度最小的子数组
-
题目描述
-
题解
class Solution { public int minSubArrayLen(int target, int[] nums) { int res=Integer.MAX_VALUE; int left=0; int sum=0; for(int right=left;right<nums.length;right++){ sum+=nums[right]; while(sum>=target){ res=Math.min(res,right-left+1); sum-=nums[left]; left++; } } return res==Integer.MAX_VALUE?0:res; } }
88. 合并两个有序数组
-
题目描述
-
题解
class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int p1=0,p2=0,cur=0; int[] sorted=new int[m+n]; while(p1<m || p2<n){ if(p1==m){ cur=nums2[p2++]; }else if(p2==n){ cur=nums1[p1++]; }else if(nums1[p1]<nums2[p2]){ cur=nums1[p1++]; }else{ cur=nums2[p2++]; } sorted[p1+p2-1]=cur; } for(int i=0;i<sorted.length;i++){ nums1[i]=sorted[i]; } } }
76. 最小覆盖子串
-
题目描述
-
题解
class Solution { public String minWindow(String s, String t) { if(s.length()==0) return ""; if(t.length()>s.length()) return ""; char[] sArr=s.toCharArray(); char[] tArr=t.toCharArray(); Map<Character,Integer> needMap=new HashMap<>(); Map<Character,Integer> map=new HashMap<>(); int left=0,maxNum=0,start=0,minLen=Integer.MAX_VALUE; for(char c:tArr){ needMap.put(c,needMap.getOrDefault(c,0)+1); } for(int right=0;right<sArr.length;right++){ char ch=sArr[right]; map.put(ch,map.getOrDefault(ch,0)+1); if(map.get(ch).equals(needMap.get(ch))){ //满足条件的字符数 maxNum++; } //满足条件时缩小窗口 while(needMap.size()==maxNum){ //记录当前窗口 if(right-left<minLen){ start=left; minLen=right-left+1; } //缩小左边界 char cleft=sArr[left]; if(map.get(cleft).equals(needMap.get(cleft))){ maxNum--; } map.put(cleft,map.get(cleft)-1); left++; } } return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen); } }
15. 三数之和
-
题目描述
-
题解
class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> res=new ArrayList<>(); if(nums.length==0) return res; Arrays.sort(nums); for(int i=0;i<nums.length;i++){ if(nums[i]>0) break; if(i>0 && nums[i]==nums[i-1]) continue; int j=i+1,k=nums.length-1; while(j<k){ int sum=nums[i]+nums[j]+nums[k]; if(sum==0){ res.add(Arrays.asList(nums[i],nums[j],nums[k])); while(j<k && nums[j]==nums[j+1]) j++; while(j<k && nums[k]==nums[k-1]) k--; j++; k--; }else if(sum>0){ k--; }else{ j++; } } } return res; } }
11. 盛最多水的容器
-
题目描述
-
题解
class Solution { public int maxArea(int[] height) { int max=0; int left=0,right=height.length-1; while(left<right){ int w=right-left; int h=Math.min(height[left],height[right]); max=Math.max(max,h*w); if(height[left]>height[right]){ right--; }else{ left++; } } return max; } }
42. 接雨水
-
题目描述
-
题解
class Solution { public int trap(int[] height) { Deque<Integer> stack=new LinkedList<>(); int area=0; for(int i=0;i<height.length;i++){ while(!stack.isEmpty() && height[stack.peek()]<height[i]){ int preIndex=stack.pop(); if(!stack.isEmpty()){ int h=Math.min(height[i],height[stack.peek()])-height[preIndex]; int w=i-stack.peek()-1; area+=w*h; } } stack.push(i); } return area; } }
26. 删除有序数组中的重复项
-
题目描述
-
题解
class Solution { public int removeDuplicates(int[] nums) { int i=1; for(int j=1;j<nums.length;j++){ if(nums[j]!=nums[j-1]){ nums[i]=nums[j]; i++; } } return i; } }
142. 环形链表 II
-
题目描述
-
题解
public class Solution { public ListNode detectCycle(ListNode head) { if(head==null) return head; ListNode fast=head; ListNode slow=head; while(fast!=null && fast.next!=null){ fast=fast.next.next; slow=slow.next; if(fast==slow){ fast=head; while(fast!=slow){ fast=fast.next; slow=slow.next; } return slow; } } return null; } }
234. 回文链表
-
题目描述
-
题解
class Solution { public boolean isPalindrome(ListNode head) { //1、快慢指针找出链表中点 if(head==null || head.next==null) return true; ListNode fast=head; ListNode slow=fast; while(fast!=null && fast.next!=null){ fast=fast.next.next; slow=slow.next; } ListNode pre=null; if(fast!=null) slow=slow.next; //偶数节点 //2、反转后半部分 while(slow!=null){ ListNode temp=slow.next; slow.next=pre; pre=slow; slow=temp; } //3、比较从头结点到中间节点是否相同 while(head!=null && pre !=null){ if(head.val!=pre.val) return false; head=head.next; pre=pre.next; } return true; } }
61. 旋转链表
-
题目描述
-
题解
class Solution { public ListNode rotateRight(ListNode head, int k) { if (head == null || head.next == null || k == 0) return head; int count = 1; // 用来统计链表总结点数 ListNode tmp = head; while (tmp.next != null) { count++; tmp = tmp.next; } k %= count; // 当k为0时,不需要旋转, if (k == 0) return head; // 不满足上述条件,必将进行旋转,所以先将首尾相连 tmp.next = head; // 现在只需要找到倒数第k+1个节点 for (int i = 0; i < count - k; i++) { tmp = tmp.next; } ListNode newHead = tmp.next; tmp.next = null; return newHead; } }
排序
148. 排序链表
-
题目描述
-
题解
class Solution { /** * 参考:Sort List——经典(链表中的归并排序) https://www.cnblogs.com/qiaozhoulin/p/4585401.html * * 归并排序法:在动手之前一直觉得空间复杂度为常量不太可能,因为原来使用归并时,都是 O(N)的, * 需要复制出相等的空间来进行赋值归并。对于链表,实际上是可以实现常数空间占用的(链表的归并 * 排序不需要额外的空间)。利用归并的思想,递归地将当前链表分为两段,然后merge,分两段的方 * 法是使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,知道快的走到了末尾,然后 * 慢的所在位置就是中间位置,这样就分成了两段。merge时,把两段头部节点值比较,用一个 p 指向 * 较小的,且记录第一个节点,然后 两段的头一步一步向后走,p也一直向后走,总是指向较小节点, * 直至其中一个头为NULL,处理剩下的元素。最后返回记录的头即可。 * * 主要考察3个知识点, * 知识点1:归并排序的整体思想 * 知识点2:找到一个链表的中间节点的方法 * 知识点3:合并两个已排好序的链表为一个新的有序链表 */ public ListNode sortList(ListNode head) { return head == null ? null : mergeSort(head); } private ListNode mergeSort(ListNode head) { if (head.next == null) { return head; } ListNode p = head, q = head, pre = null; while (q != null && q.next != null) { pre = p; p = p.next; q = q.next.next; } pre.next = null; ListNode l = mergeSort(head); ListNode r = mergeSort(p); return merge(l, r); } ListNode merge(ListNode l, ListNode r) { ListNode dummyHead = new ListNode(0); ListNode cur = dummyHead; while (l != null && r != null) { if (l.val <= r.val) { cur.next = l; cur = cur.next; l = l.next; } else { cur.next = r; cur = cur.next; r = r.next; } } if (l != null) { cur.next = l; } if (r != null) { cur.next = r; } return dummyHead.next; } }
912. 排序数组
-
题目描述
-
题解
class Solution { public int[] sortArray(int[] nums) { if(nums.length <=1)return nums; //qSort(nums,0,nums.length-1); //selectSort(nums); // insertSort(nums); // shellSort(nums); // bucketSort(nums); // countSort(nums); // mergeSort(nums,0,nums.length-1); heapSort(nums); return nums; } /** 快速排序 **/ void qSort(int[] arr,int s,int e){ int l = s, r = e; if(l < r){ int temp = arr[l]; while(l < r){ while(l < r && arr[r] >= temp) r--; if(l < r) arr[l] = arr[r]; while(l < r && arr[l] < temp) l++; if(l < r) arr[r] = arr[l]; } arr[l] = temp; qSort(arr,s,l); qSort(arr,l + 1, e); } } /** 选择排序 **/ void selectSort(int[] arr){ int min; for(int i = 0;i<arr.length;i++){ min = i; for(int j = i;j<arr.length;j++){ if(arr[j] < arr[min]){ min = j; } } if(min != i) { int temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } } } /** * * 插入排序:数列前面部分看为有序,依次将后面的无序数列元素插入到前面的有序数列中, 初始状态有序数列仅有一个元素,即首元素。在将无序数列元素插入有序数列的过程中, 采用了逆序遍历有序数列,相较于顺序遍历会稍显繁琐,但当数列本身已近排序状态效率会更高。 * * 时间复杂度:O(N2) 稳定性:稳定 * @param arr */ public void insertSort(int arr[]){ for(int i = 1; i < arr.length; i++){ int rt = arr[i]; for(int j = i - 1; j >= 0; j--){ if(rt < arr[j]){ arr[j + 1] = arr[j]; arr[j] = rt; }else{ break; } } } } /** * 希尔排序 - 插入排序的改进版 为了减少数据的移动次数,在初始序列较大时取较大的步长,通常取序列长度的一半, 此时只有两个元素比较,交换一次; 之后步长依次减半直至步长为1,即为插入排序,由于此时序列已接近有序, 故插入元素时数据移动的次数会相对较少,效率得到了提高。 * * 时间复杂度:通常认为是O(N3/2) ,未验证 稳定性:不稳定 * @param arr */ void shellSort(int arr[]){ int d = arr.length >> 1; while(d >= 1){ for(int i = d; i < arr.length; i++){ int rt = arr[i]; for(int j = i - d; j >= 0; j -= d){ if(rt < arr[j]){ arr[j + d] = arr[j]; arr[j] = rt; }else break; } } d >>= 1; } } /** * 桶排序 - 实现线性排序,但当元素间值得大小有较大差距时会带来内存空间的较大浪费 首先,找出待排序列中得最大元素max,申请内存大小为max + 1的桶(数组)并初始化为0; 然后,遍历排序数列,并依次将每个元素作为下标的桶元素值自增1; 最后,遍历桶元素,并依次将值非0的元素下标值载入排序数列( 桶元素>1表明有值大小相等的元素,此时依次将他们载入排序数列),遍历完成,排序数列便为有序数列。 * * 时间复杂度:O(x*N) 稳定性:稳定 * @param arr */ void bucketSort(int[] arr){ int[] bk = new int[50000 * 2 + 1]; for(int i = 0; i < arr.length; i++){ bk[arr[i] + 50000] += 1; } int ar = 0; for(int i = 0; i < bk.length; i++){ for(int j = bk[i]; j > 0; j--){ arr[ar++] = i - 50000; } } } /** * 基数排序 - 桶排序的改进版,桶的大小固定为10,减少了内存空间的开销。 首先,找出待排序列中得最大元素max,并依次按max的低位到高位对所有元素排序; 桶元素10个元素的大小即为待排序数列元素对应数值为相等元素的个数, 即每次遍历待排序数列,桶将其按对应数值位大小分为了10个层级, 桶内元素值得和为待排序数列元素个数。 * @param arr */ void countSort(int[] arr){ int[] bk = new int[19]; Integer max = Integer.MIN_VALUE; for(int i = 0; i < arr.length; i++){ if(max < Math.abs(arr[i])) max = arr[i]; } if(max < 0) max = -max; max = max.toString().length(); int [][] bd = new int[19][arr.length]; for(int k = 0; k < max; k++) { for (int i = 0; i < arr.length; i++) { int value = (int)(arr[i] / (Math.pow(10,k)) % 10); bd[value+9][bk[value+9]++] = arr[i]; } int fl = 0; for(int l = 0; l < 19; l++){ if(bk[l] != 0){ for(int s = 0; s < bk[l]; s++){ arr[fl++] = bd[l][s]; } } } bk = new int[19]; fl = 0; } } /** * 归并排序 - 采用了分治和递归的思想, 递归&分治-排序整个数列如同排序两个有序数列, 依次执行这个过程直至排序末端的两个元素, 再依次向上层输送排序好的两个子列进行排序直至整个数列有序(类比二叉树的思想,from down to up)。 * * 时间复杂度:O(NlogN) 稳定性:稳定 * @param arr */ void mergeSortInOrder(int[] arr,int bgn,int mid, int end){ int l = bgn, m = mid +1, e = end; int[] arrs = new int[end - bgn + 1]; int k = 0; while(l <= mid && m <= e){ if(arr[l] < arr[m]){ arrs[k++] = arr[l++]; }else{ arrs[k++] = arr[m++]; } } while(l <= mid){ arrs[k++] = arr[l++]; } while(m <= e){ arrs[k++] = arr[m++]; } for(int i = 0; i < arrs.length; i++){ arr[i + bgn] = arrs[i]; } } void mergeSort(int[] arr, int bgn, int end) { if(bgn >= end){ return; } int mid = (bgn + end) >> 1; mergeSort(arr,bgn,mid); mergeSort(arr,mid + 1, end); mergeSortInOrder(arr,bgn,mid,end); } /** * 堆排序 - 堆排序的思想借助于二叉堆中的最大堆得以实现。 首先,将待排序数列抽象为二叉树,并构造出最大堆; 然后,依次将最大元素(即根节点元素)与待排序数列的最后一个元素交换 (即二叉树最深层最右边的叶子结点元素); * 每次遍历,刷新最后一个元素的位置(自减1),直至其与首元素相交,即完成排序。 * * 时间复杂度:O(NlogN) 稳定性:不稳定 * * @param arr */ void heapSort(int[] nums) { int size = nums.length; for (int i = size/2-1; i >=0; i--) { adjust(nums, size, i); } for (int i = size - 1; i >= 1; i--) { int temp = nums[0]; nums[0] = nums[i]; nums[i] = temp; adjust(nums, i, 0); } } void adjust(int []nums, int len, int index) { int l = 2 * index + 1; int r = 2 * index + 2; int maxIndex = index; if (l<len&&nums[l]>nums[maxIndex])maxIndex = l; if (r<len&&nums[r]>nums[maxIndex])maxIndex = r; if (maxIndex != index) { int temp = nums[maxIndex]; nums[maxIndex] = nums[index]; nums[index] = temp; adjust(nums, len, maxIndex); } } }
179. 最大数
-
题目描述
-
题解
class Solution { public String largestNumber(int[] nums) { int n = nums.length; // 转换成包装类型,以便传入 Comparator 对象(此处为 lambda 表达式) Integer[] numsArr = new Integer[n]; for (int i = 0; i < n; i++) { numsArr[i] = nums[i]; } Arrays.sort(numsArr, (x, y) -> { long sx = 10, sy = 10; while (sx <= x) { sx *= 10; } while (sy <= y) { sy *= 10; } return (int) (-sy * x - y + sx * y + x); }); if (numsArr[0] == 0) { return "0"; } StringBuilder ret = new StringBuilder(); for (int num : numsArr) { ret.append(num); } return ret.toString(); } }
56. 合并区间
-
题目描述
-
题解
class Solution { public int[][] merge(int[][] intervals) { List<int[]> res=new LinkedList<>(); Arrays.sort(intervals,(o1,o2)->Integer.compare(o1[0],o2[0])); int start=intervals[0][0]; for(int i=1;i<intervals.length;i++){ if(intervals[i][0]>intervals[i-1][1]){ res.add(new int[]{start,intervals[i-1][1]}); start=intervals[i][0]; }else{ intervals[i][1]=Math.max(intervals[i][1],intervals[i-1][1]); } } res.add(new int[]{start,intervals[intervals.length-1][1]}); return res.toArray(new int[res.size()][]); } }