Common

经典算法

堆排序

构建大根堆(升序排序)或者小根堆(降序排序).
总体思路:

  1. 构建大根堆, 从最后一个非叶子节点(2*length-1)开始处理, (叶子节点没有子节点了, 所以从最后一个非叶子节点开始)
  2. 将第一个元素(最大元素)与最后一个元素交换
  3. 将前面元素再进行调整, 保证还是一个大根堆
  4. 重复2.3步骤知道数组有序

调整算法思路:

  1. 先比较左右子树的大小, index指向最大的那个
  2. 然后比较index和根节点, 如果index>根节点, 那么交换index和根节点的位置, 否则退出循环, 已经是一个大根堆.保证这三个节点是一个大根堆, 然后再处理下面的元素(经过交换后, 根节点可能还比子节点小, 让根节点一直下沉), i指向根节点(根节点经过交换后可能在左子树或者在右子树上)

    1. //Heap sort
    2. public static void heapSort(int[] nums){
    3. //首先构造大根堆, 从最后一个非叶子节点开始,length/2-1
    4. for (int i=nums.length/2-1;i>=0;i--){
    5. adjust(nums,i,nums.length);
    6. }
    7. //将大根堆的第一个元素(最大元素)于最后一个元素交换, 然后对前n-1个元素再进行调整
    8. for (int i=nums.length-1;i>0;i--){
    9. int temp=nums[i];
    10. nums[i]=nums[0];
    11. nums[0]=temp;
    12. adjust(nums,0, i);
    13. }
    14. }
    15. //Heap_sort's adjust function
    16. public static void adjust(int[] nums,int i,int length){
    17. int temp=nums[i];//先用一个临时变量保存根节点
    18. for (int index=2*i+1;index<length;index=index*2+1){//index指向左子树
    19. if (index+1<length&&nums[index+1]>nums[index]){//比较左右节点的大小
    20. index++; //右子树更大, index*2+2, 指向右子树
    21. }
    22. if (nums[index]>t){//子树比父节点更大
    23. nums[i]=nums[index];
    24. i=index;
    25. }else {
    26. break;
    27. }
    28. }
    29. //最后进行赋值, 优化
    30. nums[i]=temp;
    31. }

    42.接雨水

    image.png
    方法一:
    先找到中间最高的柱子, 然后分别从两边向最高的柱子遍历. 最高柱子左边的柱子一定比它矮, 那么只记录左边柱子的最大值, 遍历到第i个柱子的时候, 与它左边最高的柱子比较, 如果比左边最高柱子矮, 那么表示可以接到水, 那么就用最高柱子-第i个柱子, 加到总数中, 否则那么第i个柱子就是左边最高的柱子. 同样的右边的遍历规则也是如此

    1. class Solution {
    2. public int trap(int[] height) {
    3. int max_height=0,index=0;
    4. int sum=0;
    5. //求出中间的最大高度
    6. for (int i=0;i<height.length;i++) {
    7. if (height[i]>max_height){
    8. max_height=height[i];
    9. index=i;
    10. }
    11. }
    12. //从左往右遍历
    13. int temp=0;
    14. for (int i=0;i<index;i++){
    15. if (height[i]>temp){
    16. temp=height[i];
    17. }else {
    18. sum+=temp-height[i];
    19. }
    20. }
    21. temp=0;
    22. for (int i=height.length-1;i>index;i--){
    23. if (height[i]>temp){
    24. temp=height[i];
    25. }else {
    26. sum+=temp-height[i];
    27. }
    28. }
    29. return sum;
    30. }
    31. }

    方法二:
    利用栈, 如下图所示
    Algorithm - 图2Algorithm - 图3Algorithm - 图4Algorithm - 图5Algorithm - 图6
    如果第i个柱子比栈顶的柱子高, 那么说明栈顶的柱子可能接到水, 将其出栈, 然后再观察栈顶的柱子,如果没有柱子了, 说明左边没有比第一次出栈的柱子高了, 那么这个柱子一定比第一次出栈的柱子高或者同样高, 那么算出栈顶柱子与第i个柱子的距离, 界限高度=两者谁的高度最小(木桶效应)-栈顶的柱子高度, 距离*界限高度 将其加入总和之中. 重复

    1. Stack<Integer> s = new Stack<>();
    2. int i=0,sum=0;
    3. while (i<height.length){
    4. //如果遍历到的柱子比栈顶的柱子高, 那么他们中间就可以储存水
    5. while(!s.isEmpty()&&height[i]>height[s.peek()]){
    6. int index_temp=s.pop();//将栈顶元素出栈
    7. //如果左边没有了元素,表示不能接水
    8. if(s.isEmpty()){
    9. break;
    10. }
    11. int left=s.peek();
    12. int distance=i-left-1;//
    13. sum+=distance*(Math.min(height[left],height[i])-height[index_temp]);
    14. }
    15. s.push(i++);
    16. }
    17. return sum;

54.螺旋矩阵

image.png

  1. class Solution {
  2. public List<Integer> spiralOrder(int[][] matrix) {
  3. int row_length=matrix.length;
  4. int col_length=matrix[0].length;
  5. boolean[][] flag=new boolean[row_length][col_length];
  6. int direction=0;//方向标志
  7. int i=0,j=0;
  8. List<Integer> list=new ArrayList<>();
  9. while(!flag[i][j]){
  10. list.add(matrix[i][j]);
  11. flag[i][j]=true;
  12. switch (direction){
  13. case 0:
  14. if(j+1>=col_length||flag[i][j+1]==true){ //超过右边界, 或者下一个元素遍历过
  15. direction=(direction+1)%4;
  16. i++;
  17. }else{
  18. j++;
  19. }
  20. break;
  21. case 1:
  22. if(i+1>=row_length||flag[i+1][j]==true){
  23. direction=(direction+1)%4;
  24. j--;
  25. }else{
  26. i++;
  27. }
  28. break;
  29. case 2:
  30. if(j-1<0||flag[i][j-1]==true){
  31. direction=(direction+1)%4;
  32. i--;
  33. }else{
  34. j--;
  35. }
  36. break;
  37. case 3:
  38. if(i-1<0||flag[i-1][j]==true){
  39. direction=(direction+1)%4;
  40. j++;
  41. }else{
  42. i--;
  43. }
  44. break;
  45. }
  46. if(i<0||j<0||i>=row_length||j>=col_length||flag[i][j]==true){
  47. break;
  48. }
  49. }
  50. return list;
  51. }
  52. }

704.二分查找

  1. class Solution {
  2. public int search(int[] nums, int target) {
  3. int left=0,right=nums.length-1;
  4. while(left<=right){
  5. int mid=left+(right-left)/2;
  6. if(nums[mid]==target){
  7. return mid;
  8. }else if(nums[mid]>target){
  9. right=mid-1;
  10. }else{
  11. left=mid+1;
  12. }
  13. }
  14. return -1;
  15. }
  16. }

注意循环中的判断条件, 有等号

415.字符串相加

image.png

  1. class Solution {
  2. public String addStrings(String num1, String num2) {
  3. StringBuilder sb = new StringBuilder();
  4. int carry = 0, i = num1.length()-1, j = num2.length()-1;
  5. while(i >= 0 || j >= 0 || carry != 0){
  6. //这里i和j要判断是否大于等于0, 因为他们其中之一可能会小于0.
  7. if(i>=0) carry += num1.charAt(i--)-'0';
  8. if(j>=0) carry += num2.charAt(j--)-'0';
  9. sb.append(carry%10);
  10. carry /= 10;
  11. }
  12. return sb.reverse().toString();
  13. }
  14. }

69.x的平方根

利用二分法, 对于结果的整数部分k, 满足k2<=x, 那么我们就可以利用二分法来找结果, 如果mid2>x, 那么下界就等于mid, 反之同样的到里, 知道上界和下界的差值小于1, 算法结束, 因为一个数的平方根只会在两个相邻的整数之间

  1. class Solution {
  2. public int mySqrt(int x) {
  3. if(x==1){
  4. return 1;
  5. }
  6. int right=x;
  7. int left=0;
  8. while(right-left>1){
  9. int mid=left+(right-left)/2;
  10. if(mid>x/mid){
  11. //注意这里
  12. right=mid;
  13. }else{
  14. //注意这里
  15. left=mid;
  16. }
  17. }
  18. return left;
  19. }
  20. }

4.寻找两个正序数字的中位数

image.png
就是找第k个大的数
主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
这里的 “/“ 表示整除
nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
这样 pivot 本身最大也只能是第 k-1 小的元素
如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums1 数组
如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums2 数组
* 由于我们 “删除” 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数

  1. class Solution {
  2. public double findMedianSortedArrays(int[] nums1, int[] nums2) {
  3. int length1 = nums1.length, length2 = nums2.length;
  4. int totalLength = length1 + length2;
  5. //如果是奇数, 那么求得的是一个数
  6. if (totalLength % 2 == 1) {
  7. int midIndex = totalLength / 2;
  8. //下标从0开始, 所以是要+1, 第1大, 第2大...的=数
  9. double median = getKthElement(nums1, nums2, midIndex + 1);
  10. return median;
  11. } else {
  12. int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
  13. double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
  14. return median;
  15. }
  16. }
  17. //求第K大的数
  18. public int getKthElement(int[] nums1, int[] nums2, int k) {
  19. int length1 = nums1.length, length2 = nums2.length;
  20. int index1 = 0, index2 = 0;
  21. int kthElement = 0;
  22. while (true) {
  23. // 边界情况
  24. //如果nums1到头了, 说明nums2中的元素都比nums1大, 去nums2中寻找
  25. if (index1 == length1) {
  26. return nums2[index2 + k - 1];
  27. }
  28. if (index2 == length2) {
  29. return nums1[index1 + k - 1];
  30. }
  31. if (k == 1) {
  32. //求第1大的元素, 那么直接返回两个数组较小的首位元素
  33. return Math.min(nums1[index1], nums2[index2]);
  34. }
  35. // 正常情况
  36. int half = k / 2;
  37. int newIndex1 = Math.min(index1 + half, length1) - 1;
  38. int newIndex2 = Math.min(index2 + half, length2) - 1;
  39. int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
  40. if (pivot1 <= pivot2) {
  41. //nums1前面的(newIndex1-index1+1)个元素不可能是第K大的元素, 所以k减去相应的数目.
  42. k -= (newIndex1 - index1 + 1);
  43. index1 = newIndex1 + 1;
  44. } else {
  45. k -= (newIndex2 - index2 + 1);
  46. index2 = newIndex2 + 1;
  47. }
  48. }
  49. }
  50. }

8.字符串转换整数(atoi)

image.png

  1. class Solution {
  2. public int myAtoi(String s) {
  3. char[] arr=s.toLowerCase().toCharArray();
  4. int i=0;
  5. for(;i<arr.length;i++) {
  6. if(arr[i]!=' ') {
  7. break;
  8. }
  9. }
  10. boolean flag=false;
  11. if(i==arr.length||(arr[i]>='a'&&arr[i]<='z')||arr[i]=='.') {
  12. return 0;
  13. }
  14. if(arr[i]=='-') {
  15. flag=true;
  16. i++;
  17. }else if(arr[i]=='+'){
  18. flag=false;
  19. i++;
  20. }
  21. int sum=0;
  22. //sum=sum*10+digit>Integer.MAX_VALUE
  23. //sum>(Integer.MAX_VALUE-digit)/10,循环终止
  24. for(;i<arr.length&&arr[i]>='0'&&arr[i]<='9';i++) {
  25. if(sum>(Integer.MAX_VALUE-(arr[i]-'0'))/10) {
  26. if(flag) {
  27. return Integer.MIN_VALUE;
  28. }else {
  29. return Integer.MAX_VALUE;
  30. }
  31. }else {
  32. sum=sum*10+(arr[i]-'0');
  33. }
  34. }
  35. return flag? -sum:sum;
  36. }
  37. }

41.缺失的第一个正数

image.pngimage.png
如果没有第二行的限制, 其实很简单, 但是有了限制, 先否定了排序,因为时间复杂度, 然后否定了哈希数组或者其他的数组, 所以只能利用原来的数组进行操作.
长度为N的数组, 没有出现的最小正整数只可能出现在[1,N+1]中, 因为[1,N]都出现了, 那么最小的正整数就是N+1, 如果[1,N]有没出现的, 那么最小的正整数就是在[1,N]中了.
遍历元素的算法如下:

  • 先将数组中小于0的元素设为N+1的数
  • 再将数组中<=N的数 对应下标-1(数组从0开始)的数置为负数, 表示遍历的数出现过, 如果此位置的数已经是负数, 那么不动它
  • 从头遍历数组, 如果该数大于0, 那么表示不存在该下标, 那么就返回下标+1, 如果都小于0, 那么返回N+1

    1. class Solution {
    2. public int firstMissingPositive(int[] nums) {
    3. int n = nums.length;
    4. for (int i = 0; i < n; ++i) {
    5. if (nums[i] <= 0) {
    6. nums[i] = n + 1;
    7. }
    8. }
    9. for (int i = 0; i < n; ++i) {
    10. int num = Math.abs(nums[i]);
    11. if (num <= n) {
    12. nums[num - 1] = -Math.abs(nums[num - 1]);
    13. }
    14. }
    15. for (int i = 0; i < n; ++i) {
    16. if (nums[i] > 0) {
    17. return i + 1;
    18. }
    19. }
    20. return n + 1;
    21. }
    22. }

    31.下一个排序

    image.png
    下一个排列就是比当前排列大的最小排列组合.
    为了达到目标, 将左边的较小数与左边的较大数交换, 此时的排列比原排列大, 还要满足, 较小数尽可能的靠右, 较大数尽可能的小, 只比较小数大, 这样才能保证变大地幅度最小, 也就是前面说地最小排列.
    从后往前找较小值, 如果a[i]此时[a[i+1],a[n-1] ]为降序(因为 a[i]<a[i+1] ), 可以证明, 然后将其区间升序, 保证上升的幅度最小.

    1. class Solution {
    2. public void nextPermutation(int[] nums) {
    3. //先找最左的较小数, 从右往左找
    4. int left=0,right=nums.length-1,cur=nums.length-2;
    5. for (;cur>=0;cur--){
    6. if (nums[cur+1]>nums[cur]){
    7. left=cur;
    8. break;
    9. }
    10. }
    11. //如果存在, 否则说明数组为降序, 直接将其变为升序即可
    12. if (cur>=0){
    13. //在(left,n-1]中从后往前找到第一个比num[left]大的数, 即右边的较大数
    14. while(nums[right]<=nums[left]){
    15. right--;
    16. }
    17. //交换nums[left],nums[right]
    18. int temp=nums[left];
    19. nums[left]=nums[right];
    20. nums[right]=temp;
    21. //将(left,n-1]的数变为升序
    22. left++;
    23. right= nums.length-1;
    24. }
    25. while(left<right){
    26. int temp=nums[left];
    27. nums[left]=nums[right];
    28. nums[right]=temp;
    29. left++;
    30. right--;
    31. }
    32. }
    33. }

    151.颠倒字符串中的单词

    image.png
    先去除字符串前后的空格,去除字符串中间的空格,将字符串整体翻装, 将单词反转

    1. class Solution {
    2. public String reverseWords(String s) {
    3. //去掉其中的多余空格
    4. s=s.trim();
    5. //正则表达式表示一个或者多个空格
    6. s=s.replaceAll("[\s]+"," ");
    7. char[] arr=s.toCharArray();
    8. //将整个字符串翻装
    9. revers(arr,0,arr.length-1);
    10. int cur=0;
    11. while(cur<arr.length){
    12. int k=cur;
    13. //将单词翻转
    14. while(k+1<arr.length&&arr[k+1]!=' '){
    15. k++;
    16. }
    17. revers(arr,cur,k);
    18. cur=k+2;
    19. }
    20. return new String(arr);
    21. }
    22. public void revers(char[] arr,int l,int r){
    23. while(l<r){
    24. char temp=arr[l];
    25. arr[l]=arr[r];
    26. arr[r]=temp;
    27. l++;
    28. r--;
    29. }
    30. }
    31. }

    239.滑动窗口最大值

    image.png
    本来就是用的BF来解的, 每次算出窗口中的最大值, 但是超时了, 所以困难提题不能想的这么简单.
    利用到的是单调队列, 即队列中的数是单调递增或者递减的, 那么队列头上的值就是最大的值

其中利用到的单调队列, 其实并没有维护窗口中的全部元素, 而是维护了可能成为最大值的元素
新元素入队时, 与队尾的元素比较, 如果比队尾的元素大, 那么将队尾的元素出队, 这个元素与新入队的元素相比, 不可能成为最大的元素, 直到队尾的元素比新元素大或者等于新元素, 将新元素插到队尾.
窗口滑动后元素出队时, 就与队首元素比较, 因为队列是个单调队列, 队首元素是最大的元素, 如果移除的元素是队首的元素, 那么就将队首的元素出队, 如果与队首元素不相等, 那么就不用管.

  1. class Solution {
  2. public int[] maxSlidingWindow(int[] nums, int k) {
  3. int[] result=new int[nums.length-k+1];
  4. deque deque = new deque();
  5. for (int i=0;i<k;i++){ //先将前k个元素入队
  6. deque.push(nums[i]);
  7. }
  8. result[0]=deque.peek(); //求出前k个元素的最大值
  9. for (int i=1;i+k-1<nums.length;i++){ //处理后面的元素
  10. deque.poll(nums[i-1]); //将窗口移除的元素出队
  11. deque.push(nums[i+k-1]); //将窗口新增的元素入队
  12. result[i]=deque.peek(); //返回队列中最大的值
  13. }
  14. return result;
  15. }
  16. class deque{
  17. Deque<Integer> deque=new LinkedList<>();
  18. /*
  19. 出队操作, 如果窗口移除的元素值等于队列头的值, 那么移除队列的值
  20. */
  21. public void poll(int val){
  22. if (!deque.isEmpty()&&deque.peek()==val){
  23. deque.poll();
  24. }
  25. }
  26. /*
  27. 入队操作, 如果窗口新加入的值大于队列入口的值, 那么将入口元素弹出, 直到入口的元素大于新元素, 将其入队
  28. */
  29. void push(int val){
  30. while (!deque.isEmpty()&&deque.getLast()<val){
  31. deque.pollLast();
  32. }
  33. deque.addLast(val);
  34. }
  35. /*
  36. 队列头的元素是最大的元素, 返回最大的元素
  37. */
  38. int peek(){
  39. return deque.peek();
  40. }
  41. }
  42. }

76.最小覆盖子串

image.pngAlgorithm - 图17
本题利用滑动窗口解决. left指向窗口的左边界, right指向窗口的右边界, 每次只移动一边.

  1. 首先利用一个哈希表map_t表示字符串t中的字符, 键为字符, 值为字符出现的个数.
  2. 用一个哈希表保存字符串s中的字符, 处理字符串s, 如果右指针指向的字符map_t中存在, 那么map_s中对应的元素个数加一, 这样一直移动右指针, 直到map_s中包含map_t中的全部元素, 且个数不能小于map_t中的个数, 此时右指针到左指针中包含了t字符串中的所有字符, 那么收缩左指针
  3. 收缩左指针, 如果left<=right, 且map_s包含了map_t中的所有元素, 那么left++, 向右移动, 此时还需要几个变量来保存最小的窗口, 以及最小的窗口时left和right的值, 与最小值比较. left向右移动, 直到map_s不包含map_t中的元素, 然后右回到了第2步, 继续向右寻找能全部包含字符串t中的窗口

    1. class Solution {
    2. HashMap<Character, Integer> map_t = new HashMap<>(); //map_t来保存t的字符以及每个字符的个数
    3. HashMap<Character, Integer> map_s = new HashMap<>();
    4. public String minWindow(String s, String t) {
    5. //将t中de字符放入map_t中
    6. for (int i=0;i<t.length();i++){
    7. if (map_t.containsKey(t.charAt(i))){
    8. map_t.put(t.charAt(i),map_t.get(t.charAt(i))+1);
    9. }else{
    10. map_t.put(t.charAt(i),1);
    11. }
    12. }
    13. int left=0,right=-1,min_window=Integer.MAX_VALUE;//记录最小的子串长度
    14. int min_left=-1,min_right=-1; //记录最小字串的位置
    15. while (right<s.length()){
    16. right++;
    17. if (right<s.length()&&map_t.containsKey(s.charAt(right))){ //map_t中包含此字符
    18. map_s.put(s.charAt(right),map_s.getOrDefault(s.charAt(right),0)+1);//有就个数加一,没有就添加
    19. }
    20. while (check()&&left<=right){ //如果map_s中包含了map_t中所有的字符,个数也一样, 那么收缩左边界
    21. if (right-left+1<min_window){
    22. min_window=right-left+1;
    23. min_right=right;
    24. min_left=left;
    25. }
    26. if (map_t.containsKey(s.charAt(left))){ //如果map_t中包含左边界去除的元素,那么map_s中元素的值的对应减1
    27. map_s.put(s.charAt(left),map_s.getOrDefault(s.charAt(left),0)-1);
    28. }
    29. left++;
    30. }
    31. }
    32. return min_window==Integer.MAX_VALUE? "":s.substring(min_left,min_right+1);
    33. }
    34. public boolean check(){ //检查map_s中是否包含map_t中所有的字符
    35. Iterator<Map.Entry<Character, Integer>> iterator = map_t.entrySet().iterator();
    36. while(iterator.hasNext()){
    37. Map.Entry<Character,Integer> entry = iterator.next();
    38. Character key = entry.getKey();
    39. Integer value=entry.getValue();
    40. if (map_s.getOrDefault(key,0)<value){ //如果map_s的某一个字符的个数小于map_t对应字符的个数, 那么返回错误
    41. return false;
    42. }
    43. }
    44. return true;
    45. }
    46. }

    下面的代码更加简介, 没有用到哈希表保存, 而是利用了数组, 将字符作为索引

    1. public class Solution {
    2. public String minWindow(String s, String t) {
    3. char[] cs = s.toCharArray();
    4. char[] ct = t.toCharArray();
    5. int[] a = new int[128];
    6. //字符对应数组的下标
    7. for(int ch : ct){
    8. a[ch]--;
    9. }
    10. int left = 0,right = 0,len=0;
    11. String res = "";
    12. while (right < s.length()){
    13. //右边界指向的字符对应的下标数+1, 表示右边界字符存入数组中,对应的数+1
    14. a[cs[right]]++;
    15. //如果a[cs[right]]<=0, 说明t中包含此字符
    16. if(a[cs[right]] <= 0){
    17. len++;
    18. }
    19. while (len == t.length() && a[cs[left]] > 0){
    20. a[cs[left++]]--;
    21. }
    22. if(len == t.length()){
    23. if(res == "")
    24. res = s.substring(left,right+1);
    25. else if(res.length() > right-left+1){
    26. res = s.substring(left,right+1);
    27. }
    28. }
    29. right++;
    30. }
    31. return res;
    32. }
    33. }

    20.有效的括号

    image.png

    1. class Solution {
    2. public boolean isValid(String s) {
    3. int n = s.length();
    4. if (n % 2 == 1) {
    5. return false;
    6. }
    7. Map<Character, Character> pairs = new HashMap<Character, Character>() {{
    8. put(')', '(');
    9. put(']', '[');
    10. put('}', '{');
    11. }};
    12. Deque<Character> stack = new LinkedList<Character>();
    13. for (int i = 0; i < n; i++) {
    14. char ch = s.charAt(i);
    15. if (pairs.containsKey(ch)) {
    16. if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
    17. return false;
    18. }
    19. stack.pop();
    20. } else {
    21. stack.push(ch);
    22. }
    23. }
    24. return stack.isEmpty();
    25. }
    26. }

    1.两数之和
    image.png
    利用哈希表降低复杂度, 将target-nums[i]放到哈希表中.

    1. class Solution {
    2. public int[] twoSum(int[] nums, int target) {
    3. Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
    4. for (int i = 0; i < nums.length; ++i) {
    5. if (hashtable.containsKey(target - nums[i])) {
    6. return new int[]{hashtable.get(target - nums[i]), i};
    7. }
    8. hashtable.put(nums[i], i);
    9. }
    10. return new int[0];
    11. }
    12. }

    15.三数之和

    image.png
    先将数组排序, 然后利用双指针, 遍历, 还有一个外循环控制从左往右找到所有的三元组.

    1. class Solution {
    2. public List<List<Integer>> threeSum(int[] nums) {
    3. //首先对数组进行排序,利用希尔排序
    4. for(int gap=nums.length/2;gap>0;gap/=2){
    5. for(int i=gap;i<nums.length;i++){
    6. int temp=nums[i];
    7. while(i-gap>=0&&nums[i-gap]>temp){
    8. nums[i]=nums[i-gap];
    9. i-=gap;
    10. }
    11. nums[i]=temp;
    12. }
    13. }
    14. List<List<Integer>> list1=new ArrayList<>();
    15. //利用双指针
    16. for(int i=0;i<nums.length-1;i++){ //下标从左往右
    17. for(int l=i+1,r=nums.length-1;l<r;){
    18. if(nums[i]+nums[l]+nums[r]==0){
    19. List<Integer> list2=new ArrayList<>();
    20. list2.add(nums[i]);
    21. list2.add(nums[l]);
    22. list2.add(nums[r]);
    23. list1.add(list2);
    24. l++;
    25. r--;
    26. }else if(nums[i]+nums[l]+nums[r]>0){
    27. r--;
    28. }else{
    29. l++;
    30. }
    31. }
    32. }
    33. return list1;
    34. }
    35. }

    215.数组中的第K个最大元素

    image.png
    普通做法是, 利用快速排序, 将数组降序排好序, 返回下标为k-1的元素.
    但是快速排序的实质是将一个元素归位, 对于降序排序, 它的左边元素都比它大, 它右边的元素都比它小, 此时它的下标已经确定了为i, 我们只是关心第K大的元素, 它左边或者右边的元素并不关心, 如果i大于K-1, 说明它不是第K大的元素, 而是第i+1大的元素, 比K小, 所以要在它的左边去找, 左边的元素都比它大, 所以放弃右边的元素, 这样能加速找到.
    注意第9行加上了等于

    1. class Solution {
    2. int result=0;
    3. public int findKthLargest(int[] nums, int k) {
    4. fun(nums, 0, nums.length - 1, k);
    5. return result;
    6. }
    7. public void fun(int[] nums,int left,int right,int k){
    8. if (left<=right){
    9. int l=left,r=right;
    10. int temp=nums[l];
    11. //将大于nums[l]的元素和小于nums[l]的元素分别放到它的左右边, 利用快速排序的思想
    12. while (l<r){
    13. while(l<r&&nums[r]<=temp){ //降序排列
    14. r--;
    15. }
    16. nums[l]=nums[r];
    17. while (l<r&&nums[l]>=temp){
    18. l++;
    19. }
    20. nums[r]=nums[l];
    21. }
    22. nums[l]=temp;
    23. //如果nums[l]就是第k大的元素, 那么直接返回
    24. if (k-1==l){
    25. result=nums[l];
    26. return ;
    27. }else if(k-1>l){//如果第k大的元素在nums[l]的右边, 那么就到右边寻找
    28. fun(nums,l+1,right,k);
    29. }else {
    30. fun(nums,left,l-1,k);
    31. }
    32. }
    33. }
    34. }

    3.无重复字符的最长子串

    image.png

    1. class Solution {
    2. public int lengthOfLongestSubstring(String s) {
    3. // 哈希集合,记录每个字符是否出现过
    4. Set<Character> occ = new HashSet<Character>();
    5. int n = s.length();
    6. // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
    7. int rk = -1, ans = 0;
    8. for (int i = 0; i < n; ++i) {
    9. if (i != 0) {
    10. // 左指针向右移动一格,移除一个字符
    11. occ.remove(s.charAt(i - 1));
    12. }
    13. while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
    14. // 不断地移动右指针
    15. occ.add(s.charAt(rk + 1));
    16. ++rk;
    17. }
    18. // 第 i 到 rk 个字符是一个极长的无重复字符子串
    19. ans = Math.max(ans, rk - i + 1);
    20. }
    21. return ans;
    22. }
    23. }

    155.最小栈

    image.png
    两种方法, 一种是使用辅助栈, 栈中保存最小的元素, 第二种是将最小值一起保存在一个栈中。

    1. //第一种方法
    2. class MinStack {
    3. Stack<Integer> min_stack=new Stack<>();
    4. Stack<Integer> stack=new Stack<>();
    5. public MinStack() {
    6. min_stack.push(Integer.MAX_VALUE);
    7. }
    8. public void push(int val) {
    9. stack.push(val);
    10. min_stack.push(Math.min(min_stack.peek(),val));
    11. }
    12. public void pop() {
    13. stack.pop();
    14. min_stack.pop();
    15. }
    16. public int top() {
    17. return stack.peek();
    18. }
    19. public int getMin() {
    20. return min_stack.peek();
    21. }
    22. }
    23. //第二张方法
    24. class MinStack {
    25. Stack<Integer> stack=new Stack<>();
    26. /** initialize your data structure here. */
    27. public MinStack() {
    28. }
    29. public void push(int val) {
    30. if(stack.isEmpty()){
    31. stack.push(val);
    32. stack.push(val);
    33. }else{
    34. int temp=stack.peek();
    35. stack.push(val);
    36. if(temp<val)
    37. stack.push(temp);
    38. else
    39. stack.push(val);
    40. }
    41. }
    42. public void pop() {
    43. stack.pop();
    44. stack.pop();
    45. }
    46. public int top() {
    47. return stack.get(stack.size()-2);
    48. }
    49. public int getMin() {
    50. return stack.peek();
    51. }
    52. }

    32.最长有效括号

    image.png
    先用栈处理一边字符串, 将其中的无效括号设为1, 有效括号设为0, 然后从中寻找最长连续0

    1. class Solution {
    2. public int longestValidParentheses(String s) {
    3. Stack<Integer> stack = new Stack<>();
    4. int[] flag = new int[s.length()];
    5. for (int i = 0; i < s.length(); i++) {
    6. //只将左括号入栈
    7. if (s.charAt(i) == '(') {
    8. stack.push(i);
    9. } else {
    10. //如果栈中为空, 那么此右括号无效
    11. if (stack.isEmpty()) {
    12. flag[i] = 1;
    13. //否则栈中只有左括号, 出栈一个左括号
    14. } else {
    15. stack.pop();
    16. }
    17. }
    18. }
    19. //如果栈中还有括号, 那么都为无效的括号
    20. while (!stack.isEmpty()) {
    21. flag[stack.pop()] = 1;
    22. }
    23. int max_length = 0;
    24. int length = 0;
    25. for (int i = 0; i < flag.length; i++) {
    26. if (flag[i] == 1) {
    27. length = 0;
    28. continue;
    29. }
    30. length++;
    31. max_length = Math.max(length, max_length);
    32. }
    33. return max_length;
    34. }

    43.字符串相乘

    image.pngimage.png

    1. class Solution {
    2. public String multiply(String num1, String num2) {
    3. if(num1.equals("0")||num2.equals("0")){
    4. return "0";
    5. }
    6. int length_1=num1.length();
    7. int length_2=num2.length();
    8. String ans="0";
    9. for (int i=length_1-1;i>=0;i--){
    10. StringBuilder sub_sb = new StringBuilder();
    11. int carry=0;
    12. //需要将后面补零, 也就是将前面补零.
    13. for(int j=length_1-1;j>i;j--){
    14. sub_sb.append(0);
    15. }
    16. int int_num1=num1.charAt(i)-'0';
    17. for (int j=length_2-1;j>=0;j--){
    18. int int_num2=num2.charAt(j)-'0';
    19. int sub_result=int_num1*int_num2+carry;
    20. sub_sb.append(sub_result%10);
    21. carry=sub_result/10;
    22. }
    23. if (carry!=0){
    24. sub_sb.append(carry);
    25. }
    26. ans=addString(ans,sub_sb.reverse().toString());
    27. }
    28. return ans;
    29. }
    30. //将相乘后的字符串相加
    31. public String addString(String num1,String num2){
    32. int i=num1.length()-1,j=num2.length()-1,caryy=0;
    33. StringBuilder sb = new StringBuilder();
    34. while (i>=0||j>=0||caryy!=0){
    35. int x= i>=0? num1.charAt(i)-'0':0;
    36. int y= j>=0? num2.charAt(j)-'0':0;
    37. int result=x+y+caryy;
    38. sb.append(result%10);
    39. caryy=result/10;
    40. i--;
    41. j--;
    42. }
    43. return sb.reverse().toString();
    44. }
    45. }

    165.比较版本号

    image.png
    先分割, 然后利用Integer.valueOf方法将每一组转为整数比较, 注意读题, 每个修改号都可以转为32位整数

    1. class Solution {
    2. public int compareVersion(String version1, String version2) {
    3. String[] strings1 = version1.split("\\.");
    4. String[] strings2 = version2.split("\\.");
    5. int i=0;
    6. while (i<strings1.length&&i<strings2.length){
    7. int temp1=Integer.valueOf(strings1[i]);
    8. int temp2=Integer.valueOf(strings2[i]);
    9. if(temp1>temp2){
    10. return 1;
    11. }else if (temp1<temp2){
    12. return -1;
    13. }
    14. i++;
    15. }
    16. //string1已经遍历完成
    17. if(i==strings1.length){
    18. for (;i<strings2.length;i++) {
    19. if (Integer.valueOf(strings2[i])!=0){
    20. return -1;
    21. }
    22. }
    23. }else {
    24. for (;i<strings1.length;i++) {
    25. if (Integer.valueOf(strings1[i])!=0){
    26. return 1;
    27. }
    28. }
    29. }
    30. return 0;
    31. }
    32. }

    470.用Rand7()实现Rand10()

    image.png

    1. class Solution extends SolBase {
    2. public int rand10() {
    3. //思路:使用ran7在[0,6]直接选一个数 使用ran7在[0,5]之间选一个数
    4. int a=rand7();int b=rand7();
    5. while(a==7) a=rand7();//a不能为7 必须为[0,6]这样才能保证奇偶都是1/2概率
    6. while(b>5) b=rand7();//b不能为5以上 因为一会可能要加5
    7. return ((a&1)==0? 0:5 )+b;//判断a奇偶性1/2 * b取值[0,5]之间1/5=1/10
    8. }
    9. }

    169.多数元素

    image.png
    多数元素超过了n/2, 就从头开始遍历, 如果与target元素一样, 那么count+1, 否则count-1, 如果减到了0, 那么target等于下一个元素, 遍历完之后, target就指向多数元素.
    这种过程可以抽象为 大乱斗, 有很多的国家参加, 遇到自己的人, 人数+1, 如果遇到不是自己的人, 那么人数就-1, 默认一换一, 那么厮杀过后, 剩下的肯定是多数元素.

    1. class Solution {
    2. public int majorityElement(int[] nums) {
    3. int target=nums[0];
    4. int count=1;
    5. for(int i=1;i<nums.length;i++){
    6. if(nums[i]==target){
    7. count++;
    8. }else{
    9. count--;
    10. if(count==0){
    11. target=nums[i+1];
    12. }
    13. }
    14. }
    15. return target;
    16. }
    17. }

    34.在排序数组中查找元素的第一个和最后一个位置

    image.png

    1. class Solution {
    2. public int[] searchRange(int[] nums, int target) {
    3. int[] result=new int[2];
    4. result[0]=-1;
    5. result[1]=-1;
    6. // //基础解法,从头开始遍历
    7. // for(int i=0;i<nums.length;i++){
    8. // if(nums[i]==target){
    9. // if(result[0]==-1){
    10. // result[0]=i;
    11. // result[1]=i;
    12. // }else{
    13. // result[1]=i;
    14. // }
    15. // }
    16. // }
    17. // return result;
    18. /**
    19. 进阶,时间复杂度为O(logn)考虑二分法解决,查找左右边界
    20. */
    21. //true表示左边界
    22. result[0]=fun(nums,target,true);
    23. result[1]=fun(nums,target,false);
    24. return result;
    25. }
    26. public int fun(int[] nums,int target,boolean flag){
    27. int res=-1;
    28. int l=0,r=nums.length-1;
    29. while(l<=r){
    30. int mid=l+(r-l)/2;
    31. if(target>nums[mid]){
    32. l=mid+1;
    33. }else if(target<nums[mid]){
    34. r=mid-1;
    35. }else{
    36. res=mid;
    37. if(flag==true){//如果找到目标数, 如果是左边界, 那么往左继续查找
    38. r=mid-1;
    39. }else{
    40. l=mid+1;
    41. }
    42. }
    43. }
    44. return res;
    45. }
    46. }

    14.最长公共前缀

    image.png

    1. public static String longestCommonPrefix(String[] strs) {
    2. //初始值等于第一个
    3. String s=strs[0];
    4. for(String string:strs){
    5. //如果遍历的字符串不以初始值开始, 那么初始值就从末尾减少一个, 直至以初始值开头
    6. while(!string.startsWith(s)){
    7. s=s.substring(0,s.length()-1);
    8. }
    9. if (!string.startsWith(s))
    10. break;
    11. }
    12. return s;
    13. }

    48.旋转图像

    image.png

    1. class Solution {
    2. public void rotate(int[][] matrix) {
    3. //先水平反转
    4. for (int i=0;i<matrix.length/2;i++){
    5. for (int j=0;j<matrix[0].length;j++){
    6. int temp=matrix[i][j];
    7. matrix[i][j]=matrix[matrix.length-i-1][j];
    8. matrix[matrix.length-i-1][j]=temp;
    9. }
    10. }
    11. //然后对角线反转
    12. for (int i=0;i<matrix.length;i++){
    13. for (int j=0;j<i;j++){
    14. int temp=matrix[i][j];
    15. matrix[i][j]=matrix[j][i];
    16. matrix[j][i]=temp;
    17. }
    18. }
    19. }
    20. }

    128.最长连续序列

    image.png
    时间复杂度是O(n),使用Array.sort()肯定是不行的,其利用的是快速排序和归并排序,时间复杂度都是nlogn,不符合。
    对于题目对时间复杂度有要求的,那么就用空间去换取时间,反之亦然。
    这里使用HashSet来保存其中的数组,注意第三行先要将nums数组转换为包装类,再转为set
    遍历到一个元素,那么就判断x,x+1,x+2……是否连续,这样复杂度还是O(n2),如果已知了x,x+1,x+2…x+y连续,当遍历到一个元素的时候,却重新从x+1,x+2…x+y开始匹配,得到的结果肯定没有从x开始枚举的结果好,所以当遇到一个元素m,如果m-1在set中不存在,则从m开始枚举,也就是说当m是连续数组中的第一个数时,才开始枚举。外层的复杂度为O(n),只有当一个数是连续序列中的第一个数时才进入内循环,所以每个数只会进入内存循环一次,因为如果一个数是某个连续序列中的数(不是首个元素),那么这个数跳过了。

    1. class Solution {
    2. public int longestConsecutive(int[] nums) {
    3. Set<Integer> set = Arrays.stream(nums).boxed().collect(Collectors.toSet());
    4. int max_length=0;
    5. for (Integer integer : set) {
    6. if (!set.contains(integer-1)){
    7. int temp=integer;
    8. int length=1;
    9. while (set.contains(temp+1)){
    10. temp++;
    11. length++;
    12. }
    13. max_length=Math.max(length,max_length);
    14. }
    15. }
    16. return max_length;
    17. }
    18. }

    153.寻找旋转排序数组中的最小值

    image.png
    数组是旋转过的, 题目是要求O(logn)的时间复杂度, 那么就是二分法, 二分法对部分有序的数组同样有效的. 那么对于这道题, 要先找到无序的一半和有序的一半, 那么如何找到? 经过观察发现, 如果得到的中间元素比最右边的元素大, 那么说明右边时无序的, 左边是有序的, 这个可以很容易的发现, 那么找到了有序和无序的一半, 那么最小值在那一边, 一定是无序的那一边, 可以得到如下的算法, 注意第10行, right=mid, 返回的也是左边的元素

    1. class Solution {
    2. public int findMin(int[] nums) {
    3. int left=0,right=nums.length-1;
    4. while (left < right) {
    5. int mid=(left+right)/2;
    6. //表明中间的数比最右边的数大,那么右边是无序的,最小值在其中
    7. if (nums[mid]>nums[right]){
    8. left=mid+1;
    9. }else {
    10. //可能当前元素nums[mid]就是最小的元素,不能是right=mid-1
    11. right=mid;
    12. }
    13. }
    14. return nums[left];
    15. }
    16. }

    227.基本计算器2

    image.png
    用一个栈保存当前的结果, 如果是+那么就将后面的数字直接入栈, 如果是-那么就将num取反压栈, 如果是*|/那么就取栈顶元素与num相乘或者相除后入栈, 利用一个preSign来保存先前的一个符号, 根据先前的符号来决定num的

    1. class Solution {
    2. public int calculate(String s) {
    3. char[] chars = s.toCharArray();
    4. Stack<Integer> stack=new Stack<>();
    5. int num=0;
    6. char preSign='+';
    7. for (int i=0;i<chars.length;i++){
    8. //如果是一个数字, 那么就加上
    9. if (Character.isDigit(chars[i])){
    10. num=num*10+chars[i]-'0';
    11. }
    12. //如果是符号,或者是最后一位, 将前面的数字根据先前的符号压栈
    13. if (!Character.isDigit(chars[i])&&chars[i]!=' '||i== chars.length-1){
    14. switch (preSign){
    15. case '+':
    16. stack.push(num);
    17. break;
    18. case '-':
    19. stack.push(-num);
    20. break;
    21. case '*':
    22. //如果先前的符号是'*', 那么就将本位与栈顶元素相乘后入栈
    23. stack.push(stack.pop()*num);
    24. break;
    25. case '/':
    26. stack.push(stack.pop()/num);
    27. break;
    28. }
    29. preSign=chars[i];
    30. num=0;
    31. }
    32. }
    33. int ans=0;
    34. while (!stack.isEmpty()){
    35. ans+=stack.pop();
    36. }
    37. return ans;
    38. }
    39. }

    468.验证IP地址

    image.png

    1. class Solution {
    2. public String validIPAddress(String IP) {
    3. if (IP == null) {
    4. return "Neither";
    5. }
    6. String regex0 = "(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])";
    7. String regexIPv4 = regex0 + "(\\." + regex0 + "){3}";
    8. String regex1 = "([\\da-fA-F]{1,4})";
    9. String regexIPv6 = regex1 + "(:" + regex1 + "){7}";
    10. String result = "Neither";
    11. if (IP.matches(regexIPv4)) {
    12. result = "IPv4";
    13. } else if (IP.matches(regexIPv6)) {
    14. result = "IPv6";
    15. }
    16. return result;
    17. }
    18. }

    "(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])"依次匹配1位,2位,3位的子段
    String regexIPv4 = regex0 + "(\\." + regex0 + "){3}"有三个., 所以要分开写

    179.最大数

    image.png
    自定义比较器, 比较s1+s2和s2+s1的大小

    1. class Solution {
    2. public String largestNumber(int[] nums) {
    3. int len_ = nums.length;
    4. String[] ss = new String[len_];
    5. for(int i=0; i<len_; i++){
    6. ss[i] = String.valueOf(nums[i]);
    7. }
    8. Arrays.sort(ss,(o1,o2)->(o2 + o1).compareTo(o1 + o2));
    9. StringBuilder sb = new StringBuilder();
    10. for(String s : ss) sb.append(s);
    11. return sb.charAt(0) == '0' ? "0" : sb.toString();
    12. }
    13. }
    14. //自己写的
    15. public String largestNumber(int[] nums) {
    16. Integer[] integers = Arrays.stream(nums).boxed().toArray(Integer[]::new);
    17. Arrays.sort(integers,(i, j)->{
    18. String s1=String.valueOf(i);
    19. String s2=String.valueOf(j);
    20. int sum1=Integer.valueOf(s1+s2);
    21. int sum2=Integer.valueOf(s2+s1);
    22. return sum2-sum1;
    23. });
    24. StringBuilder stringBuilder = new StringBuilder();
    25. for (Integer num : integers) {
    26. stringBuilder.append(num);
    27. }
    28. return stringBuilder.charAt(0)=='0'? "0":stringBuilder.toString();
    29. }

    136. 只出现一次的数字

    image.pngimage.png

  1. class Solution {
  2. public int singleNumber(int[] nums) {
  3. int result = 0;;
  4. for(int x : nums){
  5. result ^= x;
  6. }
  7. return result;
  8. }
  9. }

138. 复制带随机指针的链表

image.png
建立原节点和克隆节点的映射

  1. public Node copyRandomList(Node head) {
  2. Node cur=head;
  3. HashMap<Node, Node> map = new HashMap<>();
  4. while (cur !=null){
  5. Node temp=new Node(cur.val);
  6. map.put(cur,temp);
  7. cur=cur.next;
  8. }
  9. cur=head;
  10. while (cur!=null){
  11. map.get(cur).next=map.get(cur.next);
  12. map.get(cur).random=map.get(cur.random);
  13. cur=cur.next;
  14. }
  15. return map.get(head);
  16. }

209. 长度最小的子数组

image.png
利用滑动窗口

  1. public int minSubArrayLen(int target, int[] nums) {
  2. int i=0;
  3. int length=0;
  4. int sum=0;
  5. for (int j=0;j<nums.length;j++){
  6. sum+=nums[j];
  7. while (sum>=target){
  8. length= length==0? j-i+1:Math.min(length,j-i+1);
  9. sum-=nums[i++];
  10. }
  11. }
  12. return length;
  13. }
  14. }

283.移动零

image.png
设置一个index, 往后遍历, 不为0的设置在前面, 然后 将index之后的元素置零.

  1. public void moveZeroes(int[] nums) {
  2. int index=0;
  3. for (int i = 0; i < nums.length; i++) {
  4. if (nums[i]!=0){
  5. nums[index++]=nums[i];
  6. }
  7. }
  8. for (;index<nums.length;index++){
  9. nums[index]=0;
  10. }
  11. }

LinkedList

142.环形联表Ⅱ

image.png

Algorithm - 图44
fast的速度是slow的两倍, 所以他们相遇时, fast走过的节点数是slow走过的两倍, 所以有一下等式
2(A+B)=A+2B+C
A=C

  1. /**
  2. * Definition for singly-linked list.
  3. * class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode(int x) {
  7. * val = x;
  8. * next = null;
  9. * }
  10. * }
  11. */
  12. public class Solution {
  13. public ListNode detectCycle(ListNode head) {
  14. ListNode slow=head,fast=head;
  15. while(head!=null&&fast.next!=null&&fast.next.next!=null&&slow.next!=null){
  16. slow=slow.next;
  17. fast=fast.next.next;
  18. if(fast==slow){
  19. //如果快慢指针相遇, 那么就让慢指针指向头
  20. slow=head;
  21. //这里就是利用了上面得到的结论A=C, 让快慢指针速度一样, 那么就可以找到目标结点
  22. while(slow!=fast){
  23. slow=slow.next;
  24. fast=fast.next;
  25. }
  26. return slow;
  27. }
  28. }
  29. return null;
  30. }
  31. }

92.反转链表

image.png image.png
可以看作是反转链表, 但是这题却是指定开始和结束的地方, 那么思路就是先找到开始的地方和结束的地方, 然后将开始到结束的地方完成翻转, 再将与前后的节点连接

  1. //方法一
  2. class Solution {
  3. public ListNode reverseBetween(ListNode head, int left, int right) {
  4. ListNode shit=new ListNode(-1);
  5. shit.next=head;
  6. //首先切割链表
  7. ListNode cur=shit;
  8. ListNode pre=null;
  9. ListNode tail=null;
  10. ListNode start=null;
  11. //找到开始结点的前一个节点
  12. int i=0;
  13. for(;i<left-1;i++){
  14. cur=cur.next;
  15. }
  16. pre=cur;
  17. //反转子链表的开始节点
  18. start=cur.next;
  19. //找到right的下一个节点
  20. for(;i<right;i++){
  21. cur=cur.next;
  22. }
  23. tail=cur.next;
  24. //断开链表
  25. pre.next=null;
  26. cur.next=null;
  27. ListNode temp=fun(start);
  28. pre.next=temp;
  29. start.next=tail;
  30. return shit.next;
  31. }
  32. public ListNode fun(ListNode head){
  33. if(head==null){
  34. return null;
  35. }
  36. if(head.next==null){
  37. return head;
  38. }
  39. ListNode temp=fun(head.next);
  40. head.next.next=head;
  41. head.next=null;
  42. return temp;
  43. }
  44. }
  45. //方法二 头插法
  46. /**
  47. * Definition for singly-linked list.
  48. * public class ListNode {
  49. * int val;
  50. * ListNode next;
  51. * ListNode() {}
  52. * ListNode(int val) { this.val = val; }
  53. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  54. * }
  55. */
  56. class Solution {
  57. public ListNode reverseBetween(ListNode head, int left, int right) {
  58. /**
  59. 采用头插法
  60. */
  61. ListNode dummyNode=new ListNode(-1);//临时的节点添加到头部, 避免复杂情况
  62. dummyNode.next=head;
  63. int i=0;
  64. ListNode cur=dummyNode;
  65. //找到left节点上一个节点
  66. while(i<left-1){
  67. cur=cur.next;
  68. i++;
  69. }
  70. //用一个节点先保存left的前一个节点
  71. ListNode temp=cur;
  72. cur=cur.next;
  73. i++;
  74. ListNode start=cur;
  75. ListNode nex=cur.next;
  76. ListNode pre=null;
  77. //退出循环的条件是cur指向right节点
  78. while(i<=right){
  79. cur.next=pre;
  80. pre=cur;
  81. cur=nex;
  82. nex=nex==null? null:nex.next;
  83. i++;
  84. }
  85. start.next=cur;
  86. temp.next=pre;
  87. return dummyNode.next;
  88. }
  89. }

23.合并K个升序链表

image.png
此题是链表数组, 数组中储存的都是链表的头节点. 开始的想法是利用N个指针, 指向头节点, 然后依次判断大小, 将小的头节点加入到新的节点当中去, 但是这样很复杂, 如果有X个链表, 那么每次的大小比较都要比较X个.
所以还是看了题解, 题解的思路是 利用优先队列, 相当于最小堆, 先将所有的头节点入队, 然后取出队列头的元素, 取出的元素就是队列中最小的, 将他加入新链表, 然后将他的后继节点加入队列. 这样循环下去, 直到队列中没有了元素.

  1. public static ListNode fun(ListNode[] lists) {
  2. //创建优先队列, 利用优先队列将其中的值排列,
  3. PriorityQueue<ListNode> priorityQueue=new PriorityQueue<ListNode>(new Comparator<ListNode>() {
  4. @Override
  5. public int compare(ListNode o1, ListNode o2) {
  6. return o1.val-o2.val;
  7. }
  8. });
  9. //将链表数组中的头节点入队
  10. for (ListNode list : lists) {
  11. if(list!=null) {
  12. priorityQueue.add(list);
  13. }
  14. }
  15. ListNode dummyList = new ListNode(-1);
  16. ListNode cur=dummyList;
  17. while (!priorityQueue.isEmpty()){
  18. ListNode temp=priorityQueue.poll();
  19. if(temp.next!=null){
  20. priorityQueue.add(temp.next);
  21. }
  22. cur.next=temp;
  23. cur=temp;
  24. }
  25. return dummyList.next;
  26. }

143.重排链表

image.pngimage.png
虽然是中等题,但是考差了三个基本操作,非常好的一个题,考查了

  1. 利用快慢指针找到中间节点
  2. 反转链表
  3. 合并两个链表

本题的思路就是按照上面的步骤来解题的

  1. class Solution {
  2. /*
  3. 1. 先利用快慢指针找到中间的节点
  4. 2. 再将后半段的链表反转
  5. 3. 将前后链表合并
  6. */
  7. public void reorderList(ListNode head) {
  8. if(head==null||head.next==null||head.next.next==null){
  9. return;
  10. }
  11. //找到中间的节点
  12. ListNode mid=mid(head);
  13. ListNode right=mid.next;
  14. mid.next=null;
  15. mid=head;
  16. //将右边的节点反转
  17. right=revers(right);
  18. //合并两个链表
  19. merge(mid,right);
  20. }
  21. public ListNode mid(ListNode head){
  22. ListNode fast=head;
  23. ListNode slow=head;
  24. //找到中间的节点, slow指向中间的节点
  25. while(fast!=null&&fast.next!=null){
  26. fast=fast.next.next;
  27. slow=slow.next;
  28. }
  29. return slow;
  30. }
  31. //采用头插法反转链表
  32. public ListNode revers(ListNode head){
  33. ListNode next=null;
  34. ListNode pre=null;
  35. ListNode cur=head;
  36. while(cur!=null){
  37. next=cur.next;
  38. cur.next=pre;
  39. pre=cur;
  40. cur=next;
  41. }
  42. return pre;
  43. }
  44. public void merge(ListNode left,ListNode right){
  45. ListNode left_next;
  46. ListNode right_next;
  47. while(left!=null&&right!=null){
  48. left_next=left.next;
  49. right_next=right.next;
  50. left.next=right;
  51. left=left_next;
  52. right.next=left;
  53. right=right_next;
  54. }
  55. }
  56. }

应该注意第42行,之前都是将其放在循环的最后,会导致一些问题

19.删除链表的倒数第N个结点

image.png
利用快慢指针, 先让快指针走n步, 然后快慢指针一起走, 知道快指针为空

  1. class Solution {
  2. public ListNode removeNthFromEnd(ListNode head, int n) {
  3. ListNode fast=head,slow=head;
  4. for(int i=0;i<n;i++){
  5. fast=fast.next;
  6. }
  7. //fast到了末尾, 说明删除的是头结点, 那么返回head.next
  8. if(fast==null){
  9. return head.next;
  10. }
  11. while(fast!=null&&fast.next!=null){
  12. slow=slow.next;
  13. fast=fast.next;
  14. }
  15. slow.next=slow.next.next;
  16. return head;
  17. }
  18. }

82.删除排序列表中的重复元素

image.png
此题是删除重复的元素, 重复的元素全部删除, 而不是保存一个, 和一个题有点类似, 注意区分
next指向head的下一个节点,如果head的值和next值相等,那么next向后寻找,直到next的值和head的值不一样,此时head也要舍弃,则head=fun(next)
如果head的值和next值不相等,那么head.next=fun(next),处理下一个,head保留。

  1. class Solution {
  2. public ListNode deleteDuplicates(ListNode head) {
  3. if(head==null||head.next==null){
  4. return head;
  5. }
  6. ListNode next=head.next;
  7. if(head.val==next.val){
  8. while(next!=null&&head.val==next.val){
  9. next=next.next;
  10. }
  11. head=deleteDuplicates(next);
  12. }else{
  13. head.next=deleteDuplicates(next);
  14. }
  15. return head;
  16. }
  17. }

148.排序链表

image.pngimage.png
使用归并排序,先找到链表的中点,处理右边的列表,然后处理左边的链表,然后将两边合并起来返回

  1. class Solution {
  2. public ListNode sortList(ListNode head) {
  3. return mergeSort(head);
  4. }
  5. public ListNode mergeSort(ListNode head){
  6. if(head==null||head.next==null){
  7. return head
  8. }
  9. //先利用快慢指针找到中间节点
  10. ListNode slow=head,fast=head.next.next;
  11. while(fast!=null&&fast.next!=null){
  12. fast=fast.next.next;
  13. slow=slow.next;
  14. }
  15. //slow指向中间节点
  16. //对右边进行归并
  17. ListNode r=mergeSort(slow.next);
  18. slow.next=null;
  19. ListNode l=mergeSort(head);
  20. return mergeNode(l,r);
  21. }
  22. public ListNode mergeNode(ListNode left,ListNode right){
  23. ListNode dummyNode=new ListNode(-1);
  24. ListNode cur=dummyNode;
  25. while(left!=null&&right!=null){
  26. if(left.val<right.val){
  27. cur.next=left;
  28. left=left.next;
  29. }else{
  30. cur.next=right;
  31. right=right.next;
  32. }
  33. cur=cur.next;
  34. }
  35. //处理剩下的节点
  36. cur.next= left==null? right:left;
  37. return dummyNode.next;
  38. }
  39. }

注意第10行是fast=fast.next.next, 而不是fast=head, 这样会造成错误, 当只有两个节点时,slow指向最后一个节点,slow.next为null,然后处理左边的时候就造成了死递归了,因为第一个节点的next没有断开。

2.两数相加

image.png

  1. class Solution {
  2. public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
  3. int flag=0;//表示进位
  4. ListNode dummyNode=new ListNode(-1);
  5. ListNode cur=dummyNode;
  6. while(l1!=null||l2!=null||flag==1){
  7. int sum=0;
  8. if(l1!=null){
  9. sum+=l1.val;
  10. l1=l1.next;
  11. }
  12. if (l2!=null){
  13. sum+=l2.val;
  14. l2=l2.next;
  15. }
  16. if (flag==1){
  17. sum+=1;
  18. flag--;
  19. }
  20. flag=sum/10;
  21. sum=sum%10;
  22. cur.next=new ListNode(sum);
  23. cur=cur.next;
  24. }
  25. cur.next= l1!=null? l1:l2;
  26. return dummyNode.next;
  27. }
  28. }

22.链表中的倒数第k个节点

image.png
先让fast指针先走n(倒数第n个节点), 然后快慢指针一起走, fast指针到头时, slow指向目标节点

  1. class Solution {
  2. public ListNode getKthFromEnd(ListNode head, int k) {
  3. ListNode fast=head;
  4. ListNode slow=head;
  5. int i=1;
  6. while(i<k){
  7. fast=fast.next;
  8. i++;
  9. }
  10. while(fast.next!=null){
  11. fast=fast.next;
  12. slow=slow.next;
  13. }
  14. return slow;
  15. }
  16. }

141.环形链表

image.png
利用快慢指针, 如果两个指针相遇那么就有环形回路

  1. public class Solution {
  2. public boolean hasCycle(ListNode head) {
  3. ListNode slow=head,fast=head;
  4. while(fast!=null&&fast.next!=null){
  5. slow=slow.next;
  6. fast=fast.next.next;
  7. if(slow==fast){
  8. return true;
  9. }
  10. }
  11. return false;
  12. }
  13. }

25.Kge一组翻转链表

image.png

  1. 首先知道是否有K个链表, 如果没有, 那么就直接返回头结点, 此时check指向K+1个节点
  2. 如果有K个链表, 那么就采用头插法, 将列表翻转
  3. 递归处理第K+1个列表

    1. class Solution {
    2. public ListNode reverseKGroup(ListNode head, int k) {
    3. ListNode pre=null;
    4. ListNode crr=head;
    5. ListNode next=null;
    6. ListNode check=head;
    7. int can=0;
    8. int count=0;
    9. while(can<k&&check!=null){ //检查是否有K个链表
    10. can++;
    11. check=check.next;
    12. }
    13. if(can==k){ //如果可以翻转, 采用头插法翻转链表
    14. while(count<k&&crr!=null){
    15. next=crr.next;
    16. crr.next=pre;
    17. pre=crr;
    18. crr=next;
    19. count++;
    20. }
    21. if(next!=null){
    22. //此时head指向末尾的结点
    23. head.next=reverseKGroup(check,k); //递归处理check+1个
    24. }
    25. return pre;
    26. }else{ //否则直接返回头结点
    27. return head;
    28. }
    29. }
    30. }

    234.回文链表

    image.pngimage.png
    先找到中间节点, 然后将后半部分的节点反装, 然后利用双指针判断是否为回文

    1. class Solution {
    2. public boolean isPalindrome(ListNode head) {
    3. if (head.next==null){
    4. return true;
    5. }
    6. //先找到中点节点
    7. ListNode slow=head,fast=head;
    8. //pre指向slow的上一个节点
    9. while (fast!=null&&fast.next!=null){
    10. fast=fast.next.next;
    11. slow=slow.next;
    12. }
    13. fast=revers(slow);
    14. slow=head;
    15. while (slow!=null&&fast!=null){
    16. if (slow.val!= fast.val){
    17. return false;
    18. }
    19. slow=slow.next;
    20. fast=fast.next;
    21. }
    22. return true;
    23. }
    24. public ListNode revers(ListNode head){
    25. if (head.next==null){
    26. return head;
    27. }
    28. ListNode temp=revers(head.next);
    29. head.next.next=head;
    30. head.next=null;
    31. return temp;
    32. }
    33. }

    24.两两交换链表中的节点

    image.png

    1. class Solution {
    2. public ListNode swapPairs(ListNode head) {
    3. if(head==null||head.next==null){
    4. return head;
    5. }
    6. ListNode temp=head.next;
    7. head.next=swapPairs(head.next.next);
    8. temp.next=head;
    9. return temp;
    10. }
    11. }

    Recursion

    236.二叉树的最近公共祖先

    image.pngimage.png

    1. /**
    2. * Definition for a binary tree node.
    3. * public class TreeNode {
    4. * int val;
    5. * TreeNode left;
    6. * TreeNode right;
    7. * TreeNode(int x) { val = x; }
    8. * }
    9. */
    10. class Solution {
    11. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    12. //如果root为空那么直接返回null, 说明到头了
    13. if(root==null){
    14. return null;
    15. }
    16. //说明找到了了其中的一个结点
    17. if(root==p||root==q){
    18. return root;
    19. }
    20. //否则就去两边去查询
    21. TreeNode left=lowestCommonAncestor(root.left,p,q);
    22. TreeNode right=lowestCommonAncestor(root.right,p,q);
    23. //说明两边都找到了p或者q结点, 此时的root就是最近的祖先结点
    24. if(left!=null&&right!=null){
    25. return root;
    26. }else if(left!=null){
    27. //说明左边找到了p或者q, 右边没有找到p或者q, 说明left最先找到的
    28. return left;
    29. }else{
    30. return right;
    31. }
    32. }
    33. }

33.搜索旋转排序数

image.pngimage.png

从mid处将两边分开, 那么一定有一边的数组是有序的, 如上图所示.

  1. class Solution {
  2. public int search(int[] nums, int target) {
  3. return fun2(nums,0,nums.length-1,target);
  4. }
  5. public int fun2(int[] nums,int left,int right,int target){
  6. if(left>right){
  7. return -1;
  8. }
  9. int mid=left+(right-left)/2;//算出中间数
  10. if(nums[mid]==target){
  11. return mid;
  12. }
  13. //表示左半段是有序的
  14. if(nums[mid]>=nums[left]){
  15. if(nums[left]<=target&&target<nums[mid]){
  16. //查找左边
  17. return fun2(nums,left,mid-1,target);
  18. }else{
  19. //查找右边
  20. return fun2(nums,mid+1,right,target);
  21. }
  22. }else {
  23. //说明有半段是有序的
  24. if(nums[mid]<target&&target<=nums[right]){
  25. return fun2(nums,mid+1,right,target);
  26. }else{
  27. return fun2(nums,left,mid-1,target);
  28. }
  29. }
  30. }
  31. }

124.二叉树中最大路径和

image.pngimage.png

  1. class Solution {
  2. private int max_val=0;
  3. public int maxPathSum(TreeNode root) {
  4. fun(root);
  5. return max_val;
  6. }
  7. public int fun(TreeNode root){
  8. if (root==null){
  9. return 0;
  10. }
  11. int left=fun(root.left);
  12. int right=fun(root.right);
  13. int sum=left+right+root.val;
  14. int root_left=root.val+left;
  15. int root_right=root.val+right;
  16. //算出以根节点
  17. int temp_max_val=Math.max(Math.max(Math.max(root_left,root_right),sum),root.val);
  18. if(temp_max_val>max_val){
  19. max_val=temp_max_val;
  20. }
  21. //说明左节点加上根节点值 或者 右节点加上根节点的值 有大于0的, 可以返回其中的一个值
  22. if (root_left>0||root_right>0||root.val>0){
  23. //返回其中最大的值
  24. return Math.max(Math.max(root_left,root_right),root.val);
  25. }else {
  26. return 0;
  27. }
  28. }
  29. }

199.二叉树的右视图

image.png
自己的方法:
刚开始只想着只遍历右子树就行了, 但是发现左子树可能比右子树深, 从右侧也可以看到, 那么就用两个变量cur_level, right_max_depth,分别记录当前的深度和最大深度, 如果当前的深度大于最大深度, 就将此根节点加入列表中.

  1. class Solution {
  2. private List<Integer> list=new ArrayList<>();
  3. private int cur_level=0,right_max_depth=0;//第一个记录当前的深度, 第二个记录右边最大的深度
  4. public List<Integer> rightSideView(TreeNode root) {
  5. fun(root);
  6. return list;
  7. }
  8. public void fun(TreeNode root){
  9. cur_level++;
  10. if(root!=null&&cur_level>right_max_depth){
  11. if(cur_level>right_max_depth){
  12. list.add(root.val);
  13. right_max_depth=Math.max(right_max_depth,cur_level);//获取右边最大的深度
  14. }
  15. }else{
  16. return ;
  17. }
  18. fun(root.right);
  19. //回溯, 减去右结点进入的深度
  20. cur_level--;
  21. fun(root.left);
  22. cur_level--;
  23. }
  24. }

然后发现每次都是先遍历右节点, 且每一层的结点只保存最右边的节点, 所以list中的size隐藏了最大深度, 这样就不用再用right_max_depth来保存最大深度了, 也不需要全局变量cur_level来保存当前的深度, 直接利用形参记录, 递归的深度就是当前的深度, 下面是看了评论的方法后总结的.

  1. class Solution {
  2. private List<Integer> list=new ArrayList<>();
  3. public List<Integer> rightSideView(TreeNode root) {
  4. fun(root,0);
  5. return list;
  6. }
  7. public void fun(TreeNode root,int depth){
  8. if(root==null){
  9. return ;
  10. }
  11. if(depth==list.size()){
  12. list.add(root.val);
  13. }
  14. fun(root.right,depth+1);
  15. fun(root.left,depth+1);
  16. }
  17. }

105.从前序与中序遍历序列构造二叉树

image.png
首先要知道, 通过前序遍历和中序遍历可以构造唯一的二叉树.

  1. class Solution {
  2. Map<Integer,Integer> map=new HashMap<>();
  3. public TreeNode buildTree(int[] preorder, int[] inorder) {
  4. //利用hashmap快一些
  5. for (int i=0;i<inorder.length;i++) {
  6. map.put(inorder[i],i);
  7. }
  8. return fun(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
  9. }
  10. public TreeNode fun(int[] preorder,int[] inorder,int pre_left,int pre_right,int in_left,int in_right){
  11. if (pre_left>pre_right){
  12. return null;
  13. }
  14. int preRoot=pre_left; //前序序列的第一个元素即为头结点
  15. int inRoot = map.get(preorder[preRoot]); //返回的是前序遍历元素在中序遍历顺序中的下标
  16. int left_subtree_num=inRoot-in_left; //得出该元素左边还有多少个节点
  17. TreeNode root = new TreeNode(preorder[preRoot]);
  18. root.left=fun(preorder,inorder,pre_left+1,pre_left+left_subtree_num,in_left,inRoot-1); //递归处理
  19. root.right=fun(preorder,inorder,pre_left+left_subtree_num+1,pre_right,inRoot+1,in_right);
  20. return root;
  21. }
  22. }

21.合并两个有序链表

image.png

  1. class Solution {
  2. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  3. //采用递归的方法
  4. if(l1==null){
  5. return l2;
  6. }else if(l2==null){
  7. return l1;
  8. }else if(l1.val<l2.val){
  9. l1.next=mergeTwoLists(l1.next,l2);
  10. return l1;
  11. }else{
  12. l2.next=mergeTwoLists(l1,l2.next);
  13. return l2;
  14. }
  15. }
  16. }

110.平衡二叉树

image.png

  1. class Solution {
  2. public boolean isBalanced(TreeNode root) {
  3. return fun(root)!=-1;
  4. }
  5. public int fun(TreeNode root){
  6. if(root==null){
  7. return 0;
  8. }
  9. int left=fun(root.left),right=fun(root.right);
  10. if(left>=0&&right>=0&&Math.abs(left-right)<=1){ //高度没有超过1
  11. return Math.max(left,right)+1; //加上本节点的高度
  12. }else{
  13. return -1; //如果不平衡那么直接返回-1
  14. }
  15. }
  16. }

104.二叉数的最大深度

image.png

  1. class Solution {
  2. public int maxDepth(TreeNode root) {
  3. if(root==null){
  4. return 0;
  5. }
  6. return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
  7. }
  8. }

101.对称二叉树

image.png
是否为一个对称的二叉树, 不仅根节点要相等, 并且左树的左子树和右树的右子相等, 且左树的右子树和右树的左子树相等.

  1. class Solution {
  2. public boolean isSymmetric(TreeNode root) {
  3. return check(root, root);
  4. }
  5. public boolean check(TreeNode p, TreeNode q) {
  6. if (p == null && q == null) {
  7. return true;
  8. }
  9. //如果其中有一个为null, 返回false
  10. if (p == null || q == null) {
  11. return false;
  12. }
  13. //分别检查根节点的值是否相等,
  14. return p.val == q.val && check(p.left, q.right) && check(p.right, q.left);
  15. }
  16. }

543.二叉树的直径

image.png

  1. class Solution {
  2. private int diameter=0;
  3. public int diameterOfBinaryTree(TreeNode root) {
  4. fun(root);
  5. return diameter;
  6. }
  7. public int fun(TreeNode root){
  8. if(root==null){
  9. return 0;
  10. }
  11. int left=fun(root.left);
  12. int right=fun(root.right);
  13. //直径是结点之间的边数, 所以这里不返回left+right+1
  14. diameter=Math.max(diameter,left+right);
  15. //这里返回左树或者右树直径最大的值 加上 根节点的值
  16. return Math.max(left,right)+1;
  17. }
  18. }

98.验证二叉搜索树

image.png
需要设置一个上下界, 如果超过上下界, 那么就不是搜索二叉树
对于一个根节点, 他左子树的上界就是根节点的值, 下界就是Long.MIN_VALUE, 对于左子树, 他的上界为Long.MAX_VALUE, 下界就是根节点的值, 这样递归处理

方法二是利用中序遍历, 观察二叉搜索树, 发现其中序遍历是升序, 先遍历左子树,然后是根节点, 最后是右子树.

  1. class Solution {
  2. public boolean isValidBST(TreeNode root) {
  3. return fun(root,Long.MIN_VALUE,Long.MAX_VALUE);
  4. }
  5. public boolean fun(TreeNode root, long bottom, long top){
  6. if(root==null){
  7. return true;
  8. }
  9. if (root.val<=bottom||root.val>=top){
  10. return false;
  11. }
  12. //此时左子树的下界还是Long.MIN_VALUE, 上界变成了root.val, 左子树都比root.val小, 右子树同样的道理
  13. return fun(root.left,bottom,root.val)&&fun(root.right,root.val,top);
  14. }
  15. }
  16. 方法二:
  17. //利用中序遍历为升序
  18. class Solution {
  19. public boolean isValidBST(TreeNode root) {
  20. Deque<TreeNode> stack = new LinkedList<TreeNode>();
  21. double inorder = -Double.MAX_VALUE;
  22. while (!stack.isEmpty() || root != null) {
  23. //先将左子树全部放入栈中
  24. while (root != null) {
  25. stack.push(root);
  26. root = root.left;
  27. }
  28. root = stack.pop();
  29. // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
  30. if (root.val <= inorder) {
  31. return false;
  32. }
  33. inorder = root.val;
  34. //处理根结点的右子树
  35. root = root.right;
  36. }
  37. return true;
  38. }
  39. }

162.寻找峰值

image.png
看到O(logn)的就想到了二分查找,如果nums[mid]<nums[mid+1]那么右边一定有峰值,题目还保证了num[i]!=nums[i+1],那么mid+2元素要么比mid+1小,那么此时峰值元素就是mid+1;要么比mid+1大,那就继续向右查找,如果右边一直是升序,那么最后一个元素就是峰值,他比左边的元素大,比右边也大(越界,数值为负无穷)。同样左边也是一样的道理。

  1. class Solution {
  2. public int findPeakElement(int[] nums) {
  3. return fun(nums, 0, nums.length - 1);
  4. }
  5. public int fun(int[] nums,int left,int right){
  6. if (left<=right){
  7. //算出中点
  8. int mid=(left+right)/2;
  9. //如果中点的元素比两边的大,那么直接返回
  10. if (nums[mid]>(mid-1>=0? nums[mid-1]:Integer.MIN_VALUE)&&numsb[mid]>(mid+1<= nums.length-1? nums[mid+1]:Integer.MIN_VALUE)){
  11. return mid;
  12. }else if (nums[mid]<(mid-1>=0? nums[mid-1]:Integer.MIN_VALUE)){
  13. //如果中间的元素比左边的小,那么左边一定有峰值
  14. return fun(nums,left,mid-1);
  15. }else {
  16. return fun(nums,mid+1,right);
  17. }
  18. }
  19. return 0;
  20. }
  21. }

240.搜索二维矩阵2

image.pngimage.png
红色框中的元素都是大于中间元素的,绿色框中都是小于中间元素的
方法二是与方框中右上角的元素比较, 其元素比左边的元素都大, 比下边的元素都小, 那么将Target与其比较, 如果比其小, 那么target元素绝对不会在其元素的下边, 那么就将最右边的一列排除掉, 反之如果比其大, 那么target绝对不会在其元素的左边, 那么将最上方的元素排除.

  1. class Solution {
  2. public boolean searchMatrix(int[][] matrix, int target) {
  3. return fun(matrix,0,matrix[0].length-1,0,matrix.length-1,target);
  4. }
  5. public boolean fun(int[][] matrix,int start_x,int end_x,int start_y,int end_y,int target){
  6. if (start_x>end_x||start_y>end_y){
  7. return false;
  8. }
  9. int mid_x=(start_x+end_x)/2;
  10. int mid_y=(start_y+end_y)/2;
  11. if (matrix[mid_y][mid_x]==target){
  12. return true;
  13. }else if (matrix[mid_y][mid_x]>target){
  14. //排除红色框中的元素
  15. return fun(matrix,start_x,mid_x-1,start_y,end_y,target)||fun(matrix,mid_x,end_x,start_y,mid_y-1,target);
  16. }else {
  17. //排除绿色框中的元素
  18. return fun(matrix,start_x,end_x,mid_y+1,end_y,target)||fun(matrix,mid_x+1,end_x,start_y,mid_y,target);
  19. }
  20. }
  21. }

394.字符串解码

image.png

  1. class MySolution {
  2. public String decodeString(String s) {
  3. int num_start=0,num_end=0;
  4. StringBuilder sb = new StringBuilder();
  5. for (int i=0;i<s.length();i++){
  6. //这里将字符转为数字
  7. if (s.charAt(i)>='0'&&s.charAt(i)<='9'){
  8. num_start=i;
  9. num_end=i;
  10. while (s.charAt(num_end)>='0'&&s.charAt(num_end)<='9'){
  11. num_end++;
  12. }
  13. //此时num——end指向'[',
  14. //转换为数字
  15. int num=Integer.valueOf(s.substring(num_start,num_end));
  16. num_start=num_end+1;
  17. //来找与中括号匹配的右边
  18. int flag=0;
  19. while (num_end<s.length()){
  20. if (s.charAt(num_end)=='['){
  21. flag++;
  22. }
  23. if (s.charAt(num_end)==']'){
  24. flag--;
  25. }
  26. if (flag==0){
  27. //找到与第一个'['匹配的']'
  28. break;
  29. }
  30. num_end++;
  31. }
  32. i=num_end;
  33. String innerString=decodeString(s.substring(num_start,num_end));
  34. for (int j=0;j<num;j++){
  35. sb.append(innerString);
  36. }
  37. }else {
  38. //是普通字符,就加入sb中
  39. sb.append(s.charAt(i));
  40. }
  41. }
  42. return sb.toString();
  43. }
  44. }
  45. class Solution {
  46. public String decodeString(String s) {
  47. return fun1(s,0);
  48. }
  49. //shit表示']'结束的地方
  50. int shit=0;
  51. public String fun1(String s, int i) {
  52. if(i==s.length()) {
  53. return "";
  54. }
  55. String t="";
  56. if(Character.isLowerCase(s.charAt(i))) {
  57. t=s.charAt(i)+fun1(s,i+1);
  58. }else if(Character.isDigit(s.charAt(i))) {
  59. int j=0;
  60. //先算出数字
  61. while(Character.isDigit(s.charAt(i))) {
  62. j=j*10+(s.charAt(i)-'0');
  63. i++;
  64. }
  65. //此时i指向'['
  66. //然后再处理中括号中的字符串
  67. for(int x=0;x<j;x++) {
  68. t=t+fun1(s,i+1);
  69. }
  70. //s=number[String]String
  71. //表示加上中括号后的字符串,t=(number[String])+String
  72. t=t+fun1(s,shit+1);
  73. //如果遇到']'表示中间的字符串结束
  74. }else if(s.charAt(i)==']') {
  75. shit=i;
  76. }
  77. return t;
  78. }
  79. }

Greedy

300.最长递增子序列

image.png
目的是求最长的上升子序列, 最长表明每次上升的幅度最小, 那么用一个ints[len]来保存上升幅度最小的上升子序列,ints[len]=x, 表示数组中的前len个中最大的元素为x.
开始从左向右遍历数组, 先与ints数组最右边的元素, 也就是ints[len]作比较, 如果num[i]比其大,那么将其添加到ints数组的最后面, 否则利用二分查找去ints数组中查找比nums[i]小的那个元素, 找到后, 将nums[i]添加到找到的(假设ints[j])下一个元素(ints[j+1])中, 因为nums[i]比ints[j+1]相对于nums[i]上升的幅度更小, 更新ints数组.

  1. class Solution {
  2. public int lengthOfLIS(int[] nums) {
  3. int[] ints = new int[nums.length + 1];//ints[i]表示以第i个结尾的最长上升子序列中最后一个数
  4. int len=1;
  5. ints[len]=nums[0];
  6. for(int i=1;i<nums.length;i++){
  7. if(nums[i]>ints[len]){//大于最后最长升序子序列中的最后一个, 那么就将其添加到末尾
  8. ints[++len]=nums[i];
  9. }else {
  10. int l=1,r=len,index=0;
  11. while (l<=r){
  12. int mid=(l+r)/2;
  13. if (ints[mid]<nums[i]){
  14. index=mid;
  15. l=mid+1;
  16. }else {
  17. r=mid-1;
  18. }
  19. }
  20. ints[index+1]=nums[i];
  21. }
  22. }
  23. return len;
  24. }
  25. }

112.路径总和

image.png

  1. class Solution {
  2. public boolean hasPathSum(TreeNode root, int targetSum) {
  3. if(root==null)
  4. return false;
  5. if(root.right==null&&root.left==null)
  6. return targetSum-root.val==0;
  7. return hasPathSum(root.right,targetSum-root.val)||hasPathSum(root.left,targetSum-root.val);
  8. }
  9. }

122.买卖股票的最佳时间2

image.png

  1. class Solution {
  2. public int maxProfit(int[] prices) {
  3. int sum_profit=0;
  4. int i=0;
  5. while(i<prices.length){
  6. int j=i+1;
  7. //如果当前的价格比买入的价格高, 那么往后
  8. while(j<prices.length&&prices[j]>=prices[j-1]){
  9. j++;
  10. }
  11. sum_profit+=prices[j-1]-prices[i];
  12. i=j;
  13. }
  14. return sum_profit;
  15. }
  16. }

Dynamic Programming

5.最长回文子串

image.png
image.png
image.png

  1. public class Solution {
  2. public String longestPalindrome(String s) {
  3. int len = s.length();
  4. if (len < 2) {
  5. return s;
  6. }
  7. int maxLen = 1;
  8. int begin = 0;
  9. // dp[i][j] 表示 s[i..j] 是否是回文串
  10. boolean[][] dp = new boolean[len][len];
  11. // 初始化:所有长度为 1 的子串都是回文串
  12. for (int i = 0; i < len; i++) {
  13. dp[i][i] = true;
  14. }
  15. char[] charArray = s.toCharArray();
  16. // 递推开始
  17. // 先枚举子串长度
  18. for (int L = 2; L <= len; L++) {
  19. // 枚举左边界,左边界的上限设置可以宽松一些
  20. for (int i = 0; i < len; i++) {
  21. // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
  22. int j = L + i - 1;
  23. // 如果右边界越界,就可以退出当前循环
  24. if (j >= len) {
  25. break;
  26. }
  27. if (charArray[i] != charArray[j]) {
  28. dp[i][j] = false;
  29. } else {
  30. //相邻两个字符只要相等, 那么就是回文串
  31. if (j - i < 3) {
  32. dp[i][j] = true;
  33. } else {
  34. dp[i][j] = dp[i + 1][j - 1];
  35. }
  36. }
  37. // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
  38. if (dp[i][j] && j - i + 1 > maxLen) {
  39. maxLen = j - i + 1;
  40. begin = i;
  41. }
  42. }
  43. }
  44. return s.substring(begin, begin + maxLen);
  45. }
  46. }

300.最长递增子序列

image.png
利用一个一维数组动态规划, dp[i]=x表示以第i个数字(包括第i个)结尾的最长递增子序列长度为x, 此时已经计算了dp[0…i-1]的值, 状态转移方程为
image.png
表示dp[0…i-1]中最长上升子序列后面再加一个nums[i], 而dp[j]表示以第j个数字结尾的最长上升子序列, 如果能从dp[j]这个状态转移到dp[i]状态, 那么需要nums[i]>nums[j]. 整个数组中的最长上升子序列是dp[i]中的最大值
image.png

  1. if (nums.length==0){
  2. return 0;
  3. }
  4. int[] dp = new int[nums.length];
  5. int maxlen=1;
  6. //这里做了初始化
  7. dp[0]=1;
  8. for(int i=1;i<nums.length;i++){
  9. //这里表示nums[i]自身就是一个长度为1的子序列,所以dp[i]就等于1
  10. dp[i]=1;
  11. for(int j=0;j<i;j++){
  12. //找到能从dp[0]...dp[i-1]转移到dp[i]的最大值
  13. if(nums[i]>nums[j]){
  14. dp[i]=Math.max(dp[j]+1,dp[i]);
  15. }
  16. }
  17. maxlen=Math.max(maxlen,dp[i]);
  18. }
  19. return maxlen;
  20. //第二种方法
  21. int[] ints = new int[nums.length + 1];//ints[i]表示以第i个结尾的最长上升子序列中最后一个数
  22. int len=1;
  23. ints[len]=nums[0];
  24. for(int i=1;i<nums.length;i++){
  25. if(nums[i]>ints[len]){//大于最后最长升序子序列中的最后一个, 那么就将其添加到末尾
  26. ints[++len]=nums[i];
  27. }else {
  28. //否则利用二分查找找到其合适的位置,下标从1开始
  29. int l=1,r=len,index=0;
  30. while (l<=r){
  31. int mid=(l+r)/2;
  32. if (ints[mid]<nums[i]){
  33. index=mid;
  34. l=mid+1;
  35. }else {
  36. r=mid-1;
  37. }
  38. }
  39. ints[index+1]=nums[i];
  40. }
  41. }
  42. return len;

注意第41行, 是直接将index+1的元素取代掉, 而不是向数组中添加元素, 因为题目要求的是子序列, 不能改变元素之间的相对位置, 且上升幅度最小.

70.爬楼梯

image.png

  1. class Solution {
  2. public int climbStairs(int n) {
  3. int[] dp=new int[n+1];
  4. dp[1]=dp[0]=1;
  5. for(int i=2;i<=n;i++){
  6. dp[i]=dp[i-1]+dp[i-2];
  7. }
  8. return dp[n];
  9. }
  10. }

1143.最长公共子序列

image.png

  1. class Solution {
  2. public int longestCommonSubsequence(String text1, String text2) {
  3. int[][] dp=new int[text1.length()+1][text2.length()+1]; //dp[i][j]表示text1[i]与text2[j]的最长公共子序列
  4. for(int i=0;i<text1.length();i++){
  5. for(int j=0;j<text2.length();j++){
  6. if(text1.charAt(i)!=text2.charAt(j)){ //不相等, 那么比较他们前面谁的大
  7. dp[i+1][j+1]=Math.max(dp[i+1][j],dp[i][j+1]);
  8. }else{
  9. dp[i+1][j+1]=dp[i][j]+1; //相等, 那么就是上一个状态加一得来的.
  10. }
  11. }
  12. }
  13. return dp[text1.length()][text2.length()];
  14. }
  15. }

121.买卖股票的最佳时机

image.png
利用一个一维数组来动态规划, dp[i]=x表示前i天的的最大利润, min记录前i天的最低价格, 看dp[i-1]大还是当天的价格price[i]-min大, 取最大的那个, 就是dp[i]的值

  1. class Solution {
  2. public int maxProfit(int[] prices) {
  3. int[] dp=new int[prices.length]; //dp表示前i天最高的利润
  4. int min=prices[0];
  5. for(int i=0;i<prices.length;i++){
  6. //如果当前的价格比最低价格低, 那么最低价格就等于当天的价格
  7. if(prices[i]<min){
  8. min=prices[i];
  9. }
  10. dp[i]=Math.max(dp[i-1<0? 0:i-1],prices[i]-min);
  11. }
  12. return dp[prices.length-1];
  13. }
  14. }

53.最大子数组和

image.pngimage.png

  1. class Solution {
  2. public int maxSubArray(int[] nums) {
  3. int pre = 0, maxAns = nums[0];
  4. for (int x : nums) {
  5. pre = Math.max(pre + x, x);
  6. maxAns = Math.max(maxAns, pre);
  7. }
  8. return maxAns;
  9. }
  10. }

322.零钱兑换

image.png//
image.png
先将dp中所有的元素设置为amount+1,表示不可能取该元素,dp[0]=0, 表示金额为0时,取0个硬币。
如果coins[i]小于等于i,i为金额,才有可能取该硬币。

  1. class Solution {
  2. public int coinChange(int[] coins, int amount) {
  3. int[] dp=new int[amount+1];
  4. for(int i=1;i<dp.length;i++){
  5. dp[i]=amount+1;
  6. }
  7. //金额从1开始计算
  8. for(int i=1;i<=amount;i++){
  9. for(int j=0;j<coins.length;j++){
  10. //表示可以选取该硬币
  11. if(coins[j]<=i){
  12. dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
  13. }
  14. }
  15. }
  16. return dp[amount]==amount+1? -1:dp[amount];
  17. }
  18. }

64.最小路径和

image.png

  1. class Solution {
  2. public int minPathSum(int[][] grid) {
  3. int[][] dp=new int[grid.length][grid[0].length];
  4. dp[0][0]=grid[0][0];
  5. //处理上边界和左边界, 分别只能向右走和向下走
  6. for (int i=1;i<dp[0].length;i++){
  7. dp[0][i]=dp[0][i-1]+ grid[0][i];
  8. }
  9. for (int i=1;i<dp.length;i++){
  10. dp[i][0]=dp[i-1][0]+grid[i][0];
  11. }
  12. for (int i=1;i< grid.length;i++){
  13. for (int j=1;j<grid[0].length;j++){
  14. dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
  15. }
  16. }
  17. return dp[dp.length-1][dp[0].length-1];
  18. }
  19. }

718.最长重复字数组

image.png
利用动态规划,dp[i][j]表示以i-1,j-1结尾的公共字符串的长度,两者相等的时候,dp[i][j] = dp[i-1][j-1] + 1

  1. class Solution {
  2. public int findLength(int[] nums1, int[] nums2) {
  3. /**
  4. * 利用动态规划来
  5. */
  6. if (nums1.length==0||nums2.length==0){
  7. return 0;
  8. }
  9. int[][] dp=new int[nums1.length+1][nums2.length+1];
  10. int result=0;
  11. for (int i=1;i<=nums1.length;i++){
  12. for (int j=1;j<=nums2.length;j++){
  13. //如果第i个和第j个相等,那么
  14. if (nums1[i-1]==nums2[j-1]){
  15. dp[i][j]=dp[i-1][j-1]+1;
  16. result=Math.max(result,dp[i][j]);
  17. }
  18. }
  19. }
  20. return result;
  21. }
  22. }

221.最大正方形

image.pngimage.png
dp[i][j]表示以(i,j)为右下角,且只包含‘1’的最大正方形,算出所有的dp,那么其中的最大值就是我们所要求的值。现在考虑如何从其他的状态转为dp[i][j]。
如果[i][j]=‘0’,那么dp[i][j]=0,以此处为右下角的正方形不存在,因为此处就为0
如果[i][j]=‘1’,那么dp[i][j]的值由上方和左方以及左上方的最小值决定.

  1. class Solution {
  2. public int maximalSquare(char[][] matrix) {
  3. if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
  4. return 0;
  5. }
  6. int max_length=0;
  7. int[][] dp=new int[matrix.length][matrix[0].length];
  8. for (int i=0;i<matrix.length;i++){
  9. for (int j=0;j<matrix[0].length;j++){
  10. if (matrix[i][j]=='1'){
  11. //表示在边界处,只能形成边长为1的正方形
  12. if (i == 0 || j == 0) {
  13. dp[i][j]=1;
  14. }else{
  15. dp[i][j]=Math.min(Math.min(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1])+1;
  16. }
  17. max_length=Math.max(max_length,dp[i][j]);
  18. }
  19. }
  20. }
  21. return max_length*max_length;
  22. }
  23. }

62.不同路径

image.png
dp[i][j]表示走到第i行第j列的有多少个路径
如果i=0或者j=0,他们只能从上面或者左边走过来,此时路径只有一条
其他的情况就是可以从上边左边走过来,所以是上边和左边的路径个数之和。

  1. public int uniquePaths(int m, int n) {
  2. int[][] dp=new int[m][n];
  3. for(int i=0;i<m;i++){
  4. for(int j=0;j<n;j++){
  5. if(j==0||i==0){
  6. dp[i][j]=1;
  7. }else{
  8. dp[i][j]=dp[i-1][j]+dp[i][j-1];
  9. }
  10. }
  11. }
  12. return dp[m-1][n-1];
  13. }

还有一种解法是从起点到终点总共就需要m+n-2步,m步横着走,m-1步竖着走,那么就是C(m+n-2,m-1)=[(m+n-2)(m+n-3)((m+n-2)- (m-1) +1)]/[(12(m-1))]

  1. public int uniquePaths(int m, int n) {
  2. long result=1;
  3. for(int i=n,y=1;y<m;i++,y++){
  4. result=result*i/y;
  5. }
  6. return (int)result;
  7. }

152.乘积最大子数组

image.png
这跟最大子序和很类似, 只不过是乘积, 根据前面的经验, 可以列出动态方程
image.png
f(x)表示以第i个元素结尾的最大乘积, 他可由上个状态转移过来, 或者自身较大. 但是在本题中是乘积, 会出现负负得正的情况, 如a={5,6,−3,4,−3}, 他的fmax(x)对应如下{5,30,−3,4,−3}, 按照之前的算法, 我们得到的答案为30, 但是实际答案是所有的乘积, 所以我们得到一个结论当前的最优解不一定是上一个状态的最优解得到的.

这里要分情况讨论, 分为正数和负数, 如果当前的位置是一个负数, 那么我们希望前面的最优值是是一个尽可能小的负数, 然后乘上本位, 负负得正, 得到一个尽可能大的正数. 如果本位是一个正数, 那么我们希望前面是一个尽可能大的正数, 乘上本位,就是尽可能大的正数. 得到如下的公式
image.png
最后遍历max_dp数组即可得到其中的最大值

  1. class Solution {
  2. public int maxProduct(int[] nums) {
  3. int max=Integer.MIN_VALUE;
  4. int[] max_dp=new int[nums.length];
  5. int[] min_dp=new int[nums.length];
  6. max_dp[0]=nums[0];
  7. min_dp[0]=nums[0];
  8. for (int i=1;i<nums.length;i++){
  9. max_dp[i]=Math.max(Math.max(max_dp[i-1]*nums[i],min_dp[i-1]*nums[i]),nums[i]);
  10. min_dp[i]=Math.min(Math.min(max_dp[i-1]*nums[i],min_dp[i-1]*nums[i]),nums[i]);
  11. }
  12. int result=max_dp[0];
  13. for (int i : max_dp) {
  14. result=Math.max(result,i);
  15. }
  16. return result;
  17. }
  18. }

122.买卖股票的最佳时机2

image.png
image.png

  1. class Solution {
  2. public int maxProfit(int[] prices) {
  3. int dp0=0; //表示第i天没有持有股票的最大利润
  4. int dp1=-prices[0];//表示第i天持有股票的最大利润
  5. for(int i=0;i<prices.length;i++){
  6. //第i天没有持有股票, 那么可能前一天也没有持有股票, 或者前一天持有股票但是在第i天卖出了
  7. dp0=Math.max(dp0,dp1+prices[i]);
  8. //第i天持有股票, 那么前一天股票, 或者前一天没有持有股票, 但是在i天买入股票了
  9. dp1=Math.max(dp1,dp0-prices[i]);
  10. }
  11. return dp0;
  12. }
  13. }

198.打家劫舍

image.png
如果只有一间房, 那么就最大金额就是偷这一家, 如果是两间房, 那么就偷金额较大的那个
如果数量大于2间, 那么就考虑第 i 间房子, 如果前一家被偷, 那么此间就不能偷了, 那么此时的最大金额就是dp[i-1], 如果第i-2间房子被偷了, 那么最大金额就是dp[i-2]+nums[i], 表示第i-2家被偷加上此间的金额.

  1. public int rob(int[] nums) {
  2. if (nums==null||nums.length==0){
  3. return 0;
  4. }
  5. if(nums.length==1){
  6. return nums[0];
  7. }
  8. int[] dp = new int[nums.length];
  9. dp[0]=0;
  10. dp[1]=Math.max(dp[0],dp[1]);
  11. for (int i=2;i<nums.length;i++){
  12. dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
  13. }
  14. return dp[dp.length-1];
  15. }

可以发现第i间只与第i-1和i-2间相关, 利用滚动数组来保存

  1. public int rob(int[] nums) {
  2. if (nums==null||nums.length==0){
  3. return 0;
  4. }
  5. if(nums.length==1){
  6. return nums[0];
  7. }
  8. int first=nums[0];//表示第i-2家
  9. int second=Math.max(first,nums[1]);//表示第i-1家
  10. for (int i=2;i< nums.length;i++){
  11. int temp=second;
  12. second=Math.max(first+nums[i],second);
  13. first=temp;
  14. }
  15. return second;
  16. }

Depth First Search

200.岛屿数量

image.pngimage.png
利用深度优先遍历整个二维字符数组, 如果是’1’, 那么以他为根进行深度遍历, 将遍历过的字符变为’0’, 直到没有地方可走.

此题也可以利用BFS广度优先遍历, 利用一个队列保存.

  1. //深度优先
  2. class Solution {
  3. public int numIslands(char[][] grid) {
  4. int num=0;
  5. for(int i=0;i<grid.length;i++){
  6. for (int j=0;j<grid[0].length;j++){
  7. if (grid[i][j]=='1'){
  8. num++;
  9. fun(i,j,grid);
  10. }
  11. }
  12. }
  13. return num;
  14. }
  15. public void fun(int i,int j,char[][] grid){
  16. grid[i][j]='0';
  17. //上一个不越界, 且有陆地,才过去
  18. if (i-1>=0&&grid[i-1][j]=='1'){
  19. fun(i-1,j,grid);
  20. }
  21. if (i+1<grid.length&&grid[i+1][j]=='1'){
  22. fun(i+1,j,grid);
  23. }
  24. if (j-1>=0&&grid[i][j-1]=='1'){
  25. fun(i,j-1,grid);
  26. }
  27. if (j+1<grid[0].length&&grid[i][j+1]=='1'){
  28. fun(i,j+1,grid);
  29. }
  30. }
  31. }
  32. //广度优先
  33. class Solution {
  34. public int numIslands(char[][] grid) {
  35. int num=0;
  36. Queue<Integer> queue=new LinkedList<>();
  37. for(int i=0;i<grid.length;i++){
  38. for (int j=0;j<grid[0].length;j++){
  39. if (grid[i][j]=='1'){
  40. num++;
  41. grid[i][j]='0';
  42. //这里不是将元素入队, 而是将元素的index入队
  43. queue.offer(i*grid[0].length+j);
  44. //遍历以第一个为根节点遍历所有与他相连的陆地
  45. while(!queue.isEmpty()){
  46. Integer poll = queue.poll();
  47. int row=poll/grid[0].length;
  48. int clo=poll%grid[0].length;
  49. //上一个不越界, 且有陆地,才过去
  50. if (row-1>=0&&grid[row-1][clo]=='1'){
  51. queue.offer((row-1)*grid[0].length+clo);
  52. grid[row-1][clo] = '0';
  53. }
  54. if (row+1<grid.length&&grid[row+1][clo]=='1'){
  55. queue.offer((row+1)*grid[0].length+clo);
  56. grid[row+1][clo] = '0';
  57. }
  58. if (clo-1>=0&&grid[row][clo-1]=='1'){
  59. queue.offer(row*grid[0].length+clo-1);
  60. grid[row][clo-1] = '0';
  61. }
  62. if (clo+1<grid[0].length&&grid[row][clo+1]=='1'){
  63. queue.offer(row*grid[0].length+clo+1);
  64. grid[row][clo+1] = '0';
  65. }
  66. }
  67. }
  68. }
  69. }
  70. return num;
  71. }
  72. }

695.岛屿的最大面积

image.png
跟上面的题很相似,利用深度遍历或者广度遍历。

  1. class Solution {
  2. private int area=0;
  3. public int maxAreaOfIsland(int[][] grid) {
  4. int max_area=0;
  5. for(int i=0;i<grid.length;i++){
  6. for(int j=0;j<grid[0].length;j++){
  7. //找到一块陆地,使用深度遍历来计算其最大面积
  8. if(grid[i][j]==1){
  9. fun(i,j,grid);
  10. }
  11. max_area=Math.max(max_area,area);
  12. area=0;
  13. }
  14. }
  15. return max_area;
  16. }
  17. public void fun(int i,int j,int[][] grid){
  18. grid[i][j]=0;
  19. area++;
  20. if(i-1>=0&&grid[i-1][j]==1){
  21. fun(i-1,j,grid);
  22. }
  23. if(j-1>=0&&grid[i][j-1]==1){
  24. fun(i,j-1,grid);
  25. }
  26. if(i+1<grid.length&&grid[i+1][j]==1){
  27. fun(i+1,j,grid);
  28. }
  29. if(j+1<grid[0].length&&grid[i][j+1]==1){
  30. fun(i,j+1,grid);
  31. }
  32. }
  33. }

46.全排列

image.png

二叉树是一个排列树, 利用了回溯法

  1. class Solution {
  2. public List<List<Integer>> p_list=new ArrayList<>();
  3. public List<List<Integer>> permute(int[] nums) {
  4. fun(nums,0);
  5. return p_list;
  6. }
  7. /*
  8. 排列树,利用回溯法
  9. */
  10. public void fun(int[] nums,int i){
  11. if(i==nums.length-1){
  12. List<Integer> list=new ArrayList<>();
  13. for(int k:nums) {
  14. list.add(k);
  15. }
  16. p_list.add(list);
  17. return ;
  18. }
  19. for(int j=i;j<nums.length;j++){
  20. swap(i,j,nums);
  21. fun(nums,i+1);
  22. //将原来的两个数交换回去, 这里是一个回溯的过程
  23. swap(i,j,nums);
  24. }
  25. }
  26. public void swap(int i,int j,int[] nums){
  27. int temp=nums[i];
  28. nums[i]=nums[j];
  29. nums[j]=temp;
  30. }
  31. }

22.括号生成

image.png
利用DFS, 如果左括号剩余数量大于0, 那么就加上左括号, 如果右括号的剩余数量大于左括号的剩余数量, 那么加上一个右括号, 不必判断字符串最后一个字符是左括号还是右括号, 因为在生成的时候就是左括号先加入到字符串中, 只有右括号剩余数量大于左括号的时候再考虑加上右括号, 括号的是否有效已经隐藏了.

  1. class Solution {
  2. private List<String> list=new ArrayList<>();
  3. public List<String> generateParenthesis(int n) {
  4. fun(n,n,"");
  5. return list;
  6. }
  7. public void fun(int left,int right,String str){
  8. if(left==0&&right==0){
  9. list.add(str);
  10. return;
  11. }
  12. if(left>0){
  13. fun(left-1,right,str+"(");
  14. }
  15. if (right>left){
  16. fun(left,right-1,str+")");
  17. }
  18. }
  19. }

93.复原IP地址

image.png
利用回溯法判断

  1. class Solution {
  2. private List<String> result=new ArrayList<>();
  3. public List<String> restoreIpAddresses(String s) {
  4. backTrack(new StringBuilder(s),0,0);
  5. return result;
  6. }
  7. public void backTrack(StringBuilder s, int startIndex, int pointNum){
  8. if (pointNum == 3) {// 逗点数量为3时,分隔结束
  9. // 判断第四段⼦字符串是否合法,如果合法就放进result中
  10. if (isValid(s,startIndex,s.length()-1)) {
  11. result.add(s.toString());
  12. }
  13. return;
  14. }
  15. // ⬇这里要保证是三位数
  16. for (int i = startIndex;i<s.length() && i < startIndex+3; i++) {
  17. if (isValid(s, startIndex, i)) {
  18. s.insert(i+1,'.'); //在其后面插入一个'.'
  19. pointNum++;
  20. backTrack(s, i + 2, pointNum);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
  21. pointNum--;// 回溯
  22. s.deleteCharAt(i+1);//回溯的时候将其'.'删除
  23. } else {
  24. break;
  25. }
  26. }
  27. }
  28. //检查从start到end的字符串的数字是否合法
  29. public boolean isValid(StringBuilder s, int start, int end){
  30. if (start>=s.length()||s.charAt(start) == '0' && start != end||end-start>=3) { // 0开头的数字不合法
  31. return false;
  32. }
  33. int num = 0;
  34. for (int i = start; i <= end; i++) {
  35. num = num * 10 + (s.charAt(i) - '0');
  36. if (num > 255) { // 如果⼤于255了不合法
  37. return false;
  38. }
  39. }
  40. return true;
  41. }
  42. }

129.求根节点到叶子节点数字之和

image.png

  1. class Solution {
  2. private int totalSum=0;
  3. public int sumNumbers(TreeNode root) {
  4. fun(root,0);
  5. return totalSum;
  6. }
  7. public void fun(TreeNode root, int sum) {
  8. sum = sum * 10 + root.val;
  9. if (root.left == null && root.right == null) { //到达叶子节点
  10. totalSum += sum;
  11. } else if (root.left != null && root.right != null) { //左右节点都需要遍历
  12. fun(root.left, sum);
  13. fun(root.right, sum);
  14. } else if (root.left == null) { //只需要遍历右节点
  15. fun(root.right, sum);
  16. } else { //只需要遍历左节点
  17. fun(root.left, sum);
  18. }
  19. }
  20. }
  1. class Solution {
  2. public int sumNumbers(TreeNode root) {
  3. int sum=0;
  4. return fun(root,sum);
  5. }
  6. public int fun(TreeNode root,int sum){
  7. if(root==null){
  8. return 0;
  9. }
  10. //到达叶子结点,返回加上本结点的值
  11. if(root.left==null&&root.right==null){
  12. return sum*10+root.val;
  13. }
  14. return fun(root.left,sum*10+root.val)+fun(root.right,sum*10+root.val); //返回左右节点相加的结果
  15. }
  16. }

113.路径总和2

image.png

  1. class Solution {
  2. List<List<Integer>> parent_list=new ArrayList<>();
  3. public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
  4. fun(root,0,targetSum,new ArrayList<>());
  5. return parent_list;
  6. }
  7. public void fun(TreeNode root,int curSum,int targetSum,List list){
  8. if(root==null){
  9. return;
  10. }
  11. list.add(root.val);
  12. curSum+=root.val;
  13. if (root.left==null&&root.right==null&&curSum==targetSum){
  14. parent_list.add(new ArrayList<Integer>(list));
  15. }
  16. fun(root.left,curSum,targetSum,list);
  17. fun(root.right,curSum,targetSum,list);
  18. //回溯的过程,需要从list中移除本位, 是递归调用,list中的元素不会随着栈帧的出栈而将元素出栈,需要手动出栈, 但是curSum会自动减去
  19. list.remove(list.size()-1);
  20. }
  21. }

78.子集

image.png

  1. class Solution {
  2. List<Integer> t = new ArrayList<Integer>();
  3. List<List<Integer>> ans = new ArrayList<List<Integer>>();
  4. public List<List<Integer>> subsets(int[] nums) {
  5. dfs(0, nums);
  6. return ans;
  7. }
  8. public void dfs(int cur, int[] nums) {
  9. if (cur == nums.length) {
  10. ans.add(new ArrayList<Integer>(t));
  11. return;
  12. }
  13. //选择当前的元素
  14. t.add(nums[cur]);
  15. dfs(cur + 1, nums);
  16. //将当前的元素移除, 表示不选择该元素
  17. t.remove(t.size() - 1);
  18. dfs(cur + 1, nums);
  19. }
  20. }

39.组合总和

截屏2022-05-21 11.15.35.png
使用回溯,注意第22行,start没有+1,因为元素可以重复使用。

  1. class Solution {
  2. public List<List<Integer>> combinationSum(int[] candidates, int target) {
  3. List<List<Integer>> lists = new ArrayList<>();
  4. Arrays.sort(candidates);
  5. fun(candidates,target,lists,0,new ArrayList<Integer>());
  6. return lists;
  7. }
  8. public void fun(int[] candidates,int target,List<List<Integer>> res,int level,List<Integer> sub_list){
  9. if (target<0){
  10. return;
  11. }
  12. if (target==0){
  13. res.add(new ArrayList<>(sub_list));
  14. }
  15. for (int start=level;start< candidates.length;start++){
  16. if (target<0){
  17. break;
  18. }
  19. //将此元素现添加到其中
  20. sub_list.add(candidates[start]);
  21. //这里start不+1,因为此元素可以重复使用
  22. fun(candidates,target- candidates[start],res,start,sub_list);
  23. //再将此元素移除
  24. sub_list.remove(sub_list.size()-1);
  25. }
  26. }
  27. }

Breadth First Search

103.二叉树的锯齿形层序遍历

image.png

  1. class Solution {
  2. public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
  3. List<List<Integer>> List=new ArrayList<>();
  4. Queue<TreeNode> queue=new LinkedList<>();
  5. if(root!=null){ //先将root入列
  6. queue.offer(root);
  7. }else{
  8. return List;
  9. }
  10. int size=queue.size(); //先获取队列中的个数, 便于一层的遍历
  11. boolean flag=true; //flag来标志遍历顺序, 从左往右为true, 反之为false
  12. while(!queue.isEmpty()){ //当队列不为空,循环
  13. List<Integer> subList=new ArrayList<>();
  14. while(size>0){
  15. TreeNode temp=queue.poll();
  16. //从左往右
  17. if(flag==true){
  18. subList.add(temp.val);
  19. }else{
  20. subList.add(0,temp.val);
  21. }
  22. if(temp.left!=null){
  23. queue.offer(temp.left);
  24. }
  25. if(temp.right!=null){
  26. queue.offer(temp.right);
  27. }
  28. size--;
  29. }
  30. List.add(subList);
  31. flag=!flag; //一层遍历完成, flag反转
  32. size=queue.size();
  33. }
  34. return List;
  35. }
  36. }

102.二叉树的层序遍历

image.png

  1. class Solution {
  2. public List<List<Integer>> levelOrder(TreeNode root) {
  3. List<List<Integer>> ret = new ArrayList<List<Integer>>();
  4. if (root == null) {
  5. return ret;
  6. }
  7. Queue<TreeNode> queue = new LinkedList<TreeNode>();
  8. queue.offer(root);
  9. while (!queue.isEmpty()) {
  10. List<Integer> level = new ArrayList<Integer>();
  11. int currentLevelSize = queue.size(); //用来记录本层的节点个数, 便于下面的遍历
  12. for (int i = 1; i <= currentLevelSize; ++i) { //将一层的结点添加至list中
  13. TreeNode node = queue.poll();
  14. level.add(node.val);
  15. if (node.left != null) {
  16. queue.offer(node.left);
  17. }
  18. if (node.right != null) {
  19. queue.offer(node.right);
  20. }
  21. }
  22. ret.add(level);
  23. }
  24. return ret;
  25. }
  26. }

662.最大宽度

image.png
image.png
题目是求出最大的宽度, 首先想到的是BFS, 但是发现两个结点之间的空结点也算作宽度内. 这里利用到的是层序遍历结点的下标值, 同一层的结点的下标值之差就是他们之间的宽度.
那么每一个结点的下标都是由它的父节点下标的出来的, 设父节点的下标为position, 那么它的左节点下标为position2, 右节点下标为position2+1, 那么将一层的结点全部放入到list中, 用最后一个结点的下标减去第一个下标, 得到就是这层的宽度.

  1. class Solution {
  2. public int widthOfBinaryTree(TreeNode root) {
  3. customNode node=new customNode(root,0);
  4. Queue<customNode> queue=new LinkedList<>();
  5. int max_width=0;
  6. //List<Integer> list=new ArrayList<>();
  7. queue.offer(node);
  8. while(!queue.isEmpty()){
  9. List<Integer> list=new ArrayList<>();
  10. int size=queue.size();
  11. for(int i=0;i<size;i++){
  12. customNode temp=queue.poll();
  13. list.add(temp.position);
  14. if(temp.treeNode.left!=null){
  15. queue.offer(new customNode(temp.treeNode.left,temp.position*2));
  16. }
  17. if(temp.treeNode.right!=null){
  18. queue.offer(new customNode(temp.treeNode.right,temp.position*2+1));
  19. }
  20. }
  21. max_width=Math.max(max_width,list.get(list.size()-1)-list.get(0)+1);
  22. }
  23. return max_width;
  24. }
  25. //自定义节点,保存节点以及他的下标值
  26. class customNode{
  27. TreeNode treeNode;
  28. int position;
  29. public customNode(TreeNode treeNode,int position){
  30. this.treeNode=treeNode;
  31. this.position=position;
  32. }
  33. }
  34. }