目录

  • 数据结构——数组
    • 直接解
      • 【剑指offer】03.数组中重复的数字
        • 排序法
        • 集合法
        • 原地置换
      • 【剑指offer】04. 二维数组中的查找
      • 【剑指offer】29. 顺时针打印矩阵
      • 【剑指offer】39. 数组中出现次数超过一半的数字
      • 【剑指offer】40. 最小的k个数
      • 【剑指offer】45. 把数组排成最小的数
      • 【剑指offer】59. 滑动窗口的最大值
      • 【剑指offer】61. 扑克牌中的顺子
      • 【剑指offer】66. 构建乘积数组
    • 特殊解——动态规划
      • 【剑指offer】10.1 斐波那契数列
      • 【剑指offer】10.2 青蛙跳台阶问题
      • 【剑指offer】10.3 矩形覆盖
      • 【剑指offer】10.4 变态跳台阶
      • 【剑指offer】42. 连续子数组的最大和
      • 【剑指offer】47. 礼物的最大价值
    • 特殊解——二分法
      • 【剑指offer】11. 旋转数组的最小数字
      • 【剑指offer】53. 数字在排序数组中出现的次数
      • 【剑指offer】53.2 0~n-1中缺失的数字
    • 特殊解——回溯搜索/DFS/BFS
      • 【剑指offer】12. 矩阵中的路径
      • 【剑指offer】13. 机器人的运动范围
    • 特殊解——排序
      • 【剑指offer】21. 调整数组顺序使奇数位于偶数前面
      • 【剑指offer】51. 数组中的逆序对
    • 特殊解——位运算
      • 【剑指offer】56. 数组中数字出现的次数
      • 【剑指offer】56.2 数组中数字出现的次数
    • 特殊解——双指针
      • 【剑指offer】57. 和为s的两个数字
      • 【剑指offer】57.2 和为s的两个数字
    • 特殊解——贪心算法
      • 【剑指offer】63. 股票的最大利润

数据结构——数组

直接解

【剑指offer】03.数组中重复的数字

//03. 数组中重复的数字
// 找出数组中重复的数字。

// 力扣
// 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。
// 数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每
// 个数字重复了几次。请找出数组中任意一个重复的数字。

// 牛客
// 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数
// 字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次
// 。请找出数组中第一个重复的数字。 例如,如果输入长度为7的数组{2,3,
// 1,0,2,5,3},那么对应的输出是第一个重复的数字2。
// 返回描述:
// 如果数组中有重复的数字,函数返回true,否则返回false。
// 如果数组中有重复的数字,把重复的数字放到参数duplication[0]中。(ps
// :duplication已经初始化,可以直接赋值使用。)

排序法

把数组排成升序,再遍历找相邻重复数

// 时间复杂度 O(nlogn):3 ms, 在所有 Java 提交中击败了59.39%的用户
// 空间复杂度 O(1):45.9 MB, 在所有 Java 提交中击败了96.69%的用户
import java.util.Arsrays;class Solution {public int findRepeatNumber(int[] nums) {if (nums == null || nums.length <= 0) {return -1;}Arrays.sort(nums);for (int i=0; i < nums.length-1; i++) {if (nums[i] == nums[i+1]) {return nums[i];}}return -1;}
}

集合法

很粗暴,直接存hashset,存不进就是遇到重复了

// 时间复杂度 O(n): 5 ms, 在所有 Java 提交中击败了49.49%的用户
// 空间复杂度 O(n):48.1 MB, 在所有 Java 提交中击败了23.18%的用户
import java.util.*;class Solution {public int findRepeatNumber(int[] nums) {Set<Integer> set = new HashSet<>();int result = -1;if (nums == null || nums.length <= 0) { return result;}for (int i=0; i <= nums.length-1; i++) {if (!set.add(nums[i])) {  // 如果存不进hashset,说明遇到重复result = nums[i];return result;  // 返回遍历数}}return result;}
}// 牛客
// 牛客题目要求时间复杂度 O(N),空间复杂度 O(1),因此比较严格一点
// 运行时间 14ms
// 占用内存 10048KB
import java.util.HashSet;
public class Solution {// Parameters://    numbers:     an array of integers//    length:      the length of array numbers//    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;//                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++//    这里要特别注意~返回任意重复的一个,赋值duplication[0]// Return value:       true if the input is valid, and there are some duplications in the array number//                     otherwise falsepublic boolean duplicate(int numbers[],int length,int [] duplication) {if (length == 0)return false;HashSet<Integer> set = new HashSet<>();for (int i = 0; i < length; i++) {if (!set.add(numbers[i])) {duplication[0] = numbers[i];return true;}}return false;}
}

原地置换

找重复的数,且数取值在数组长度范围内,大部分数肯定是不重复的,将它们摆放成数字和索引相同的数组,即{0, 2, 3, 1}我们希望摆成{0, 1, 2, 3}。如果数组有两个2,那么原来2的位置有一个2,就会发现重复。

// 时间复杂度 O(n):1 ms, 在所有 Java 提交中击败了90.57%的用户
// 空间复杂度 O(1):45.8 MB, 在所有 Java 提交中击败了98.14%的用户
class Solution {public int findRepeatNumber(int[] nums) {if (nums == null || nums.length <= 0) {return -1;}int i = 0; // 初始化索引iwhile (i <= nums.length-1) {// 若遍历数与索引不等if (nums[i] != i) {// System.out.println(nums[i]);// 若遍历数与以遍历数为索引的对应数相等(遇重复)if (nums[i] == nums[nums[i]]) {return nums[nums[i]]; // 直接返回结果}//交换:遍历数与以遍历数为索引的对应数swap(nums, i);  } // (交换后)确认遍历数与索引相等之后,再右移索引else {i++;}}return -1;}// 定义交换方法private void swap(int[] list, int i) {int temp = list[list[i]];list[list[i]] = list[i];list[i] = temp;}
}// 牛客
import java.util.HashSet;
public class Solution {// Parameters://    numbers:     an array of integers//    length:      the length of array numbers//    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;//                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++//    这里要特别注意~返回任意重复的一个,赋值duplication[0]// Return value:       true if the input is valid, and there are some duplications in the array number//                     otherwise falsepublic boolean duplicate(int numbers[],int length,int [] duplication) {if (length == 0)return false;int i = 0;while (i < length) {if (numbers[i] != i){if (numbers[i] == numbers[numbers[i]]) {duplication[0] = numbers[numbers[i]];return true;}swap(numbers, i);}else i++;}return false;}// 使得numbers[i]和numbers[numbers[i]]交换private void swap(int[] numbers, int i) {int temp = numbers[numbers[i]];numbers[numbers[i]] = numbers[i];numbers[i] = temp;}
}

【剑指offer】04. 二维数组中的查找

题目描述

// 04. 二维数组中的查找
// 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,
// 每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的
// 一个二维数组和一个整数,判断数组中是否含有该整数。
// 限制:// 0 <= n <= 1000
// 0 <= m <= 1000

题解

// 时间复杂度 O(M + N): 18 ms
// 空间复杂度 O(1): 43.1 MB
// 从矩阵右上角开始遍历,则左边的数一定比右边的数小,
// 下边的数一定比上面的数大。
// 比较遍历数和target。
// 若遍历数大,target小,说明target在遍历数左边,遍历位置左移。
// 如果遍历数小,target大,说明target在遍历数下面,遍历位置下移
class Solution {public boolean findNumberIn2DArray(int[][] matrix, int target) {// 排除特殊情况if (matrix == null || matrix.length <= 0 || matrix[0].length <= 0) {return false;}// 取matrix的行和列int rows = matrix.length, cols = matrix[0].length;// 初始化遍历起始点索引,起始点为右上角int i = 0, j = cols-1;while (i <= rows-1 && j >= 0) {System.out.println(i + " " + j);if (matrix[i][j] == target) { return true;}if (matrix[i][j] < target) {i++;}// 防止之前i移动在先,造成越界,加一个i的不越界判定if (i <= rows-1 && matrix[i][j] > target) {j--;}}return false;}
}

【剑指offer】29. 顺时针打印矩阵

题目描述


// 力扣
// 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。// 牛客
// 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字
// ,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

题解

// 牛客
// printRes函数中,下行和左列要加一个row1和row2不相等的判断和
// col1和col2的判断,防止重复遍历。而matrix = [[1,2,3],[4,5,6],[7,8,9]]
// 中的实例比较特殊,我们需要遍历中心的元素5,所以我们在printRes函数
// 中的前两个for循环都不设置row1和row2相等的判断和col1和col2的判断。
// 运行时间:16ms
// 占用内存:9752k
import java.util.ArrayList;
public class Solution {public ArrayList<Integer> printMatrix(int [][] matrix) {int row1 = 0;  // 行上边界int row2= matrix.length - 1;  // 行下边界int col1 = 0;  // 列左边界int col2 = matrix[0].length - 1;  // 列右边界ArrayList<Integer> res = new ArrayList<>();while (row1 <= row2 && col1 <= col2) {// 一个while循环遍历一圈res = printRes(matrix, row1, row2, col1, col2, res);row1++; row2--; col1++; col2--;  // 缩圈}return res;}private ArrayList<Integer> printRes(int[][] matrix, int row1, int row2, int col1, int col2, ArrayList<Integer> res) {// 上行for (int i = col1; i <= col2; i++) {res.add(matrix[row1][i]);}// 右列(右列要从row1+1位置开始,因为row1位置以前的元素在上行遍历过)for (int i = row1 + 1; i <= row2; i++) {res.add(matrix[i][col2]);}// 下行(下行要从col2-1开始,col2以前的位置被右列遍历过)if (row1 != row2) {for (int i = col2 - 1; i >= col1; i--)res.add(matrix[row2][i]);}// 左列(左列在空间位置上,row2以下和row1以上的位置都被遍历过)if (col1 != col2) {for (int i = row2 - 1; i > row1; i--) res.add(matrix[i][col1]);}return res;}
}// 力扣
// 力扣还需要考虑ArrayList到int[]的转换
// 执行用时:5 ms, 在所有 Java 提交中击败了9.34%的用户
// 内存消耗:39.5 MB, 在所有 Java 提交中击败了94.34%的用户
class Solution {public int[] spiralOrder(int[][] matrix) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return new int[0];}int row1 = 0;  // 行上边界int row2= matrix.length - 1;  // 行下边界int col1 = 0;  // 列左边界int col2 = matrix[0].length - 1;  // 列右边界ArrayList<Integer> res = new ArrayList<>();while (row1 <= row2 && col1 <= col2) {res = printRes(matrix, row1, row2, col1, col2, res);row1++; row2--; col1++; col2--;  // 缩圈}int[] result = toIntList(res);return result;}private ArrayList<Integer> printRes(int[][] matrix, int row1, int row2, int col1, int col2, ArrayList<Integer> res) {for (int i = col1; i <= col2; i++) {res.add(matrix[row1][i]);}for (int i = row1 + 1; i <= row2; i++) {res.add(matrix[i][col2]);}if (row1 != row2) {for (int i = col2 - 1; i >= col1; i--)res.add(matrix[row2][i]);}if (col1 != col2) {for (int i = row2 - 1; i > row1; i--) res.add(matrix[i][col1]);}return res;}private int[] toIntList(ArrayList<Integer> res) {int[] result = new int[res.size()];for (int i = 0; i < res.size(); i++) {result[i] = res.get(i);}return result;}
}// 更好的办法是直接用int[] 存储,中间不经过arraylist
// 执行用时:1 ms, 在所有 Java 提交中击败了97.37%的用户
// 内存消耗:39.5 MB, 在所有 Java 提交中击败了85.60%的用户
class Solution {int[] res;int t = 0;public int[] spiralOrder(int[][] matrix) {if (matrix.length == 0)return new int[0];int row1 = 0;int row2 = matrix.length - 1;int col1 = 0;int col2 = matrix[0].length - 1;this.res = new int[(row2 + 1) * (col2 + 1)];while (row1 <= row2 && col1 <= col2) {cyclePrint(matrix, row1, row2, col1, col2);row1++;row2--;col1++;col2--;}return res;}private void cyclePrint(int[][] matrix, int row1, int row2, int col1, int col2) {for (int i = col1; i <= col2; i++) {  // 上边res[t++] = matrix[row1][i];}for (int i = row1 + 1; i <= row2; i++) {  // 右边res[t++] = matrix[i][col2];}if (row1 != row2) {for (int i = col2 - 1; i >= col1; i--) {  // 下边res[t++] = matrix[row2][i];}}if (col1 != col2) {for (int i = row2 - 1; i > row1; i--) {  // 左边res[t++] = matrix[i][col1];}}}
}

【剑指offer】39. 数组中出现次数超过一半的数字

题目描述

// 39. 数组中出现次数超过一半的数字// 力扣
// 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
// 你可以假设数组是非空的,并且给定的数组总是存在多数元素。// 牛客
// 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例
// 如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现
// 了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

题解

/// 直接法 /
// 排序,然后逐个遍历数
// 直接法无法在牛客通过// 力扣
//
// 执行用时:3 ms, 在所有 Java 提交中击败了34.81%的用户
// 内存消耗:41.7 MB, 在所有 Java 提交中击败了59.53%的用户
class Solution {public int majorityElement(int[] nums) {if (nums.length == 0)return -1;if (nums.length == 1)return nums[0];Arrays.sort(nums);int len = nums.length / 2 + 1;int count = 1;int i = 0;while (i < nums.length) {if (nums[i] != nums[i + 1]) count = 1;else {count++;i++;}if (count >= len)break;}return nums[i];}
}

多数投票法我做了个ppt放在代码下面,感兴趣可以看看。

 多数投票法
// 比较好的方法// 如果某个数字符合条件,那么它出现的次数就比其他所有数字出现的次数加起来还要多。
// 具体可以看看这个 https://www.zhihu.com/question/49973163/answer/617122734// 力扣
// 不验证的话,就是取出现次数最多的数字
// 执行用时:1 ms, 在所有 Java 提交中击败了99.99%的用户
// 内存消耗:41.9 MB, 在所有 Java 提交中击败了38.26%的用户
class Solution {public int majorityElement(int[] nums) {int max = nums[0];for (int i = 1, count = 1; i < nums.length; i++) {if (nums[i] == max)count++;else count--;if (count == 0) {max = nums[i];count = 1;}}return max;}
}// 力扣
// 加上验证环节
// 执行用时:2 ms, 在所有 Java 提交中击败了60.11%的用户
// 内存消耗:41.8 MB, 在所有 Java 提交中击败了44.96%的用户
class Solution {public int majorityElement(int[] nums) {int max = nums[0];for (int i = 1, count = 1; i < nums.length; i++) {if (nums[i] == max)count++;else count--;if (count == 0) {max = nums[i];count = 1;}}// 验证环节int count = 0;for (int i = 0; i < nums.length; i++) {if (nums[i] == max)count++;}return count >= nums.length / 2 + 1 ? max : 0;}
}// 牛客
// 牛客必须要有验证环节,否则无法通过,所以牛客要力扣要严格(严谨)
// 运行时间:12ms
// 占用内存:9556k
public class Solution {public int MoreThanHalfNum_Solution(int [] array) {int max = array[0];for (int i = 1, count = 1; i < array.length; i++) {if (array[i] == max)count++;else count--;if (count <= 0) {max = array[i];count = 1;}}// 验证环节int count = 0;for (int i = 0; i < array.length; i++) {if (array[i] == max)count++;}return count >= array.length / 2 + 1 ? max : 0;}
}











【剑指offer】40. 最小的k个数

题目描述

// 力扣
// 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、
// 2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。// 牛客
// 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字
// ,则最小的4个数字是1,2,3,4。

题解

// 暴力法 //// 力扣
// 暴力法,能通过但没有意义
// 执行用时:8 ms, 在所有 Java 提交中击败了64.97%的用户
// 内存消耗:40 MB, 在所有 Java 提交中击败了17.80%的用户
import java.util.Arrays;
class Solution {public int[] getLeastNumbers(int[] arr, int k) {int[] res = new int[k];Arrays.sort(arr);for (int i = 0; i < k; i++) {res[i] = arr[i];}return res;}
}

如果用数组表示堆的话,根据层序顺序标号规则,将层序标号对应到数组索引中,那么在数组索引中找二叉树左右结点可以通过如下规律:找结点的左节点:索引2,找结点的右结点:索引2+1,找结点的父结点:索引 / 2(整数除法,要去除小数点)。

/// 最大堆法 ///
// 比较好的方法// 牛客
// 运行时间:15ms
// 占用内存:9944k
import java.util.ArrayList;
import java.util.PriorityQueue;  // 利用优先队列构建堆
import java.util.Comparator;  // 比较器
public class Solution {public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {if (k > input.length || k < 0)  // 如果k值有问题,返回空数组return new ArrayList<>();// 优先队列默认是最小堆(最小值置顶),将其修改成最大堆(最大值置顶)// 得到构建好的最大堆maxHeapPriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});// for循环遍历input中的元素,将其放入num中for (int num: input) {maxHeap.add(num);// 由于是最大堆,所以最大值一定会被置顶// 当堆中元素超过k(达到k+1)了,那么置顶元素一定比其余k个数大,// 将其弹出。input所有元素被遍历过,且在堆中被比较过大小,// 所以能在堆中留下的k个数就是input里最小的k个数。if (maxHeap.size() > k)  maxHeap.poll();}// 将堆以ArrayList格式返回return new ArrayList<>(maxHeap);}
}// 力扣
// 执行用时:25 ms, 在所有 Java 提交中击败了13.48%的用户
// 内存消耗:39.6 MB, 在所有 Java 提交中击败了77.55%的用户
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Comparator;
class Solution {public int[] getLeastNumbers(int[] arr, int k) {if (k > arr.length || k <= 0)return new int[0];PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});for (int num: arr) {maxHeap.add(num);if (maxHeap.size() > k)maxHeap.poll();}int[] res = new int[k];ArrayList<Integer> kmin = new ArrayList<>(maxHeap);toResList(kmin, res, k);return res;}public void toResList(ArrayList<Integer> kmin, int[] res, int k) {for (int i = 0; i < kmin.size(); i++) {res[i] = kmin.get(i);}}
}// 力扣
// 执行用时:27 ms, 在所有 Java 提交中击败了11.67%的用户
// 内存消耗:40.1 MB, 在所有 Java 提交中击败了8.06%的用户
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Comparator;
class Solution {public int[] getLeastNumbers(int[] arr, int k) {if (k > arr.length || k <= 0)return new int[0];PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});for (int num: arr) {maxHeap.add(num);if (maxHeap.size() > k)maxHeap.poll();}int[] res = new int[k];toResList(maxHeap, res);return res;}public void toResList(PriorityQueue<Integer> maxHeap, int[] res) {int i = 0;for (int num : maxHeap) {res[i++] = num;}}
}

/ 快速选择法 /
// 快速选择和快排很类似// 牛客
// 运行时间:14ms
// 占用内存:9924k
import java.util.ArrayList;
public class Solution {public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {ArrayList<Integer> res = new ArrayList<>();if (k > input.length || k <= 0) return res;findKthSmallest(input, k);  // 快选主函数// 此时input前k个元素即为最小的k个元素for (int i = 0; i < k; i++)  res.add(input[i]);  // 将前k个元素存入res中return res;}  // 快速选择函数// 找k个最小值public void findKthSmallest(int[] input, int k) {int low = 0, high = input.length - 1;  // 初始化左右边界while (low < high) {int size = partition(input, low, high);if (size == k)  // 直到size等于k,停止循环break;if (size > k) // 最小的k个数一定在前size个数中high = size - 1;  // 右边界high左移else // size < k   // 在右侧数组中继续找最小的k个数low = size + 1;  // 左边界low右移}}// 切分函数,我们希望使数组切分值的左边都是小元素,右边都是大元素private int partition(int[] input, int low, int high) {int split = input[low];  // 切分值初始化int i = low, j = high + 1;  // 双指针i,j遍历input元素while (true) {// i从左往右移,遍历到不小于split的元素为止while (i != high && input[++i] < split);// j从右往左移,遍历到不大于split的元素位置while (j != low && input[--j] > split);if (i >= j)break;// input[i]比split大,input[j]比split小,交换位置swap(input, i, j);  }swap(input, low, j);  // input[j]比split值本身要小,交换位置return j;  // 返回}// 交换函数private void swap(int[] input, int i, int j) {int t = input[i];input[i] = input[j];input[j] = t;}
}// 力扣
// 执行用时:3 ms, 在所有 Java 提交中击败了83.07%的用户
// 内存消耗:39.8 MB, 在所有 Java 提交中击败了51.36%的用户
import java.util.ArrayList;
class Solution {public int[] getLeastNumbers(int[] arr, int k) {if (k > arr.length || k <= 0)return new int[0];findKSmallest(arr, k);int[] res = new int[k];for (int i = 0; i < k; i++) {res[i] = arr[i];}return res;}public void findKSmallest(int[] arr, int k) {int low = 0, high = arr.length - 1;while (true) {int size = partition(arr, low, high);if (size == k)break;if (size > k)high = size - 1;else low = size + 1;}}private int partition (int[] arr, int low, int high) {int split = arr[low];int i = low, j = high + 1;while (true) {while (i < high && arr[++i] < split);while (j > low && arr[--j] > split);if (i >= j)break;swap(arr, i, j);}swap(arr, low, j);return j;}private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}

【剑指offer】45. 把数组排成最小的数

题目描述

// 45. 把数组排成最小的数// 力扣
// 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,
// 打印能拼接出的所有数字中最小的一个。// 牛客
// 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印
// 能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则
// 打印出这三个数字能排成的最小数字为321323。

题解

// 力扣
// 执行用时:6 ms, 在所有 Java 提交中击败了77.89%的用户
// 内存消耗:38 MB, 在所有 Java 提交中击败了60.99%的用户
import java.util.Arrays;
class Solution {public String minNumber(int[] nums) {if (nums == null || nums.length == 0)return "";int len = nums.length;  // 取数组nums的长度,即为字符组的长度String res = "";  // 答案存储在res中String[] strlist = new String[len];  // 创建与nums同尺寸的String listfor (int i = 0; i < len; i++)strlist[i] = Integer.toString(nums[i]);  // 转字符串后排入String list// 给字符组list排序,lambda表达式来自定义比较器。// 原本是比较字符组每一个元素(字母)的大小来升序,// 现在是比较字符组每一个元素加起来组成的字符的大小来升序Arrays.sort(strlist, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));// 排序后,将list所有元素按顺序存入resfor (String s : strlist)res += s;return res;  // 最后返回res}
}// 牛客
// 运行时间:100ms
// 占用内存:15052KB
import java.util.ArrayList;
import java.util.Arrays;
public class Solution {public String PrintMinNumber(int [] numbers) {if (numbers == null || numbers.length == 0)return "";String res = "";int len = numbers.length;String[] strList = new String[len];for (int i = 0; i < len; i++)strList[i] = Integer.toString(numbers[i]);Arrays.sort(strList, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));for (String s: strList)res += s;return res;}
}// 牛客
// 比较器修改的另一种写法
// 运行时间:14ms
// 占用内存:9988KB
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Solution {public String PrintMinNumber(int [] numbers) {if (numbers == null || numbers.length == 0)return "";String res = "";int len = numbers.length;String[] strList = new String[len];for (int i = 0; i < len; i++)strList[i] = Integer.toString(numbers[i]);Arrays.sort(strList, new Comparator<String>() {@Overridepublic int compare(String s1, String s2) {return (s1 + s2).compareTo(s2 + s1);}});for (String s: strList)res += s;return res;}
}

【剑指offer】59. 滑动窗口的最大值

题目描述

// 59. 滑动窗口的最大值// 力扣
// 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。// 牛客
// 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例
// 如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个
// 滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,
// 1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1},
// {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {
// 2,3,4,2,6,[2,5,1]}。
// 窗口大于数组长度的时候,返回空

题解

最大堆法

// 最大堆 // 牛客
// 推荐方法。
// 构建最大堆maxHeap,构建答案保存数组res,
// 先把窗口范围size内的元素放入maxHeap,之后堆顶元素(最大值)放入res,
// 然后构建左指针i,右指针j,for循环右移两个指针,之前的左指针遍历元素
// 在maxHeap中去掉,加入右指针的新遍历元素,构成新的maxHeap(窗口遍历元素)
// 再次把堆顶最大值放入res,如此循环。
// 运行时间:14ms,超过75.10%用Java提交的代码
// 占用内存:9744KB,超过9.76%用Java提交的代码
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {public ArrayList<Integer> maxInWindows(int [] num, int size) {ArrayList<Integer> res = new ArrayList<Integer>();if (size > num.length || size < 1)return res;PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});for (int i = 0; i < size; i++) {maxHeap.add(num[i]);}res.add(maxHeap.peek());for (int i = 0, j = i + size; j < num.length; i++, j++) {maxHeap.remove(num[i]);maxHeap.add(num[j]);res.add(maxHeap.peek());}return res;}
}// 力扣
// 执行用时:93 ms, 在所有 Java 提交中击败了5.11%的用户
// 内存消耗:46.4 MB, 在所有 Java 提交中击败了74.46%的用户
class Solution {public int[] maxSlidingWindow(int[] num, int size) {ArrayList<Integer> res = new ArrayList<Integer>();if (size > num.length || size < 1)return toIntList(res);PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});for (int i = 0; i < size; i++) {maxHeap.add(num[i]);}res.add(maxHeap.peek());for (int i = 0, j = i + size; j < num.length; i++, j++) {maxHeap.remove(num[i]);maxHeap.add(num[j]);res.add(maxHeap.peek());}return toIntList(res);}private int[] toIntList(ArrayList<Integer> res) {int[] result = new int[res.size()];for (int i = 0; i < result.length; i++) {result[i] = res.get(i);}return result;}
}

队列方法


// 双端队列
// 双端队列法旨在用双端队列deque来维护窗口元素特别是窗口的最大值元素。
//
// 先排除特殊情况,如果size比nums长度还大,或者size比0小,直接返回空数组。
//
// 定义双端队列deque(用链表代替),定义相应大小的答案保存数组res,
// 第一个for循环将nums中的前size个元素nums[i]从尾部存入deque。由于我们不需要
// deque窗口元素中的所有值,而是只要窗口元素中的最大值,所以如果deque不
// 为空且将要存入的nums[i],比deque的尾部元素要大,在nums[i]存入deque之前,
// 我们将deque尾部元素循环移除,直到deque尾部元素大于等于nums[i],
// 或者直到deque元素已经排空了,这时候再将nums[i]存入,这样子deque的头部
// 元素一定是deque中的最大值元素,并且deque头部到尾部的元素呈现降序的排序。
// 第一个for循环起初始化窗口的作用,将nums中前size个元素放入deque并规范成
// 降序链表,我们可以轻松从头部取到窗口最大值。
//
// 定义res遍历索引j,初始化为0,如果deque初始化好了(非空),将deque的
// 头部元素(窗口最大值)存入res[0]中,j右移。
//
// 第二个for循环,从nums的size索引开始遍历nums的元素nums[i],则nums[i]
// 为窗口的右索引元素,则窗口的左索引元素为nums[i - size],在循环内,
// 条件判断:如果deque的头部元素(窗口最大值)等于左索引元素,则将
// deque中的头部元素去除。
// 然后调用while循环,与第一个for循环初始化deque时相似,如果deque不为空
// 且将要存入的nums[i],比deque的尾部元素要大,在nums[i]存入deque之前,
// 我们将deque尾部元素循环移除,直到deque尾部元素大于等于nums[i],
// 或者直到deque元素已经排空了,这时候再将nums[i]存入。窗口完成一格移动,
// 将res[j]位置赋值为deque的头部元素(窗口最大值),j右移。
// 第二个for循环,主要是完成窗口移动的过程,通过双端链表和插入元素前的
// 贪心删除元素,来维护一个降序的双端链表,所以头部元素就是窗口最大值。
//
// 如此循环,最后返回res即可。
//
// 执行用时:15 ms, 在所有 Java 提交中击败了48.97%的用户
// 内存消耗:47.5 MB, 在所有 Java 提交中击败了12.75%的用户
import java.util.LinkedList;
class Solution {public int[] maxSlidingWindow(int[] num, int size) {if (size > num.length || size < 1)return new int[0];Deque<Integer> queue = new LinkedList<>();int[] res = new int[num.length - size + 1];for (int i = 0; i < size; i++) {while (!queue.isEmpty() && queue.peekLast() < num[i]) {queue.removeLast();}queue.addLast(num[i]);}int j = 0;if (!queue.isEmpty())res[j++] = queue.peekFirst();for (int i = size; i < num.length; i++) {if (!queue.isEmpty() && queue.peekFirst() == num[i - size]) {queue.removeFirst();}while (!queue.isEmpty() && queue.peekLast() < num[i]) {queue.removeLast();}queue.addLast(num[i]);res[j++] = queue.peekFirst();}return res;}
}

【剑指offer】61. 扑克牌中的顺子

题目描述

// 61. 扑克牌中的顺子// 力扣
// 从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的
// 。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,
// 可以看成任意数字。A 不能视为 14。// 牛客
// LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王
// (一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能
// 不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王
// ,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王
// 可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1
// ,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在
// ,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成
// 顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

题解

// 力扣
// 先排除特殊情况,nums中不足5个牌直接false,
// 创建计数数组pokerList,记录每种牌出现的次数,有14种牌所以长度为14,
// 创建最大最小值max和min,用于记录5张牌的最大最小值,
// for循环遍历手牌num,出现次数累计一次pokerList[num]++,
// 如果num是0,跳过判断遍历下一张牌,
// 不是0的话,如果出现次数pokerList[num]大于1,说明有对子,有对子的牌
// 无法构成顺子,直接false。
// 以上不满足的话,如果num小于min,把min更新为num,
// 如果num大于max,把max更新为num。
// 循环结束,我们可以得到手牌中的最大值max和最小值min,
// 在没有重复对子的前提下,如果max-min的值小于等于4,则必定构成顺子,
// 返回true。比如max是5,min是1,说明其他三张牌一定是4 3 2和 0 这四种
// 情况,所以一定构成顺子。
// 执行用时:1 ms, 在所有 Java 提交中击败了91.68%的用户
// 内存消耗:35.8 MB, 在所有 Java 提交中击败了59.10%的用户
class Solution {public boolean isStraight(int[] nums) {if (nums.length != 5)return false;int[] pokerList = new int[14];int max = Integer.MIN_VALUE;int min = Integer.MAX_VALUE;for (int num: nums) {pokerList[num]++;if (num == 0)continue;else if (pokerList[num] > 1)return false;if (num < min)min = num;if (num > max)max = num;}return (max - min <= 4);}
}// 牛客
// 运行时间:13ms,超过72.54%用Java提交的代码
// 占用内存:9600KB,超过6.93%用Java提交的代码
public class Solution {public boolean IsContinuous(int [] numbers) {if (numbers.length != 5)return false;int min = Integer.MAX_VALUE;int max = Integer.MIN_VALUE;int[] poker = new int[14];for (int num: numbers) {poker[num]++;if (num == 0)continue;else if (poker[num] > 1)return false;if (num < min)min = num;if (num > max)max = num;}return (max - min <= 4);}
}

【剑指offer】66. 构建乘积数组

题目描述

// 66. 构建乘积数组// 力扣
// 给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i]
// 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]
// ×A[i+1]×…×A[n-1]。不能使用除法。// 牛客
// 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素
// B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。(注意:规
// 定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-
// 2];)
// 对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。

题解

 暴力 ///
// 【伪解法】
// 力扣
// 无法通过
class Solution {public int[] constructArr(int[] a) {int[] b = new int[a.length];for (int i = 0; i < b.length; i++) {int num = 1;for (int j = 0; j < a.length; j++) {if (j == i)continue;num *= a[j];}b[i] = num;}return b;}
}// 力扣
// 可以看着实例来理解
// 输入: [1,2,3,4,5]
// 输出: [120,60,40,30,24]
//
// 计算b[i]是除a[i]以外所有的a中元素的乘积,
// 我们可以把i看做一个分界线,在i移动的时候,把a中的元素分成了
// 左右两边,我们按照左右两半边来分别进行计算。
// 例如:实例中乘积为b[3]=30的a元素,包括1 2 3和5,左半边就是1 2 3。
// 乘积为b[2]=40的a元素,包括1 2 4和5,左半边是1 2
// 乘积为b[1]=60的a元素,包括1 3 4和5,左半边是1
// 可以发现,左半边其实在逐渐往一个方向累乘,右半边也是。
// 我们只需将两边分开累乘赋值给b,即可高效地完成运算。
//
// 第一个for循环,从左往右遍历a和b,索引记为i,遍历到len-2为止,
// a[0]对b[0]没有贡献, 我们将b[0]初始化为1,
// 将a[i]累乘到product中,将product赋给b[i+1],完成左半边的累乘。
//
// 第二个for循环,从右往左遍历a和b,遍历到1为止,a[i]累乘到product,
// b由于之前已经计算过了,所以product再累乘到b[i - 1]元素。
// 左右半边计算完,则b元素就累乘完了,直接返回b。// 执行用时:2 ms, 在所有 Java 提交中击败了80.14%的用户
// 内存消耗:51.3 MB, 在所有 Java 提交中击败了26.55%的用户
class Solution {public int[] constructArr(int[] a) {if (a == null || a.length == 0)return new int[0];int len = a.length;int[] b = new int[len];int product = 1;b[0] = product;for (int i = 0; i <= len - 2; i++) {product *= a[i];b[i + 1] = product;}product = 1;for (int i = len - 1; i >= 1; i--) {product *= a[i];b[i - 1] *= product;}return b;}
}// 牛客
// 运行时间:9ms超过99.07%用Java提交的代码
// 占用内存:9732KB超过2.41%用Java提交的代码
import java.util.ArrayList;
public class Solution {public int[] multiply(int[] A) {if (A == null || A.length == 0)return new int[0];int len = A.length;int[] B = new int[len];int product = 1;B[0] = 1;for (int i = 0; i <= len - 2; i++) {product *= A[i];B[i + 1] = product;}product = 1;for (int i = len - 1; i >= 1; i--) {product *= A[i];B[i - 1] *= product;}return B;}
}

特殊解——动态规划

【剑指offer】10.1 斐波那契数列

题目描述


// 力扣
// 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。
// 斐波那契数列的定义如下:// F(0) = 0,   F(1) = 1
// F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
// 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两
// 数相加而得出。// 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008
// ,请返回 1。

题解

// 牛客暴力递归法 /
// 递归可以,但是很暴力
public class Solution {public int Fibonacci(int n) {if (n == 1)return 1;if (n == 0)return 0;// 根据定义直接递归法return Fibonacci(n - 1) + Fibonacci(n - 2);}
}
// 动态规划  /
public class Solution {public int Fibonacci(int n) {if (n <= 1) {return n;}int[] f = new int[n + 1];f[1] = 1;// 从0,1开始,从头推到f[n]for (int i = 2; i <= n; i++) { f[i] = f[i - 2] + f[i - 1];   // 循环递推}return f[n];}
}public class Solution {public int Fibonacci(int n) {if (n <= 1) {return n;}int pre1 = 0, pre2 = 1;int f = 0;for (int i = 2; i <= n; i++) {f = pre2 + pre1;pre1 = pre2;pre2 = f;}return f;}
}

// 力扣
// 力扣如果用递归方法会发现直接报时间复杂度超了/// 动态规划
// 执行用时:0 ms , 在所有 Java 提交中击败了 100.00% 的用户
// 内存消耗: 35.2 MB, 在所有 Java 提交中击败了83.53%的用户class Solution {public int fib(int n) {int a = 0, b = 1, sum;for (int i = 0; i < n; i++) {// 答案要求需要取模 1e9+7(1000000007),// 如计算初始结果为:1000000008,请返回 1。sum = (a + b) % 1000000007;a = b;b = sum;}return a;}
}// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.3 MB, 在所有 Java 提交中击败了46.56%的用户
class Solution {public int fib(int n) {if (n == 0 || n == 1)return n;int[] dp = new int[n + 1];dp[0] = 0;dp[1] = 1;for (int i = 2; i < n + 1; i++) {dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007;}return dp[n];}
}

【剑指offer】10.2 青蛙跳台阶问题

题目描述

// 10.2. 青蛙跳台阶问题// 力扣
// 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一
// 个 n 级的台阶总共有多少种跳法。// 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008
// ,请返回 1。// 牛客
// 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的
// 台阶总共有多少种跳法(先后次序不同算不同的结果)。

题解

// 实际上跟斐波那契数列是一个意思,青蛙要么跳1步,f(1)=1
// ,要么跳2步,f(2)=2
// 如果是台阶n阶,计算所有的跳法,
// 首先青蛙跳第一步的时候存在跳1阶和跳2阶两种情况
// 跳1阶,还剩下n-1阶,那么就是f(n-1)种跳法。
// 跳2阶,还剩下n-2阶,那么就是f(n-2)种跳法。
// 所有情况加起来,就是最终n阶台阶跳法,其实就是f(n)=f(n-1)+f(n-2)。
// 其实和斐波那契数列一样的算法。
// 牛客// 暴力递归解,不可取
// 运行时间:317ms
// 占用内存:9584k
public class Solution {public int JumpFloor(int target) {if (target < 3) return target;return JumpFloor(target - 1) + JumpFloor(target - 2);}
}// 动态规划
// 运行时间:9ms
// 占用内存:9688k
public class Solution {public int JumpFloor(int target) {if (target < 3) return target;int prev1 = 1, prev2 = 2;int f = 0;for (int i = 2; i < target; i++) {f = prev1 + prev2;prev1 = prev2;prev2 = f;}return f;}
}
// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.4 MB, 在所有 Java 提交中击败了61.21%的用户
class Solution {public int numWays(int n) {if (n == 0)return 1;if (n < 3)return n;int prev1 = 1, prev2 = 2;int f = 0;for (int i = 2; i < n; i++) {f = (prev1 + prev2) % 1000000007;  // 根据数值要求做修改prev1 = prev2;prev2 = f;}return f;}
}// f(n-1) + f(n-2) = f(n)
// 好理解版
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:35.2 MB, 在所有 Java 提交中击败了60.02%的用户
class Solution {public int numWays(int n) {if (n == 0)return 1;if (n <= 2)return n;int[] dp = new int[n];dp[0] = 1;dp[1] = 2;for (int i = 2; i < n; i++) {dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007;}return dp[n - 1];}
}

【剑指offer】10.3 矩形覆盖

题目描述

// 10. 矩形覆盖// 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。
// 请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?// 比如n=3时,2*3的矩形块有3种覆盖方法

题解

// 如果图画出来就明白了,其实还是斐波那契数列问题。
// 要找有多少种方法,而不关心放了多少块,
// 所以其实2*1小矩形有单块竖放和两块横放两种放法,
// 其中横放必须要两块一起横放。明白了这点,那么:
// 当n=1时,需要覆盖面积为2*1,总共有f(1)=1种放法
// 当n=2时,需要覆盖面积为2*2,总共有f(2)=2种放法(两个横放和两个竖放)
// 当n=n时,需要覆盖面积为2*n,第一步可以竖放可以横放,
// 假设第一步是竖放,则还剩下2*(n-1)覆盖面积,有f(n-1)种放法,
// 假设第一步是横放,还剩下2*(n-2)覆盖面积,有f(n-2)种放法,
// 那么总的方法数为所有放法加起来,所以f(n) = f(n-1) + f(n-2)// 牛客
// 暴力递归法,不推荐
// 运行时间:320ms
// 占用内存:9560kpublic class Solution {public int RectCover(int target) {if (target <= 2)return target;return RectCover(target - 1) + RectCover(target - 2);}
}// 运行时间:9ms
// 占用内存:9652k
public class Solution {public int RectCover(int target) {if (target <= 2)return target;int f = 0;int prev1 = 1, prev2 = 2;for (int i = 3; i <= target; i++) {f = prev1 + prev2;prev1 = prev2;prev2 = f;}return f;}
}public class Solution {public int rectCover(int target) {if (target == 0)return 0;if (target <= 2)return target;int[] dp = new int[target];dp[0] = 1;dp[1] = 2;for (int i = 2; i < target; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[target - 1];}
}

【剑指offer】10.4 变态跳台阶

题目描述

// 10.4 变态跳台阶// 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。
// 求该青蛙跳上一个n级的台阶总共有多少种跳法。

题解

// 牛客
// 动态规划
// n级台阶,第一步有n种跳法:1,2,3,...,n,记为F(n)
// 跳1阶,剩下n-1阶,有F(n-1)种跳法
// 跳2阶,剩下n-2阶,有F(n-2)种跳法
// ...
// 跳n-1阶,剩下1阶,只有1中跳跳法
// 有:
// F(n) = F(n-1) + F(n-2) + ... + F(1) + 1
// F(n- 1) = F(n-2) + F(n-3) + .. + F(1) + 1
// ...
// F(2) = F(1) + 1
// F(1) = 1
// 可以看到F(n)中的每一项都可以按一样的循环来展开。// 运行时间:10ms
// 占用内存:9560k
import java.util.Arrays;
public class Solution {public int JumpFloorII(int target) {int[] f = new int[target];Arrays.fill(f, 1);for (int i = 1; i < target; i++){for (int j = 0; j < i; j++) {f[i] = f[i] + f[j];}}return f[target - 1];}
}// 运行时间:13ms,超过69.65%用Java提交的代码
// 占用内存:9704KB,超过3.29%用Java提交的代码
// f(n)
// f(n-1) + f(n-2) + f(n-3) + ... + f(2) + f(1) + f(0)
// f(0) = 1, f(1) = 2, f(2) =
public class Solution {public int jumpFloorII(int n) {if (n == 0)return 0;if (n <= 2)return n;int[] dp = new int[n + 1];dp[0] = 1;dp[1] = 1;for (int i = 2; i < n + 1; i++) {for (int j = 0; j < i; j++) {dp[i] += dp[j];}}return dp[n];}
}// 数学解
// f(n) = f(n-1) + f(n-2) + f(n-3) + .. + f(1)
// f(n-1) = f(n-2) + f(n-3) + .. + f(1)
// 所以有f(n) - f(n-1) = f(n-1),则,f(n)/f(n-1)=2,为等比数列,
// 根据定义可以计算第n项的值为f(1)*2^(n-1)
// 运行时间:10ms
// 占用内存:9688k
public class Solution {public int JumpFloorII(int target) {return (int) Math.pow(2, target - 1);}
}

【剑指offer】42. 连续子数组的最大和

题目描述


// 42. 连续子数组的最大和// 力扣
// 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。
// 求所有子数组的和的最大值。// 要求时间复杂度为O(n)。// 牛客
// 输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整
// 数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n).

题解

// 暴力法
// 暴力法在力扣无法通过// 牛客
// 运行时间:10ms
// 占用内存:9448k
public class Solution {private int max = 0;public int FindGreatestSumOfSubArray(int[] array) {if (array.length == 0)return 0;max = array[0];for (int i = 0; i < array.length; i++) {int pathSum = 0;for (int j = i; j < array.length; j++) {pathSum += array[j];if (pathSum >= max)max = pathSum;}}return max;}
}

// 动态规划 //// 牛客
// 运行时间:10ms
// 占用内存:9544k
public class Solution {    public int FindGreatestSumOfSubArray(int[] array) {if (array.length == 0)return 0;int max = array[0];  // 将max初始化为array第一个元素int pathSum = 0;  // 当前遍历子数组路径的和记为pathSumfor (int val: array) {  // 遍历array中的元素记为valif (pathSum <= 0)  // 如果pathSum不是正值,加val也不会使val增多pathSum = val;  // 还不如直接令pathSum修改为当前的valelse  // 如果pathSum是正值,不管val是正是负,都会对val有增加pathSum += val;  // 所以此时pathSum直接累加valif (pathSum >= max)  // 每次运算将大数保存至maxmax = pathSum;}return max;}
}// 看完上面的注释,还需要补充一点的是:
// 这道题需要连续的子数组,我们遍历数组一定是从左往右,
// val是一定要加的,不可能跳过val去加下一个,而pathSum是之
// 前遍历的子数组的和,是可以保留也可以放弃的。
// 因此pathSum的处理上,我们通过判断pathSum对val值的增益与否,
// 来选择是否累加val,还是直接将pathSum重置为val。
// (而不是判断val的正负与否来累加,因为val是一定要加的。)// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了97.97%的用户
// 内存消耗:45 MB, 在所有 Java 提交中击败了42.77%的用户
class Solution {public int maxSubArray(int[] nums) {if (nums.length == 0)return 0;int max = nums[0];int pathSum = 0;for (int val: nums) {if (pathSum <= 0) pathSum = val;else pathSum += val;if (pathSum >= max)max = pathSum;}return max;}
}

【剑指offer】47. 礼物的最大价值

题目描述

// 47. 礼物的最大价值// 力扣
// 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值
// (价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次
// 向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上
// 面的礼物的价值,请计算你最多能拿到多少价值的礼物?

题解

// 题解其实不难,就是动态规划,权衡对哪个方向(左还是上)求和累计的值最大
// 那个方向累计和最大就累加哪个方向的值。
// 本题要的是最大值,而不是路径,我们只需要得到左上到右下的最大值即可。
// 因此同样是动态规划,可以根据动态规划存储方式的不同来优化。
// 力扣
// 本地dp
// 执行用时:3 ms, 在所有 Java 提交中击败了80.03%的用户
// 内存消耗:41.1 MB, 在所有 Java 提交中击败了58.97%的用户
class Solution {public int maxValue(int[][] grid) {if (grid == null || grid.length == 0)return 0;int row = grid.length, col = grid[0].length;for (int i = 0; i < row; i++) {for (int j = 0; j < col; j++) {if (i == 0 && j == 0) continue;if (i == 0)grid[i][j] += grid[i][j - 1];else if (j == 0) grid[i][j] += grid[i - 1][j];elsegrid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);}}return grid[row - 1][col - 1];}
}

示意图:










则最后有:

// 力扣
// 单数组维护dp存储
// 执行用时:2 ms, 在所有 Java 提交中击败了97.66%的用户
// 内存消耗:41.1 MB, 在所有 Java 提交中击败了58.97%的用户
class Solution {public int maxValue(int[][] grid) {if (grid == null || grid[0].length == 0)return 0;int col = grid[0].length;int[] dp = new int[col];for (int[] row : grid) {dp[0] += row[0];for (int i = 1; i < col; i++) {// 如果dp[i]没有赋值,则肯定Math给的是dp[i - 1]// 如果dp[i]已经被赋值了,Math.max中的dp[i]为// 上一行的i列的dp值。dp[i] = Math.max(dp[i], dp[i - 1]) + row[i];}}return dp[col - 1];}
}

示意图:






特殊解——二分法

【剑指offer】11. 旋转数组的最小数字

题目描述


// 力扣
// 11. 旋转数组的最小数字// 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的
// 旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元
// 素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组
// 的最小值为1。  // 牛客
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

题解

// 二分查找// 力扣
// 执行用时:14 ms, 在所有 Java 提交中击败了38.70%的用户
// 内存消耗:38.7 MB, 在所有 Java 提交中击败了22.10%的用户
// 牛客
// 运行时间:177ms
// 占用内存:28280k
class Solution {public int minArray(int[] numbers) {if (numbers.length == 0) {return 0;}int start = 0, end = numbers.length - 1;while (start < end) {int mid = (start + end) / 2;// System.out.println("start: " + start + " mid: " + mid + " end: " + end);  // 可以打印中间结果试试if (numbers[start] == numbers[mid] && numbers[mid] == numbers[end]) {  // 如果三索引数相等,直接for循环遍历找最小值return findMin(numbers, start, end);}else if (numbers[mid] <= numbers[end]) {  // 这里取end索引和mid索引数判断比较方便。// 因为如果发生翻转,翻转部分的所有数值都会比左边第一个数值要小(或相等)。// 如果end索引数比mid索引数大(或相等),最小值一定在左半边,end移到mid位置。end = mid;}else                                      // 否则(如果mid索引数比end索引数大),start移动到mid+1位置start = mid + 1;}return numbers[start];  // 直到start与end索引相遇,直接返回该索引数}// findMin:从左到右,for循环遍历找最小值(根据题意,从左到右遇到的第一个小数即为最小数)public static int findMin(int[] numbers, int start, int end) {for (int i = start; i <= end; i++) {if (numbers[start] > numbers[i]) {return numbers[i];}}return numbers[start];}
}// 牛客
// [3,3,3,4,1]
// [3,4,1,3,3]
// [4,1,3,3,3]
// 运行时间:171ms 超过87.93%用Java提交的代码
// 占用内存:28412KB 超过9.02%用Java提交的代码
import java.util.ArrayList;
public class Solution {public int minNumberInRotateArray(int [] arr) {int len = arr.length;if (len == 0)return 0;int left = 0, right = len - 1;while (left <= right) {int mid = (left + right) / 2;if (arr[left] == arr[mid] && arr[right] == arr[mid])return findMin(arr, left, right);if (arr[right] >= arr[mid]) {right = mid;}else {left = mid + 1;}}return -1;}private int findMin(int[] arr, int left, int right) {for (int i = left; i <= right; i++) {if (arr[left] > arr[i])return arr[i];}return arr[left];}
}
// 直接解// 牛客
// 运行时间:204ms
// 占用内存:28728KB
// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38.3 MB, 在所有 Java 提交中击败了70.89%的用户
public class Solution {public int minNumberInRotateArray(int [] array) {if(array.length == 0){return 0;}else{for(int i = 0; i < array.length - 1; i++){if(array[i + 1] - array[i] < 0){return array[i+1];}}return array[0];}}
}

【剑指offer】53. 数字在排序数组中出现的次数

题目描述

// 53. 数字在排序数组中出现的次数// 力扣
// 统计一个数字在排序数组中出现的次数。// 牛客
// 统计一个数字在升序数组中出现的次数。

题解

// 二分查找 // 牛客
// 二分查找法,构建二分查找函数binarysearch,
// binarysearch函数,输入arr和k,通过二分查找方法找到k数字(的重复集),
// 并返回k数字(的重复集)末端的下一位索引。
// 具体情况:arr首端索引left,arr末端索引right,对半找中点索引
// mid = (left + right) / 2,如果中点索引arr[mid]不大于目标k,则
// left更新到mid的下一位索引mid+1,如此循环,直到left到达第一个大于
// k的元素的索引。
//
// 主函数GetNumberOfK则调用两次binarysearch,第一次调用binarysearch(array, k)
// 找k之后第一个大于k的元素(第一次调用找到k重复集的length索引)。
// 第二次调用binarysearch(array, k - 1)找k-1之后第一个大于k-1的元素(就是k),
// (第二次调用找到k重复集的0索引),两个返回值相减,就是k的出现次数(k
// 重复集的长度)// 运行时间:11ms,超过84.49%用Java提交的代码
// 占用内存:9608KB,超过5.82%用Java提交的代码
public class Solution {public int GetNumberOfK(int[] array , int k) {return binarysearch(array, k) - binarysearch(array, k - 1);}public int binarysearch(int[] arr, int k) {int left = 0;int right = arr.length - 1;while (left <= right) {int mid = (left + right) / 2;if (arr[mid] <= k) {left = mid + 1;}else {  // k < arr[mid]right = mid - 1;}}return left;}
}// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:41.3 MB, 在所有 Java 提交中击败了70.38%的用户
class Solution {public int search(int[] nums, int target) {return binarysearch(nums, target) - binarysearch(nums ,target - 1);}public int binarysearch(int[] arr, int target) {int left = 0;int right = arr.length - 1;while (left <= right) {int mid = (left + right) / 2;if (arr[mid] <= target)left = mid + 1;else  // target < arr[mid] right = mid - 1;}return left;}
}

【剑指offer】53.2 0~n-1中缺失的数字

题目描述

// 53.2 0~n-1中缺失的数字// 力扣
// 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都
// 在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该
// 数组中,请找出这个数字。

题解

/ 直接法
// 直接法不通用,还是得想想其他方法。// 力扣
// 根据题意,说明元素和索引必须相等,不相等说明索引对应的元素缺失,
// 就直接返回索引。如果遍历完没发现元素索引的冲突,说明缺失的就是末端元素
// 直接返回末端的下一位索引。
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:39 MB, 在所有 Java 提交中击败了50.07%的用户
class Solution {public int missingNumber(int[] nums) {for (int i = 0; i < nums.length; i++) {if (i != nums[i])return i;}return nums.length;}
}// 二分查找 ///// 力扣
// 二分查找法和53题是一样的,
// 初始化首尾指针left和right,对半取中间位索引mid = (left + right) / 2
// ,如果遍历的元素nums[mid]和mid相等,说明缺失元素的位置在右半边,left更新
// 为mid+1,否则(如果遍历元素nums[mid]>mid),说明缺失元素在左半边,
// right更新为mid-1,left和right相遇时即为缺失元素的索引位置,直接返回即可
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38.9 MB, 在所有 Java 提交中击败了60.99%的用户
class Solution {public int missingNumber(int[] nums) {int left = 0;int right = nums.length - 1;while (left <= right) {int mid = (left + right) / 2;if (nums[mid] == mid)left = mid + 1;else  // nums[mid] > midright = mid - 1;}return left;}
}

特殊解——回溯搜索/DFS/BFS

【剑指offer】12. 矩阵中的路径

题目描述

// 12. 矩阵中的路径// 力扣
// 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某
// 字符串所有字符的路径。路径可以从矩阵中的任意一格开始,
// 每一步可以在矩阵中向左、右、上、下移动一格。如果一条路
// 径经过了矩阵的某一格,那么该路径不能再次进入该格子。例
// 如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径
// 中的字母用加粗标出)。
// [["a","b","c","e"],
// ["s","f","c","s"],
// ["a","d","e","e"]]// 但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占
// 据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。// 牛客
// 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所
// 有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在
// 矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩
// 阵中的某一个格子,则该路径不能再进入该格子。 例如
// [["a","b","c","e"],
// ["s","f","c","s"],
// ["a","d","e","e"]]
// 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因
// 为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能
// 再次进入该格子。

题解

/// 回溯搜索法 ///
构建待搜索矩阵同尺寸的布尔矩阵marked,用于标记该位置元素是否被使用过。
设置路径步长pathLen,用于同步搜索目标的长度。
遍历待搜索矩阵matrix每一个位置的元素,对该位置的元素做所有方向的搜索,搜索不到就回溯到原状态,之前修改的marked和pathLen要重置。// 牛客// 运行时间 12ms
// 占用内存 9592KB
public class Solution {private final static int[][] next = {{0,-1}, {0,1}, {-1,0}, {1,0}}; // 左右上下四个方向private int rows;private int cols;// 解题函数public boolean hasPath(char[] array, int rows, int cols, char[] str) {if (rows == 0 || cols == 0)return false;this.rows = rows;this.cols = cols;boolean[][] marked = new boolean[rows][cols];  // 标记状态矩阵,可标记某元素是否被使用,回溯后使用状态被清空char[][] matrix = buildMatrix(array);  // 题目给的数组char[]转化为矩阵char[][]// 两层for循环,遍历待搜索矩阵matrix的所有元素,第r行第c列元素为matrix[c][r]for (int r = 0; r < rows; r++) {for (int c = 0; c < cols; c++) {if (backTracking(matrix, str, marked, r, c, 0)) {return true;}}}return false;}// 回溯搜索法// 输入变量:待搜索矩阵matrix,搜索目标str,状态标记矩阵marked(初始化为全false), 遍历行r,遍历列c,已遍历的路径步长pathLen(初始化为0)// 每一次backTracking搜索,路径长度pathLen将+1,搜索失败,则回溯进行下一次for遍历// pathLen又会重置为0,marked也会重置为全false。private boolean backTracking(char[][] matrix, char[] str, boolean[][] marked, int r, int c, int pathLen) {if (pathLen == str.length)  // 如果遍历路径步长等于搜索目标str的长度,返回truereturn true;if (r < 0 || r >= rows || c < 0 || c >= cols)  // 如果遍历行和列超出边界,返回false,(回到上一次marked的标记和路径步数pathLen,即进行了回溯)return false;if (marked[r][c])  // 如果遍历元素被使用过,返回false,进行回溯return false;if (matrix[r][c] != str[pathLen])  // 如果搜索矩阵遍历元素与当前路径步数对应的搜索目标字符不相等,返回false,进行回溯return false;                  // 换言之,不返回false,说明遍历元素与对应目标字符是一致的。marked[r][c] = true;  // 当前元素标记为已被使用// 开始以当前遍历元素matrix[r][c]为起点进行搜索,for循环取next中左右上下四个方向,递归backTracking进行搜索// 路径pathLen+1for (int[] dic : next) {  if (backTracking(matrix, str, marked, r + dic[0], c + dic[1], pathLen + 1)) {return true;}}// 当前遍历元素搜索结束,未发现目标,元素标记重置回falsemarked[r][c] = false;return false;  // 搜索结束,返回false。进行回溯}// 将数组char[]转为矩阵char[][]public char[][] buildMatrix(char[] array) {char[][] matrix = new char[rows][cols];int idx = 0;for (int r = 0; r < rows; r++) {for (int c = 0; c < cols; c++) {matrix[r][c] = array[idx++];}}return matrix;}
}
// 力扣// 和牛客原理是一样的
// 执行用时:7 ms, 在所有 Java 提交中击败了40.96%的用户
// 内存消耗:40.1 MB, 在所有 Java 提交中击败了87.40%的用户
class Solution {private int rows;private int cols;private final int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};  // 左右上下public boolean exist(char[][] board, String word) {this.rows = board.length;this.cols = board[0].length;char[] wordarray = word.toCharArray();if (rows == 0 || cols == 0)return false;boolean[][] marked = new boolean[rows][cols];for (int r = 0; r < rows; r++) {for (int c = 0; c < cols; c++) {if (backTracking(board, wordarray, marked, r, c, 0)) {return true;}}}return false;}public boolean backTracking(char[][] matrix, char[] str, boolean[][] marked, int r, int c, int pathLen) {if (pathLen == str.length)return true;if (r < 0 || r >= rows || c < 0 || c >= cols)return false;if (marked[r][c])return false;if (matrix[r][c] != str[pathLen])return false;marked[r][c] = true;for (int[] dic : next) {if (backTracking(matrix, str, marked, r + dic[0], c + dic[1], pathLen + 1)) return true;}marked[r][c] = false;return false;}
}// 差不多的实现
// 执行用时:8 ms, 在所有 Java 提交中击败了24.52%的用户
// 内存消耗:40.4 MB, 在所有 Java 提交中击败了22.69%的用户
class Solution {int[][] direction = {{-1,0}, {1,0}, {0,1}, {0,-1}};boolean[][] used;char[][] board;String word;public boolean exist(char[][] board, String word) {int row = board.length;int col = board[0].length;this.used = new boolean[row][col];this.board = board;this.word = word;for (int i = 0; i < row; i++) {for (int j = 0; j < col; j++) {if (search(row, col, i, j, 0))return true;}}return false;}private boolean search(int row, int col, int i, int j, int wi) {if (wi == word.length()) {return true;}if (i < 0 || i >= row || j < 0 || j >= col || wi > word.length()) {return false;}if (used[i][j]) {return false;}if (board[i][j] != word.charAt(wi)) {return false;}used[i][j] = true;for (int[] dic: direction) {int x = dic[0];int y = dic[1];if (search(row, col, i + x, j + y, wi + 1))return true;}used[i][j] = false;return false;}
}

【剑指offer】13. 机器人的运动范围

题目描述

// 13. 机器人的运动范围
// 力扣
// 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。
// 一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、
// 右、上、下移动一格(不能移动到方格外),也不能进入行坐标
// 和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能
// 够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [
// 35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?// 牛客
// 地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,
// 每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐
// 标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够
// 进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35
// ,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

题解

// 力扣// 本题使用的方法为深度优先搜索(DFS),方法和12题的回溯法很像,
// 但其实回溯法是DFS的特殊情况。
// 执行用时:2 ms, 在所有 Java 提交中击败了43.58%的用户
// 内存消耗:35.4 MB, 在所有 Java 提交中击败了84.10% 的用户
class Solution {private int rows;private int cols;private int k;private int[][] digitSum;private static final int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};private int count = 0;// 解题函数public int movingCount(int m, int n, int k) {this.rows = m;this.cols = n;this.k = k;if (rows == 0 || cols == 0) // 行或者列其中一个为0,则返回0return 0;if (k == 0) // 如果k为0,则能到达位置只有(0,0)这第一个位置return 1;boolean[][] marked = new boolean[rows][cols]; // 定义与搜索矩阵同尺寸的标记矩阵markeddfs(marked, 0, 0);  // 开始深度优先搜索递归,起始位置为左上角(0,0)return count;  // 搜索完毕,返回计数}// 定义搜索函数// 输入标记矩阵marked,行索引r(初始化为0),列索引c(初始化为0)private void dfs(boolean[][] marked, int r, int c) {if (r < 0 || r >= rows || c < 0 || c >= cols)  // 超过正常矩阵边界,返回return;if (marked[r][c])  // 该位置已被遍历过,返回return;if ((digitSum(r) + digitSum(c)) > k)  // 超过数位和的上限,返回return;count++;  // 如果满足条件,成功计数一次marked[r][c] = true;  // 该位置标记为已被使用// System.out.println(markedToString(marked));  // 打印marked矩阵for (int[] n : next)dfs(marked, r + n[0], c + n[1]);}// 定义函数:位数之和public int digitSum(int n) {int n0 = n / 1000000000;int n1 = n % 1000000000 / 100000000;int n2 = n % 100000000 / 10000000;int n3 = n % 10000000 / 1000000;int n4 = n % 1000000 / 1000000;int n5 = n % 100000 / 10000;int n6 = n % 10000 / 1000;int n7 = n % 1000 / 100;int n8 = n % 100 / 10;int n9 = n % 10;int sum = n0 + n1 + n2 + n3 + n4 + n5 + n6 +n7 + n8 + n9;return sum;}// 打印marked的toString函数(不是必须)public String markedToString(boolean[][] marked) {StringBuilder res = new StringBuilder();for (int i = 0; i < marked.length; i++) {res.append("[");for (int j = 0; j < marked[0].length; j++) {if (marked[i][j])res.append(" true ");else res.append(" false ");}res.append("]");res.append("\r\n");}return res.toString();}
}// 换一种位数加法的写法
// 执行用时:2 ms, 在所有 Java 提交中击败了44.08%的用户
// 内存消耗:35.6 MB, 在所有 Java 提交中击败了38.95%的用户
class Solution {int[][] direction = new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};boolean[][] used;int count = 0;public int movingCount(int m, int n, int k) {this.used = new boolean[m][n];search(m, n, 0, 0, k);return count;}private void search(int m, int n, int i, int j, int k) {if (digitSum(i, j) > k) return;if (i < 0 || i >= m || j < 0 || j >= n) return;if (used[i][j])return;used[i][j] = true;count++;for (int[] dic: direction) {int x = dic[0];int y = dic[1];search(m, n, i + x, j + y, k);}}private int digitSum(int row, int col) {int res = 0;int a = row;int b = col;while (a != 0) {res += a % 10;a = a / 10;}while (b != 0) {res += b % 10;b = b / 10;}return res;}
}
// 牛客
// 思路和力扣是一样的
// 运行时间:11ms
// 占用内存:9692kpublic class Solution {private int rows;private int cols;private int threshold;private int[][] digitSum;private static final int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};private int count = 0;public int movingCount(int threshold, int rows, int cols) {this.rows = rows;this.cols = cols;this.threshold = threshold;if (rows == 0 || cols == 0)return 0;if (threshold == 0)return 1;boolean[][] marked = new boolean[rows][cols];dfs(marked, 0, 0);return count;}private void dfs(boolean[][] marked, int r, int c) {if (r < 0 || r >= rows || c < 0 || c >= cols)  return;if (marked[r][c])  return;if ((digitSum(r) + digitSum(c)) > threshold)  return;count++;  marked[r][c] = true;  // System.out.println(markedToString(marked));  for (int[] n : next)dfs(marked, r + n[0], c + n[1]);}// 定义函数:位数之和public int digitSum(int n) {int n0 = n / 1000000000;int n1 = n % 1000000000 / 100000000;int n2 = n % 100000000 / 10000000;int n3 = n % 10000000 / 1000000;int n4 = n % 1000000 / 1000000;int n5 = n % 100000 / 10000;int n6 = n % 10000 / 1000;int n7 = n % 1000 / 100;int n8 = n % 100 / 10;int n9 = n % 10;int sum = n0 + n1 + n2 + n3 + n4 + n5 + n6 +n7 + n8 + n9;return sum;}// 打印marked的toString函数(不是必须)public String markedToString(boolean[][] marked) {StringBuilder res = new StringBuilder();for (int i = 0; i < marked.length; i++) {res.append("[");for (int j = 0; j < marked[0].length; j++) {if (marked[i][j])res.append(" true ");else res.append(" false ");}res.append("]");res.append("\r\n");}return res.toString();}
}

特殊解——排序

【剑指offer】21. 调整数组顺序使奇数位于偶数前面

题目描述

// 力扣
// 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,
// 使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。// 牛客
// 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有
// 的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇
// 数和奇数,偶数和偶数之间的相对位置不变。// 牛客的题目要难一点点,需要保证奇数之间,偶数之间的相对位置不变。

题解

// 双指针法 ///
// 力扣
// 执行用时:3 ms, 在所有 Java 提交中击败了29.92%的用户
// 内存消耗:46.4 MB, 在所有 Java 提交中击败了77.86%的用户
class Solution {public int[] exchange(int[] nums) {int head = 0;int end = nums.length - 1;int temp = 0;while (head < end) {if (nums[head] % 2 == 0 && nums[end] % 2 != 0) { // 头指针找偶数,尾指针找奇数temp = nums[head];  // 偶数存临时变量nums[head] = nums[end];  // 奇数放前面nums[end] = temp;end -= 1;head += 1;}else if (nums[head] % 2 == 0) { // 头指针找偶数,尾指针没找到奇数end -= 1;continue;}else if (nums[end] % 2 != 0) {  // 尾指针找奇数,头指针没找到head += 1;continue;}else {end -= 1;head += 1;}}return nums;}
}// 简化一下
class Solution {public int[] exchange(int[] nums) {int[] res = new int[nums.length];int t = 0, l = 0, r = nums.length - 1;while (l <= r) {if (isEven(nums[t])) {res[r--] = nums[t++];} else {res[l++] = nums[t++];}}return res;}private boolean isEven(int x) {boolean res = false;if (x % 2 == 0) res = true;return res;}
}
/ 直接法 /// 牛客
// 运行时间:9ms
// 占用内存:9780k
public class Solution {public void reOrderArray(int[] array) {int[] dummy = array.clone();  // 用于遍历,原array用于修改int head = 0;int countOdd_end = 0;for (int x : array) {if (!isEven(x))countOdd_end += 1;}for (int x : dummy) {if (isEven(x)) {array[countOdd_end++] = x;}else {array[head++] = x;}}}// 判断是否是偶数private boolean isEven(int x) {return (x % 2 == 0);}
}// 力扣
// 执行用时:4 ms, 在所有 Java 提交中击败了9.50%的用户
// 内存消耗:47.8 MB, 在所有 Java 提交中击败了12.24%的用户
public class Solution {public int[] exchange(int[] array) {int[] dummy = array.clone();int head = 0;int countOdd_end = 0;for (int x : array) {if (!isEven(x))countOdd_end += 1;}for (int y : dummy) {if (isEven(y)) {array[countOdd_end++] = y;}else {array[head++] = y;}}return array;}// 判断是否是偶数private boolean isEven(int x) {return (x % 2 == 0);}
}
 冒泡
// 牛客
// 运行时间:9ms
// 占用内存:9652k
public class Solution {public void reOrderArray(int[] array) {int len = array.length;int temp = 0;for (int i = len - 1; i > 0; i--) {for (int j = 0; j < i; j++) {if (isEven(array[j]) && !isEven(array[j + 1])) {temp = array[j];array[j] = array[j + 1];array[j + 1] = temp;}}}}// 判断是否是偶数private boolean isEven(int x) {return (x % 2 == 0);}
}

【剑指offer】51. 数组中的逆序对

题目描述

// 51. 数组中的逆序对// 力扣
// 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两
// 个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对
// 的总数。// 牛客
// 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数
// 字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。
// 并将P对1000000007取模的结果输出。 即输出P%1000000007

题解

 暴力法 ///// 最直观的的做法是两个for循环全部遍历比对一轮
// 但是这种做法时间复杂度无法通过。// 力扣
// 无法通过
class Solution {public int reversePairs(int[] nums) {int count = 0;if (nums.length < 2)return 0;for (int i = 0; i < nums.length; i++) {for (int j = i + 1; j < nums.length; j++) {if (nums[i] > nums[j])count++;}}return count;}
}归并排序法 // 牛客
// 根据题意,前大后小的两个数字构成一个逆序对。
// 直接写一个归并排序,在merge函数当nums[l]<nums[r]时,记录逆序对个数
// 运行时间:235ms
// 占用内存:33856KB
public class Solution {int count = 0;public int InversePairs(int[] nums) {int[] temp = new int[nums.length];mergesort(nums, 0, nums.length - 1, temp);return count % 1000000007;}private void mergesort(int[] nums, int left, int right, int[] temp) {if (left < right) {int mid = (left + right) / 2;mergesort(nums, left, mid, temp);mergesort(nums, mid + 1, right, temp);merge(nums, left, mid, right, temp);}}// merge合并有序数组,所以左子数组是升序的,右子数组也是升序的private void merge(int[] nums, int left, int mid, int right, int[] temp) {int l = left;int r = mid + 1;int t = 0;// 之前一直是nums[l]<=nums[r],l右移如果出现了nums[l]>nums[r],// 说明l到mid的数,跟nums[r]都组成逆序对。(归并排序,左子数组元// 素排序后还会在左子数组,右子数组元素排序后还会在右子数组)while (l <= mid && r <= right) {if (nums[l] <= nums[r])temp[t++] = nums[l++];else {temp[t++] = nums[r++];count += mid - l + 1;count = count % 1000000007;}}while (l <= mid) {temp[t++] = nums[l++];}while (r <= right) {temp[t++] = nums[r++];}t = 0;while (left <= right)nums[left++] = temp[t++];}
}// 力扣
// 执行用时:37 ms, 在所有 Java 提交中击败了61.83%的用户
// 内存消耗:48.3 MB, 在所有 Java 提交中击败了37.82%的用户
class Solution {public int count = 0;public int reversePairs(int[] nums) {int[] temp = new int[nums.length];mergesort(nums, 0, nums.length - 1, temp);return count;}private void mergesort(int[] nums, int left, int right, int[] temp) {if (left< right) {int mid = (left + right) / 2;mergesort(nums, left, mid, temp);mergesort(nums, mid + 1, right, temp);merge(nums, left, mid, right, temp);}}private void merge(int[] nums, int left, int mid, int right, int[] temp) {int l = left;int r = mid + 1;int t = 0;while (l <= mid && r <= right) {if (nums[l] <= nums[r])temp[t++] = nums[l++];else {temp[t++] = nums[r++];count += mid - l + 1;}} while (l <= mid)temp[t++] = nums[l++];while (r <= right)temp[t++] = nums[r++];t = 0;while (left <= right)nums[left++] = temp[t++];}
}

特殊解——位运算

【剑指offer】56. 数组中数字出现的次数

题目描述


// 56. 数组中数字出现的次数// 力扣
// 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次
// 。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(
// n),空间复杂度是O(1)。// 牛客
// 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写
// 程序找出这两个只出现一次的数字。

题解

集合辅助解

 集合辅助 // 第一时间能想到的简单方法,好理解但不是最优// 力扣
// 定义一个集合set,遍历nums中的所有元素nums[i],并将nums[i]存入set中
// 如果存不进,说明nums[i]出现了2次,在set中删除nums[i],留下来的就
// 都是只出现过一次的元素了,提取出来存入数组res中,返回即可。
// 执行用时:13 ms, 在所有 Java 提交中击败了10.23%的用户
// 内存消耗:39.7 MB, 在所有 Java 提交中击败了97.13%的用户
import java.util.HashSet;
class Solution {public int[] singleNumbers(int[] nums) {HashSet<Integer> set = new HashSet<Integer>();for (int i = 0; i < nums.length; i++) {if (!set.add(nums[i]))set.remove(nums[i]);}int[] res = new int[set.size()];int i = 0;for (int num: set) {res[i++] = num;}return res;}
}// 牛客
// 运行时间:11ms,超过59.56%用Java提交的代码
// 占用内存:11092KB,超过51.92%用Java提交的代码import java.util.*;
public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param array int整型一维数组 * @return int整型一维数组*/public int[] FindNumsAppearOnce (int[] array) {HashSet<Integer> set = new HashSet<Integer>();for (int i = 0; i < array.length; i++) {if (!set.add(array[i]))set.remove(array[i]);}int[] res = new int[set.size()];int i = 0;for (int num: set) {res[i++] = num;}return res;}
}

最优解

/// 位运算法 /// 位运算法才是真正要学的最优解法
// 首先需要说明,异或操作 ^ 的性质包括:
// 交换律
// 结合律
// A ^ 0 = A;
// A ^ A = 0// 问题一:
// 假设nums只存在1个只出现过1次的数字,为A,其他数字都出现了2次
// 且nums数组为{10, 10, A, 2, 5, 5, 2},那么我们把数字全部异或起来
// 就可以把A分离出来,
// 即 10^10^A^2^5^5^2 = 10^10^2^2^5^5^A (交换律) = 0^0^0^A = A
// 如果把题目换成这样,本题就做完了,可惜换不得。
//
// 问题二:
// 题目中,数组nums存在2个只出现1次的数字,假设为A,B。
// 其他数字都出现了两次,我们假设nums数组为{10, 10, A, 2, 5, B, 5, 2}。
// 那么我们是否可以把这道题分解为两个问题一来解决呢?
// 化繁为简,先把nums中所有元素异或起来,得到
// 10^10^A^2^5^B^5^2 = A^B。
// A B肯定是不同的数,既然是不同的数,A B一定存在某一位(假设是第x位)
// 上的数是不同的,而在二进制视角中,数字要么是1要么是0,
// 所以在第x位上要么A是1,B是0,要么B是1,A是0,这样才有A B在第x位上的
// 数是不同的。所以A B的异或A^B,在第x位上一定等于1 (1^0=1)。
//
// 我们用 A^B第x位上的1,来把问题二分解成问题一。
// 我们设置一个辅助参数 one=1(二进制00000001),循环左移和A^B来做
// 逻辑与运算,来找这个第x位,如果one & (A^B) == 0,one就左移,
// 直到one & (A^B) != 0,说明找到了第x位。
//
// 然后循环遍历nums中的数,记为num,还记得第x位上要么A是1,B是0,
// 要么B是1,A是0,我们把num和one直接做与运算,
// 用if条件判断,把所有逻辑与的结果是1的数字,全部异或到int x参数上。
// 用if条件判断,把所有逻辑与的结果是0的数字,全部异或到int y参数上。
// 这样,不管第x位上A B是0还是1,我们都把它们分开到了不同的地方去异或
// 就把问题二分解成了两个问题一。最后直接返回x和y就行~// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:40.2 MB, 在所有 Java 提交中击败了34.85%的用户
class Solution {public int[] singleNumbers(int[] nums) {int x = 0, y = 0, t = 0, one = 1;for (int num : nums)t ^= num;while ((t & one) == 0)one <<= 1;for (int num : nums) {if ((num & one) != 0)x ^= num;else y ^= num;}return new int[] {x, y};}
}// 牛客
// 牛客比力扣多了一点,需要把小的数排在前面,其他都一样
// 运行时间:13ms,超过24.78%用Java提交的代码
// 占用内存:11092KB,超过51.91%用Java提交的代码
public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param array int整型一维数组 * @return int整型一维数组*/public int[] FindNumsAppearOnce (int[] array) {int x = 0, y = 0, t = 0, one = 1;for (int num : array)t ^= num;while ((t & one) == 0)one <<= 1;for (int num : array) {if ((num & one) != 0)x ^= num;else y ^= num;}int[] res = new int[] {y, x};if (res[0] > res[1]) {int temp = res[0];res[0] = res[1];res[1] = temp;}return res;}
}

【剑指offer】56.2 数组中数字出现的次数

题目描述

// 56.2 数组中数字出现的次数// 力扣
// 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现
// 了三次。请找出那个只出现一次的数字。

题解

// 力扣
// 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次
// 。请找出那个只出现一次的数字。// 创建计数数组count,记录所有nums元素在二进制表示下每一位出现1的次数
// 比如count[0]就是nums所有元素在最低位出现1的次数。
//
// 一个双for循环,第一个for遍历nums的元素num,第二个for遍历计数数组索引j,
// 也就是从低到高遍历num的每一位二进制位。
// 如果num & 1为1,则当前逻辑运算结果1累加到count[j]中,然后
// num整体右移1一格num = num >>> 1,j也右移一位,即开始遍历下一位
// 二进制位,再和1做逻辑与,如此循环。第二层for循环走一轮能找出一个num的
// 所有位出现1的次数,并记录到count的相应位置中。
// 两个for循环之后,能把所有nums的元素的所有位出现的1累计到count的相应位置// 第二个for循环,倒序遍历count,索引为i,索引的计数元素为count[i],
// 由于需要找出的目标元素target出现了1次,其他元素都出现了3次,
// 所以其他元素对应的二进制位出现1的次数一定是3的倍数,如果该位出现
// 1的次数不是3的倍数,无法整除3,说明该位一定出现了target的1。我们将
// 余数或逻辑运算赋给变量res中,然后res左移,i左移,如此循环,可以把
// count中所有位中属于target的二进制1赋给res,使得res等于target。
// 最后返回res即可。// 执行用时:5 ms, 在所有 Java 提交中击败了85.82%的用户
// 内存消耗:39.3 MB, 在所有 Java 提交中击败了73.60%的用户
class Solution {public int singleNumber(int[] nums) {int[] count = new int[32];for (int num : nums) {for (int j = 0; j < count.length; j++) {count[j] += num & 1;num >>>= 1;}}int res = 0, three = 3;for (int i = count.length - 1; i >= 0; i--) {res <<= 1;res |= count[i] % three;}return res;}
}// 啰嗦一点的写法,比较好理解
// 执行用时:14 ms, 在所有 Java 提交中击败了48.98%的用户
// 内存消耗:39.4 MB, 在所有 Java 提交中击败了52.13%的用户
class Solution {public int singleNumber(int[] nums) {int[] count = new int[32];int res = 0;for (int num: nums) {int one = 1;for (int j = 0; j < 32; j++) {if ((num & one) == one) count[j]++;one = one << 1; }}for (int i = count.length - 1; i >= 0; i--) {res = res << 1;int temp = count[i] % 3;if (temp == 0) continue;else res = res | count[i] % 3;}return res;}
}

特殊解——双指针

【剑指offer】57. 和为s的两个数字

题目描述


// 57. 和为s的两个数字// 力扣
// 输入一个递增排序的数组和一个数字s,在数组中查找两个数,
// 使得它们的和正好是s。如果有多对数字的和等于s,则输出任意
// 一对即可。// 牛客
// 输入一个递增排序的数组和一个数字S,在数组中查找两个数,
// 使得他们的和正好是S,如果有多对数字的和等于S,输出两个
// 数的乘积最小的。

题解


// 力扣
// 前后双指针,求的和sum与target比较,
// sum大了,右指针左移,减小sum,
// sum小了,左指针右移,增大sum。
// 执行用时:2 ms, 在所有 Java 提交中击败了94.94%的用户
// 内存消耗:55.3 MB, 在所有 Java 提交中击败了72.04%的用户class Solution {public int[] twoSum(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left < right) {if (nums[left] + nums[right] > target) {right--;}else if (nums[left] + nums[right] < target) {left++;}else { return new int[]{nums[left], nums[right]};}}return new int[0];}
}// 牛客
// 运行时间:10ms,超过89.79%用Java提交的代码
// 占用内存:9512KB,超过10.62%用Java提交的代码
import java.util.ArrayList;
public class Solution {public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {ArrayList<Integer> res = new ArrayList<Integer>(2);int left = 0, right = array.length - 1;while (left < right) {if (array[left] + array[right] > sum)right--;else if (array[left] + array[right] < sum)left++;else {res.add(array[left]);res.add(array[right]);return res;}}return res;}
}

【剑指offer】57.2 和为s的两个数字

题目描述

// 57.2 和为s的两个数字// 力扣
// 输入一个正整数 target ,输出所有和为 target 的连续正整数序列
// (至少含有两个数)。// 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。// 牛客
// 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上
// 就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续
// 的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正
// 数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快
// 的找出所有和为S的连续正数序列? Good Luck!

题解


// 牛客
// 思路简单,但是能做出来还是不容易的。
// 从前面初始化两个指针left和right,由于至少包含2个数,且必须是正整数
// 所以left初始化为1(left至少为1),right比left大,right至少为2,
// 累计的数组和至少为3,所以初始化累计和sum为3,
// 开始比较sum与target大小,如果sum比target小,需要窗口扩大,right右移
// 右移之后sum累加right,如果sum比target大,需要窗口缩小,left右移,
// sum累减left,之后left再右移(注意是先指针移动还是先操作sum加减)
// 直到sum等于target,将left到right之间所有元素存入新指定的ArrayList中
// 再存入res中。
// 运行时间:15ms,超过87.63%用Java提交的代码
// 占用内存:9984KB超过2.76%用Java提交的代码
import java.util.ArrayList;
public class Solution {public ArrayList<ArrayList<Integer>> FindContinuousSequence(int target) {ArrayList<ArrayList<Integer>> res = new ArrayList<>();if (target < 2)return res;int left = 1, right = 2;int sum = 3;while (left < right) {if (sum < target) {right++;sum += right;}else if (sum > target) {sum -= left;left++;}else {ArrayList<Integer> list = new ArrayList<>();for (int i = left; i <= right; i++) {list.add(i);}res.add(list);sum -= left;left++;}}return res;}
}// 力扣
// 执行用时:2 ms, 在所有 Java 提交中击败了97.62%的用户
// 内存消耗:36.8 MB, 在所有 Java 提交中击败了17.75%的用户
import java.util.ArrayList;
class Solution {public int[][] findContinuousSequence(int target) {ArrayList<int[]> res = new ArrayList<>();int left = 1, right = 2;int sum = 3;while (left < right) {if (sum < target) {right++;sum += right;}else if (sum > target) {sum -= left;left++;}else {int[] list = new int[right - left + 1];int j = 0;for (int i = left; i <= right; i++) {list[j++] = i;}res.add(list);sum -= left;left++;}}int[][] result = res.toArray(new int[res.size()][]);return result;}
}

特殊解——贪心算法

【剑指offer】63. 股票的最大利润

题目描述

// 63. 股票的最大利润// 力扣
// 假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该
// 股票一次可能获得的最大利润是多少?

题解

// 贪心算法 // 力扣
// 初始化min
// 执行用时:2 ms, 在所有 Java 提交中击败了66.58%的用户
// 内存消耗:38.2 MB, 在所有 Java 提交中击败了71.67%的用户
class Solution {public int maxProfit(int[] prices) {if (prices.length == null || prices.length == 0)return 0;int minPrice = prices[0];int profit = Integer.MIN_VALUE;for (int p : prices) {minPrice = Math.min(p, minPrice);profit = Math.max(profit, (p - minPrice));}return profit;}
}

【剑指offer】数据结构——数组相关推荐

  1. 【LeetCode】剑指 Offer 51. 数组中的逆序对

    [LeetCode]剑指 Offer 51. 数组中的逆序对 文章目录 [LeetCode]剑指 Offer 51. 数组中的逆序对 package offer;public class Soluti ...

  2. 【LeetCode】剑指 Offer 56. 数组中数字出现的次数

    [LeetCode]剑指 Offer 56. 数组中数字出现的次数 文章目录 [LeetCode]剑指 Offer 56. 数组中数字出现的次数 package offer;import java.u ...

  3. 【LeetCode】剑指 Offer 39. 数组中出现次数超过一半的数字

    [LeetCode]剑指 Offer 39. 数组中出现次数超过一半的数字 文章目录 [LeetCode]剑指 Offer 39. 数组中出现次数超过一半的数字 一.摩尔投票法 一.摩尔投票法 核心理 ...

  4. java中数组的下标比较_【Java】 剑指offer(53-3) 数组中数值和下标相等的元素

    本文参考自<剑指offer>一书,代码采用Java语言. 题目 假设一个单调递增的数组里的每个元素都是整数并且是唯一的.请编程实现一个函数找出数组中任意一个数值等于其下标的元素.例如,在数 ...

  5. 剑指 Offer 39. 数组中出现次数超过一半的数字

    剑指 Offer 39. 数组中出现次数超过一半的数字https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban ...

  6. 剑指offer:数组中的逆序对

    题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对1000000007取模的结果输出. 即输出P%1000 ...

  7. 剑指offer 旋转数组的最小数字

    把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组 ...

  8. 剑指offer之数组中的逆序对

    1 问题 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对1000000007取模的结果输出. 即输出P%1000 ...

  9. 【剑指offer】数组中的逆序对

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/mmc_maodun/article/details/27520535 转载请注明出处:http:// ...

  10. 剑指offer:数组中只出现一次的数字

    题目描述 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字. 解题思路 用容器存次数. class Solution { public:void FindNu ...

最新文章

  1. CV之后,纯MLP架构又来搞NLP了,性能媲美预训练大模型
  2. Halcon算子知识:optical_flow_mg
  3. java程序结构_Java 程序结构说明(学习 Java 编程语言 004)
  4. C语言必知的几个概念
  5. 牛客contest897 D-Bamboo Rat(二分+黑白染色+最小割)
  6. 视频监控技术 迎来网络“多媒体”时代
  7. when is Lessifier triggered to be loaded
  8. Codeforces Round #579 (Div. 3) F1. Complete the Projects (easy version) 排序 + 贪心
  9. 系统在此应用程序堆栈溢出_从部署我的第一个完整堆栈Web应用程序中学到的经验教训...
  10. java通过桥访问excel_通过jdbc-odbc桥来访问excel文件
  11. 美国国土安全部发布针对高风险铁路基础设施的强制性安全要求
  12. 消费者生产者代码之---一步一步带你写
  13. git-合并分支指定commits
  14. JUC 基础内容概述
  15. 笑话一则(小狗的故事)
  16. android卡机字符串,Android调用replace方法正则表达式卡死
  17. 佳能数码相机内存卡数据恢复
  18. notepad下载(官网进不去)
  19. 国家开放大学《国际经济法》第二章 国际货物买卖法 边学边练
  20. virtualBox安装拓展包extension pack失败 --解决办法

热门文章

  1. Salesforce被除名、阿里云再入围,2021年ABI魔力象限的变与不变
  2. 吐槽一下苹果的产品缺陷
  3. Ubuntu18 配置静态IP地址
  4. matlab nlinfit实例,matlab nlinfit函数的应用例子
  5. CS75显示服务器,长安cs75仪表盘指示灯,长安cs75仪表盘故障灯
  6. 图像的形态学开操作(开运算)和闭操作(闭运算)的概念和作用,并用OpenCV的函数morphologyEx()实现对图像的开闭操作
  7. 简单使用java程序进行数值拆分~
  8. 记录edge浏览器无法登录微软账户的修复过程
  9. 安卓手机数据信号好的服务器,手机信号你真的看懂了?帮您找到最强信号
  10. Getshell思路总结