Java中有关数组的知识:

2021.01.04 找出数组中重复的数字

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

  • 思路:个人认为有两种解法。
    1. 法一:先对数组进行排序,可选时间复杂度较低的排序方式,例如:快速排序、归并排序等。然后从头开始遍历数组,若出现下一个数与上一个数相等,则表示出现了重复的情况。返回第一个遇到的重复的数。
    2. 法二:不用对数组进行排序,准备一个长度与目标数组长度相等的标记数组,然后从头遍历目标数组,每遇到一个数,就在标记数组对应的地方进行加1(标记数组初始化后每个元素的值为0,例:nums[2]=1,则进行++tag[1]的操作);最后,遍历一遍标记数组,遇到的第一个元素值大于1的即为重复出现的数字。
    3. 法三(力扣给的官方解法):利用哈希集合的特性(集合中无重复值),所以在往集合中添加值的时候,若出现重复值,则会返回false表示添加失败。
    4. 法四(大佬题解):原地置换。一个萝卜一个坑的思路,数组下标为 i 的数组元素对应的值应为 i ;从头开始遍历,若第i个数字为m,先判断第i位数字是否与第m位数字相等,若相等,则表示有重复的值,否则将第m个数字与第i个数字对换,即第m位上的数字为m。交换之后若第i位上的数字仍不为i,则继续交换,若第i位上的数字为i,则对下一位数字进行相同的操作。
    5. 法五(剑指里边的另一种解法):把从1~n的数字从中间的数字m分为两部分,前面一半为1~m,后面一半为m+1~n。如果1~m的数字的数目超过m,那么这一半的区间里一定包含重复的数字;否则,另一半m+1~n的区间里一定包含重复的数字。我们可以继续把包含重复数字的区间一分为二,直到找到一个重复的数字。这个过程和二分查找算法很类似,只是多了一步统计区间里数字的数目。时间复杂度O(nlogn)(神奇)(有缺陷)
  • 自己的菜鸡代码: ```java //法二的代码 class Solution { public int findRepeatNumber(int[] nums) {
    1. int tag[] = new int[nums.length];
    2. for(int i = 0; i < nums.length; i++)
    3. ++tag[nums[i]];
    4. for(int i = 0; i < nums.length; i++)
    5. if(tag[i] > 1)
    6. return i;
    7. //若是没有重复的值则返回-1
    8. return -1;
    } }

//法一的代码========================================================= class Solution { public int findRepeatNumber(int[] nums) { QuickSort(nums,0,nums.length-1); int i; for(i = 0;i < nums.length-1; i++){ if(nums[i] == nums[i+1]) break; } return nums[i]; }

  1. public void QuickSort(int[] nums,int head,int rear){
  2. //进行快速排序,获得已排好序的数组
  3. if(head < rear){
  4. int part = partition(nums,head,rear);
  5. QuickSort(nums,head,part-1);
  6. QuickSort(nums,part+1,rear);
  7. }
  8. }
  9. public int partition(int[] nums,int head,int rear){
  10. int tag = nums[head];
  11. while(head < rear){
  12. while(nums[rear]>=tag && head<rear) --rear;
  13. nums[head] = nums[rear];
  14. while(nums[head]<=tag && head<rear) ++head;
  15. nums[rear] = nums[head];
  16. }
  17. nums[head] = tag;
  18. return head;
  19. }

}

//法三的代码======================================================= class Solution { public int findRepeatNumber(int[] nums) { Set set = new HashSet(); int tag = -1; for(int num : nums){ if(!set.add(num)){ tag = num; break; } }

  1. return tag;
  2. }

}

//法四的代码======================================================= class Solution { public int findRepeatNumber(int[] nums) { int temp; for(int i = 0; i<nums.length; i++){ while(nums[i] != i){ if(nums[i] == nums[nums[i]]){ return nums[i]; }

  1. temp = nums[i];
  2. nums[i] = nums[temp];
  3. nums[temp] = temp;
  4. }
  5. }
  6. return -1;
  7. }

}

  1. 法二的运行结果(左):法二的时间复杂度为O(n),但是空间复杂度也为O(n),是采取空间换时间的方式。<br />法一的运行结果(右):啊,可能是哪里出错了<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1609746563646-906f34de-1f27-421b-a756-a71e8b6b066e.png#align=left&display=inline&height=354&margin=%5Bobject%20Object%5D&name=image.png&originHeight=472&originWidth=658&size=29093&status=done&style=none&width=493)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1609749634664-4439fce5-c1db-4aa8-bddc-a55ec73315ac.png#align=left&display=inline&height=272&margin=%5Bobject%20Object%5D&name=image.png&originHeight=328&originWidth=720&size=19262&status=done&style=none&width=598)<br />法三的运行结果(左):遍历数组一遍的时间复杂度为O(n),添加元素的时间复杂度为O(1),故总的时间复杂度为O(n),但是用了一个额外的哈希集合,故空间复杂度为O(n)。<br />法四的运行结果(右):<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1609750914174-3cfe1096-c42d-4f6c-9a06-7f1686a054e9.png#align=left&display=inline&height=276&margin=%5Bobject%20Object%5D&name=image.png&originHeight=448&originWidth=662&size=28431&status=done&style=none&width=408)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1609753001444-c9a61e0b-b2db-441a-aea4-852a8fe0236d.png#align=left&display=inline&height=275&margin=%5Bobject%20Object%5D&name=image.png&originHeight=463&originWidth=705&size=29954&status=done&style=none&width=418)
  2. <a name="zscBO"></a>
  3. ### 2021.01.05 二维数组中的查找
  4. 今日题目:在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
  5. - 思路:个人感觉这个应该有多种解决方法
  6. 1. 法一:暴力法解决,逐行遍历查找,时间复杂度为O(n*m)。若nm是很大的数字则时间复杂度会很高,而且没有利用题干中给出的递增条件
  7. 1. 法二:因为每一行都按照从左到右递增的顺序排序,所以可以对该二维数组逐行进行二分查找。一行的二分查找时间为O(logm),则整个的时间复杂度为O(nlogm)。
  8. 1. 法三(看评论区):线性查找,利用题干中给出的条件,即每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序,所以从右上角开始,若大于目标值,则往下一行,若小于目标值,则往前一列。评论区有个大佬给了张图,我觉得非常清楚明白。如下图,时间复杂度为O(n+m)。![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1609823983757-56bc269a-ef52-44fd-b927-cf52fe7d8410.png#align=left&display=inline&height=392&margin=%5Bobject%20Object%5D&name=image.png&originHeight=434&originWidth=552&size=50068&status=done&style=none&width=499)。
  9. - 自己的菜鸡代码:
  10. ```java
  11. //法一的代码
  12. class Solution {
  13. public boolean findNumberIn2DArray(int[][] matrix, int target) {
  14. if(matrix.length < 1) return false;
  15. int row = matrix.length;
  16. int colunm = matrix[0].length;
  17. int i,j;
  18. for(i = 0; i < row; i++){
  19. for(j = 0; j < colunm; j++){
  20. if(matrix[i][j] == target){
  21. return true;
  22. }
  23. }
  24. }
  25. return false;
  26. }
  27. }
  28. // 法二的代码
  29. class Solution {
  30. public boolean findNumberIn2DArray(int[][] matrix, int target) {
  31. if(matrix.length < 1) return false;
  32. int row = matrix.length;
  33. int colunm = matrix[0].length;
  34. int head,rear,mid;
  35. for(int i = 0; i < row; i++){
  36. head = 0;
  37. rear = colunm - 1;
  38. while(head <= rear){
  39. mid = (head + rear) / 2;
  40. if(matrix[i][mid] == target) return true;
  41. else if(matrix[i][mid] < target) head = mid + 1;
  42. else rear = mid - 1;
  43. }
  44. }
  45. return false;
  46. }
  47. }
  48. // 法三的代码
  49. class Solution {
  50. public boolean findNumberIn2DArray(int[][] matrix, int target) {
  51. //线性查找的方法
  52. if(matrix.length < 1) return false;
  53. int row = matrix.length;
  54. int colunm = matrix[0].length;
  55. int i = 0, j = colunm - 1;
  56. while(i >= 0 && i < row && j >= 0 && j < colunm){
  57. if(matrix[i][j] == target) return true;
  58. else if(matrix[i][j] < target) ++i;
  59. else --j;
  60. }
  61. return false;
  62. }
  63. }

法一的运行结果(左):
法三的运行结果(右):
可能是因为测试用例的范围为0 <= n <= 1000,0 <= m <= 1000
故以下两个结果不明显,若n与m的值特别大,则法三的时间消耗会明显更少一些。
image.pngimage.png
法二的运行结果:
image.png

2021.01.19 在排序数组中查找数字

今日题目:统计一个数字在排序数组中出现的次数。
image.png
提示:0 <= 数组长度 <= 50000

  • 思路:
    • 法一:暴力美学。整个数组遍历一遍,但是这样就没有用到“排序数组”这一特点。时间复杂度为O(n),空间复杂度为O(1)。
    • 法二:利用“排序数组”这一特点,这题是二分查找的变形。先查找右边界,若数组中不包含target则提前返回0;若数组中包含target,则在[0, j]中一定包含左边界,故再用一次二分查找。时间复杂度为O(logn),空间复杂度为O(1)。
  • 自己的菜鸡代码: ```java // 法一 class Solution { public int search(int[] nums, int target) {
    1. int count = 0;
    2. for(int i = 0; i < nums.length; i++){
    3. if(nums[i] == target) ++count;
    4. }
    5. return count;
    } }

// 把法一稍稍改了一下,就快了许多 class Solution { public int search(int[] nums, int target) { int count = 0; for(int i = 0; i < nums.length; i++){ if(nums[i] < target) continue; else if(nums[i] == target) ++count; else break; } return count; } }

// 法二 class Solution { public int search(int[] nums, int target) { if(nums.length == 0) return 0; int i = 0, j = nums.length - 1; // 查找y右边界 int mid; while(i <= j){ mid = (i + j) / 2; if(nums[mid] <= target) i = mid + 1; if(nums[mid] > target) j = mid - 1; } // 判断是否包含target,若不包含则提前结束,若包含则继续查找左边界 if(j < 0 || nums[j] != target) return 0; // 查找左边界 int right = j; i = 0; while(i <= j){ mid = (i + j) / 2; if(nums[mid] < target) i = mid + 1; if(nums[mid] >= target) j = mid - 1; }

  1. return right - i + 1;
  2. }

}

  1. - 运行结果:
  2. 法一<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611020733046-a817b863-a091-46b1-8ed4-71adf6a59fb0.png#align=left&display=inline&height=277&margin=%5Bobject%20Object%5D&name=image.png&originHeight=455&originWidth=626&size=28259&status=done&style=none&width=381)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611020934136-ff5a6633-daa2-4888-9881-8a4e991bae26.png#align=left&display=inline&height=272&margin=%5Bobject%20Object%5D&name=image.png&originHeight=449&originWidth=665&size=31984&status=done&style=none&width=403)<br />法二<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611023949292-e8df0e5e-89c8-4dd8-a4cb-c4bc8680d40c.png#align=left&display=inline&height=262&margin=%5Bobject%20Object%5D&name=image.png&originHeight=449&originWidth=664&size=29006&status=done&style=none&width=387)
  3. <a name="pZYRw"></a>
  4. ### 2021.01.19 和为 s 的两个数字
  5. 今日题目:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611025092111-0788ec76-f1e9-4b64-b0a0-b7d3bd13cfe6.png#align=left&display=inline&height=263&margin=%5Bobject%20Object%5D&name=image.png&originHeight=263&originWidth=436&size=10870&status=done&style=none&width=436)<br />**限制:**
  6. - 1 <= nums.length <= 10^5
  7. - 1 <= nums[i] <= 10^6
  8. - 思路:
  9. - 法一:暴力美学。逐个比对,没什么技术含量,时间复杂度为O(n),空间复杂度为O(1),giao超出时间限制 (理智丧失.jpg)这个不配叫算法😊。想到一个时间复杂度应该会低一点的,第一个数也是逐个尝试,时间复杂度为O(n),第二个数在第一个数后面的区间内采用二分查找法O(logn)。
  10. - 法二:用一个哈希表,通过遍历数组找到数字组合,时间复杂度和空间复杂度都为O(n)。
  11. - 法三:双指针碰撞(看评论区的大佬解法)。head从下标0开始,tail nums.length-1 开始,在head < tail 的循环条件下,若是nums[head] + nums[tail] > target ,则tail减一;若是nums[head] + nums[tail] < target ,则head加一若是相等,则返回nums[head] nums[tail] 。若不存在则返回空数组。这样子时间复杂度为O(n),空间复杂度为O(1)。
  12. - 自己的菜鸡代码:
  13. ```java
  14. // 法一 (超出时间限制55555不可取)
  15. class Solution {
  16. public int[] twoSum(int[] nums, int target) {
  17. // 暴力美学
  18. int temp;
  19. int[] result = new int[2];
  20. for(int i = 0; i < nums.length && nums[i] < target; i++){
  21. temp = target - nums[i];
  22. for(int j = i + 1; j < nums.length && nums[j] < target; j++){
  23. if(nums[j] == temp){
  24. result[0] = nums[i];
  25. result[1] = nums[j];
  26. return result;
  27. }
  28. if(nums[j] + nums[i] > target) break;
  29. }
  30. }
  31. return null;
  32. }
  33. }
  34. // 法一改进版
  35. class Solution {
  36. public int[] twoSum(int[] nums, int target) {
  37. int temp;
  38. int[] result = new int[2];
  39. for(int i = 0; i < nums.length; i++){
  40. temp = target - nums[i];
  41. int left = i + 1, right = nums.length - 1, mid;
  42. while(left <= right){
  43. mid = (left + right) / 2;
  44. if(nums[mid] < temp) left = mid + 1;
  45. else if(nums[mid] > temp) right = mid - 1;
  46. else{
  47. result[0] = nums[i];
  48. result[1] = nums[mid];
  49. return result;
  50. }
  51. }
  52. }
  53. return null;
  54. }
  55. }
  56. // 法二 利用哈希表
  57. class Solution {
  58. public int[] twoSum(int[] nums, int target) {
  59. // 利用哈希表降低时间复杂度
  60. HashSet<Integer> set = new HashSet<>();
  61. int i;
  62. for(int num : nums){
  63. set.add(num);
  64. }
  65. for(int num : nums){
  66. int temp = target - num;
  67. if(set.contains(temp)){
  68. return new int[]{num, temp};
  69. }
  70. }
  71. return new int[]{};
  72. }
  73. }
  74. // 法三
  75. class Solution {
  76. public int[] twoSum(int[] nums, int target) {
  77. // 双指针碰撞
  78. int head = 0, tail = nums.length - 1;
  79. while(head < tail){
  80. int temp = nums[head] + nums[tail];
  81. if(temp > target) --tail;
  82. else if(temp < target) ++head;
  83. else return new int[]{nums[head], nums[tail]};
  84. }
  85. return new int[]{};
  86. }
  87. }
  • 运行结果:

法一改进前(左)和改进后(右)
image.pngimage.png
法二(左)、法三(右)
image.pngimage.png

2021.01.19 旋转数组的最小数字

今日题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
image.pngimage.png

  • 思路:
    • 法一:遍历数组,将当前值域后面一个值相比较,若是前一个数大于后一个数,则后一个数即旋转数组中的最小数字。若是遍历完整个数组都没有发现这样子的一对数,则表示该数组是从小到大排列,因此最小值即为第一个数组元素。时间复杂度为O(n),空间复杂度为O(1)。
    • 法二:采用二分查找法。时间复杂度为O(logn),空间复杂度为O(1)。看大佬的思路有点不好理解。寻找旋转数组的最小元素即为寻找右排序数组的首个元素nums[x],称 x 为旋转点。
      • 初始化:i 指向数组首部,j 指向数组尾部。
      • 循环二分:设mid =(i + j)/ 2(可以分成三种情况)
        1. 当nums[mid] < nums[j],旋转点在[i , m]中。
        2. 当nums[mid] > nums[j],旋转点在[m + 1 , j]中。
        3. 当nums[mid] == nums[j],旋转点的位置不好确定,此时可将 j 进行减一处理以缩小判断范围。
      • 为什么要进行 —j的操作呢?

因为在出现nums[mid] == nums[j]时,有两种情况。举例[1,0,1,1,1,1,1,1]和[1,1,1,1,1,0,1,1]

  1. - x < j时:易得执行j = j - 1后旋转点 x 仍在区间[i , j]内。
  2. - x = j时:执行j = j - 1后越过(丢失)了旋转点 x ,但最终返回的元素值nums[i]仍等于旋转点值nums[x]。

======以下这一段不好理解======

  1. - 由于x = j,因此nums[x] = nums[j] = nums[mid] <= nums[i];
  2. - 又由于i <= m < j,恒成立,因此有m < x,即此时 m 一定在左排序数组中,因此nums[m] >= nums[i];
  3. - ![](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611041557183-531fa877-9976-48dc-aad2-9fbd266a174a.png#align=left&display=inline&height=192&margin=%5Bobject%20Object%5D&originHeight=730&originWidth=1242&size=0&status=done&style=none&width=327)![](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611041576839-60fbb72d-e5ad-4caa-b405-61992e270e2c.png#align=left&display=inline&height=194&margin=%5Bobject%20Object%5D&originHeight=730&originWidth=1242&size=0&status=done&style=none&width=330)
  4. - 可推出 nums[i] = nums[m],且区间 [i, m] 内所有元素值相等,即有:nums[i]=nums[i+1]=⋯=nums[m]=nums[x]。此时,执行 j = j - 1 后虽然丢失了旋转点 x ,但之后区间 [i, j] 只包含左排序数组,二分下去返回的一定是本轮的 nums[i] ,而其与 nums[x] 相等。
  5. - 为什么本题二分法不用 nums[m] nums[i] 作比较?
  6. - 二分目的是判断 m 在哪个排序数组中,从而缩小区间。而在 nums[m] > nums[i]情况下,无法判断 m 在哪个排序数组中。本质上是由于 j 初始值肯定在右排序数组中; i 初始值无法确定在哪个排序数组中。举例如下:
  7. - 对于以下两示例,当 i = 0, j = 4, m = 2 时,有 nums[m] > nums[i] ,而结果不同。
  8. - [1, 2, 3, 4 ,5] 旋转点 x = 0 m 在右排序数组(此示例只有右排序数组);
  9. - [3, 4, 5, 1 ,2] 旋转点 x = 3 m 在左排序数组。
  • 自己的菜鸡代码: ```java // 法一 class Solution { public int minArray(int[] numbers) {
    1. int i;
    2. for(i = 0; i < numbers.length - 1; i++){
    3. if(numbers[i] > numbers[i+1]) break;
    4. }
    5. if(i == numbers.length - 1) return numbers[0];
    6. return numbers[i+1];
    } }

// 法二 代码很短但是很神奇 class Solution { public int minArray(int[] numbers) { int i = 0, j = numbers.length - 1, mid; while(i < j){ mid = (i + j) / 2; if(numbers[mid] < numbers[j]) j = mid; else if(numbers[mid] > numbers[j]) i = mid + 1; else —j; } return numbers[i]; } }

  1. - 运行代码:
  2. 法一(左,虽然看上去好像还行的样子,但是一旦数量级上来了,暴力法是最差的,所以还是老老实实学习怎么写算法8),法二(右)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611035171663-f6cff632-df9a-42b9-ad5a-503c435cdbc8.png#align=left&display=inline&height=276&margin=%5Bobject%20Object%5D&name=image.png&originHeight=447&originWidth=624&size=28555&status=done&style=none&width=385)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611040820522-9b4fc150-cdf7-49c8-a21c-28c46f558ec7.png#align=left&display=inline&height=276&margin=%5Bobject%20Object%5D&name=image.png&originHeight=443&originWidth=663&size=28835&status=done&style=none&width=413)
  3. <a name="hZTfE"></a>
  4. ### 2021.01.22 打印从1到最大的n位数
  5. 今日题目:输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 123 一直到最大的 3 位数 999。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611300870154-86e47a7d-2c91-4df3-89c0-a155c888b2e9.png#align=left&display=inline&height=123&margin=%5Bobject%20Object%5D&name=image.png&originHeight=128&originWidth=272&size=3502&status=done&style=none&width=262)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611300883125-c2f4b603-2679-431b-ad41-31d3cfd16d95.png#align=left&display=inline&height=116&margin=%5Bobject%20Object%5D&name=image.png&originHeight=116&originWidth=314&size=4211&status=done&style=none&width=314)
  6. - 思路:因为是十进制数,所以求出打印上限为(10^n),但是java中的指数运算是Math.pow(a, b);返回的值为double类型,需要强制转为int类型即可,这题返回的是int[]数组,说明不会用int类型不会溢出,否则最好用long类型。
  7. > 本题在面试中通常是要考虑大数问题,在大数问题中必须用字符串来模拟整型数据的加法,若直接用整型数据的话,会产生溢出。
  8. - 自己的菜鸡代码:
  9. ```java
  10. class Solution {
  11. public int[] printNumbers(int n) {
  12. int num = (int)Math.pow(10,n);
  13. int[] res = new int[num - 1];
  14. for(int i = 1; i < num; i++){
  15. res[i - 1] = i;
  16. }
  17. return res;
  18. }
  19. }
  • 运行结果:

image.png

2021.01.22 数组中出现次数超过一半的数字

今日题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
image.png
限制:1 <= 数组长度 <= 50000

  • 思路:
    • 法一:利用哈希表来记录所有值的出现次数,若有值的出现次数大于数组长度的一半,就直接返回该值。时间复杂度为O(n),空间复杂度为O(n),本来想用时间换空间的,结果效果好像很一般。时间复杂度为O(n),空间复杂度为O(n)。
    • 法二:可以将数组进行快速排序,然后中位数就是出现次数超过一半的数字。时间复杂度为O(nlogn),空间复杂度为O(1)。
    • 法三:(在评论区学到的一个新玩意)摩尔投票法:若记众数的票数为-1,则一定右所有数字的票数和>0;若数组的前a个数字的票数和=0,则数组剩余(n-a)个数字的票数和一定仍>0,记(n - a)个数字的中枢仍为x。当发生 票数和 = 0=0 时,剩余数组的众数一定不变。
      • 算法流程:
        • 初始化:票数统计vote = 0,众数设为x。
        • 循环:遍历数组nums中的每个数字num。

当票数vote = 0,则假设当前数字num是众数。
当num = x,票数vote+1;当num!= x,票数vote-1.

  1. - 返回值:返回x即可。
  2. - 若数组中不一定包含众数,则最后再遍历一遍数组,统计x的数量,若大于数组长度的一半,则返回x,否则未找到众数。
  3. - 时间复杂度为O(n),空间复杂度为O(1)。
  • 自己的菜鸡代码: ```java // 法一 class Solution { //private int[] data; public int majorityElement(int[] nums) {
    1. // 用空间换时间
    2. HashMap<Integer,Integer> map = new HashMap<>();
    3. int num = 0;
    4. for(int i = 0; i < nums.length; i++){
    5. if(map.get(nums[i]) == null){
    6. num = 1;
    7. }else {
    8. num = map.get(nums[i]);
    9. ++num;
    10. }
    11. if(num > nums.length / 2) return nums[i];
    12. map.put(nums[i], num);
    13. }// for
    14. return num;
    } }

// 法二 class Solution { //private int[] data; public int majorityElement(int[] nums) { nums = QuickSort(nums, 0, nums.length - 1); return nums[nums.length / 2]; }

  1. int[] QuickSort(int[] data, int head, int rear){
  2. if(head > rear) return data;
  3. int left = head, right = rear;
  4. int flag = data[head];
  5. while(head < rear){
  6. while(head < rear && data[rear] >= flag) --rear;
  7. data[head] = data[rear];
  8. while(head < rear && data[head] <= flag) ++head;
  9. data[rear] = data[head];
  10. }
  11. data[head] = flag;
  12. QuickSort(data, left, head - 1);
  13. QuickSort(data, head + 1, right);
  14. return data;
  15. }

}

// 法三 (太秀了) class Solution { //private int[] data; public int majorityElement(int[] nums) { // 摩尔投票法 int x = 0, vote = 0; for(int num: nums){ if(vote == 0) x = num; vote += num==x ? 1:-1; } return x; } }

  1. - 运行结果:
  2. 法一(左)、法二(右)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611308013530-48314232-4d85-4818-9037-a5476f57eed4.png#align=left&display=inline&height=307&margin=%5Bobject%20Object%5D&name=image.png&originHeight=462&originWidth=635&size=29147&status=done&style=none&width=422)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611539285039-6c8af2f7-4e80-42d6-8c48-4d40d83c2457.png#align=left&display=inline&height=307&margin=%5Bobject%20Object%5D&name=image.png&originHeight=482&originWidth=642&size=30467&status=done&style=none&width=409)<br />法三:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611541025558-37a2724c-06ca-45a3-a11a-5bb2751362f8.png#align=left&display=inline&height=328&margin=%5Bobject%20Object%5D&name=image.png&originHeight=478&originWidth=604&size=32360&status=done&style=none&width=414)
  3. <a name="E6we9"></a>
  4. ### 2021.01.25 调整数组顺序使奇数位于偶数前面
  5. 今日题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611541929829-876b1294-852d-4fef-af03-5961ea8b06c0.png#align=left&display=inline&height=152&margin=%5Bobject%20Object%5D&name=image.png&originHeight=152&originWidth=325&size=5301&status=done&style=none&width=325)<br />提示:
  6. 1. 1 <= nums.length <= 50000
  7. 1. 1 <= nums[i] <= 10000
  8. - 思路:采用双指针的方式,设头指针为head,尾指针为rear,在head<rear的情况下,head从前往后移动,遇到偶数停下,rear从后往前移动,遇到奇数停下,交换num[head]与num[rear]。
  9. - 自己的菜鸡代码:
  10. ```java
  11. class Solution {
  12. public int[] exchange(int[] nums) {
  13. int head = 0, rear = nums.length - 1;
  14. while(head < rear){
  15. while(head < rear && nums[head] % 2 == 1) ++head;
  16. while(head < rear && nums[rear] % 2 == 0) --rear;
  17. int temp = nums[head];
  18. nums[head] = nums[rear];
  19. nums[rear] = temp;
  20. }
  21. return nums;
  22. }
  23. }
  • 运行结果:

image.png

2021.01.25 顺时针打印矩阵(未写)

今日题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
image.png
限制:

  • 0 <= matrix.length <= 100
  • 0 <= matrix[i].length <= 100

  • 思路:

  • 自己的菜鸡代码:
  • 运行结果:

2021.01.25 最小的k个数

今日题目:输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
image.pngimage.png
限制:

  • 0 <= k <= arr.length <= 10000
  • 0 <= arr[i] <= 10000

  • 思路:

法一:可以先将数组进行快速排序,然后取前k个不重复的数。时间复杂度为O(nlogn),空间复杂度为O(1)。

看评论区发现不需要对整个数组进行快速排序,当锚点位置在第k个,则前面的数都是小于锚点值的,故直接返回前k个数就好。

法二:可以利用一个辅助数组tag,因为0 <= arr[i] <= 10000,所以可以遍历arr数组,经历每个数组时就进行++tag[arr[i]]的操作,最后遍历tag数组,前k个tag[i] > 0 的元素即为所求结果。时间复杂度为O(n),空间复杂度为O(n)。
法三:可以用堆(俺暂时不会)。

  • 自己的菜鸡代码: ```java // 法一 class Solution { //private int[] nums; public int[] getLeastNumbers(int[] arr, int k) {

    1. // 先排序再取值
    2. if(arr.length == 0 || k == 0) return new int[]{};
    3. int[] res = new int[k];
    4. arr = QuickSort(arr, 0, arr.length - 1);
    5. res[0] = arr[0];
    6. int count = 1;
    7. for(int i = 1; i < arr.length; i++){
    8. if(count == k)return res;
    9. res[count++] = arr[i];
    10. }
    11. return res;

    }

    int[] QuickSort(int[] nums, int head, int rear){

    1. if(head >= rear) return nums;
    2. int left = head, right = rear;
    3. int flag = nums[head];
    4. while(head < rear){
    5. while(head < rear && nums[rear] >= flag) --rear;
    6. nums[head] = nums[rear];
    7. while(head < rear && nums[head] <= flag) ++head;
    8. nums[rear] = nums[head];
    9. }
    10. nums[head] = flag;
    11. QuickSort(nums, left, head - 1);
    12. QuickSort(nums, head + 1, right);
    13. return nums;

    } }

// 法一改进版 class Solution { public int[] getLeastNumbers(int[] arr, int k) { // 先排序再取值 if(arr.length == 0 || k == 0) return new int[]{}; return QuickSort(arr, 0, arr.length - 1, k); }

  1. int[] QuickSort(int[] nums, int head, int rear, int k){
  2. int index = partition(nums, head, rear);
  3. if(head == k - 1) return Arrays.copyOf(nums, k);
  4. return index >= k? QuickSort(nums, head, index - 1, k):QuickSort(nums, index + 1, rear, k);
  5. }
  6. int partition(int[] nums, int head, int rear){
  7. int left = head, right = rear;
  8. int flag = nums[head];
  9. while(head < rear){
  10. while(head < rear && nums[rear] >= flag) --rear;
  11. nums[head] = nums[rear];
  12. while(head < rear && nums[head] <= flag) ++head;
  13. nums[rear] = nums[head];
  14. }
  15. return head;
  16. }

}

// 法二 使用额外的辅助空间 class Solution { //private int[] nums; public int[] getLeastNumbers(int[] arr, int k) { if(arr.length == 0 || k == 0) return new int[]{}; int[] tag = new int[10000+1]; for(int i = 0; i < arr.length; i++){ ++tag[arr[i]]; } int[] res = new int[k]; int count = 0; for(int i = 0; i < tag.length; i++){ if(tag[i] > 0){ for(int j = 0; j < tag[i] && count < k; j++) res[count++] = i; } if(count == k) return res; } return res; } }

  1. - 运行结果:
  2. 法一(图一)、法二(图二)、法一改进版(图三)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611555129258-3eb54ba6-6b45-4ad8-8269-12fc4f40b6b4.png#align=left&display=inline&height=232&margin=%5Bobject%20Object%5D&name=image.png&originHeight=463&originWidth=644&size=29267&status=done&style=none&width=322)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611556731283-c3df51a5-be16-4c28-bb5d-873f1c063629.png#align=left&display=inline&height=232&margin=%5Bobject%20Object%5D&name=image.png&originHeight=464&originWidth=661&size=29459&status=done&style=none&width=330.5)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611559807828-b0c499e8-fff8-4623-852c-824cc4a84aa4.png#align=left&display=inline&height=224&margin=%5Bobject%20Object%5D&name=image.png&originHeight=448&originWidth=655&size=28561&status=done&style=none&width=327.5)
  3. <a name="loEoJ"></a>
  4. ### 2021.01.27 0~n-1中缺失的数字
  5. 今日题目:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0n-1之内。在范围0n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611755410430-4c25f4f1-f13a-47d3-9240-5b0e71d648eb.png#align=left&display=inline&height=114&margin=%5Bobject%20Object%5D&name=image.png&originHeight=114&originWidth=189&size=2480&status=done&style=none&width=189)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611755452721-e776f356-dccd-4caf-9c96-78bb64ecaea8.png#align=left&display=inline&height=116&margin=%5Bobject%20Object%5D&name=image.png&originHeight=118&originWidth=271&size=3508&status=done&style=none&width=266)<br />限制:1 <= 数组长度 <= 10000
  6. - 思路:
  7. > 排序数组中的搜索问题首先想到二分查找,或者是双指针法
  8. - 法一:想到之前的一个原地排序的算法,这题是否能参考一下,分为两种情况:一种是数组中有n-1,另一种情况是缺失的数就是n-1,进行原地排序后,再遍历一遍数组,n-1所在的位置就是缺失的数,若是没有查找到n-1,则直接返回n-1。原地排序的过程:若nums[i] != i nums[i] != n -1,就互换nums[i]与nums[ nums[i] ]的值。时间复杂度为O(n),空间复杂度为O(1)。
  9. - 法二:二分查找。我忽略了题目中一个很重要的条件:这是个递增排序的数组。根据题意,数组按照以下规则划分为两部分:左子数组:nums[i] = i ,右子数组:nums[i] != i ,因此,缺失的数就是右子数组首元素的索引值。
  10. - 算法过程:
  11. 1. 初始化:i = 0j = nums.length
  12. 1. 循环二分:当i <= j时循环;
  13. 1. 计算中点mid = i + j)/ 2
  14. 1. num[i] == i,则右子数组首元素一定在区间[mid + 1, j]中;
  15. 1. nums[i] != i,则右子数组首元素一定在区间[i , mid - 1]中;
  16. 3. 返回值:跳出循环时,变量ij分别指向“右子数组首元素”和“左子数组末元素”。返回i即可。
  17. - 时间复杂度:O(logn),空间复杂度O(1);
  18. - 法三:因为之前忽略了数组是递增的这个条件,所以这个方法就直接遍历数组,当遇到数组索引和对应的值不同时,就直接返回索引值 i
  19. - 时间复杂度:O(n),空间复杂度:O(1)。
  20. - 自己的菜鸡代码:
  21. ```java
  22. // 法一
  23. class Solution {
  24. public int missingNumber(int[] nums) {
  25. int temp;
  26. for(int i = 0; i < nums.length; i++){
  27. while(nums[i] != i && nums[i] != nums.length){
  28. temp = nums[i];
  29. nums[i] = nums[temp];
  30. nums[temp] = temp;
  31. }
  32. }
  33. for(int i = 0; i < nums.length; i++){
  34. if(nums[i] == nums.length) return i;
  35. }
  36. return nums.length;
  37. }
  38. }
  39. // 法二
  40. class Solution {
  41. public int missingNumber(int[] nums) {
  42. // 采用二分查找的思想(这个要怎么写啊555好难)
  43. int i = 0, j = nums.length - 1, mid;
  44. while(i <= j){
  45. mid = (i + j) / 2;
  46. if(nums[mid] == mid) i = mid + 1;
  47. else j = mid - 1;
  48. }
  49. return i;
  50. }
  51. }
  52. // 法三
  53. class Solution {
  54. public int missingNumber(int[] nums) {
  55. int i;
  56. for(i = 0; i < nums.length; i++){
  57. if(nums[i] != i) return i;
  58. }
  59. return i;
  60. }
  61. }
  • 运行结果:

法一(1),法二(2),法三(3):
image.pngimage.pngimage.png

2021.01.28 寻找数组的中心索引

今日题目:给定一个整数类型的数组 nums,请编写一个能够返回数组 “中心索引” 的方法。
我们是这样定义数组 中心索引 的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。
image.pngimage.png
说明:

  • nums 的长度范围为 [0, 10000]。
  • 任何一个 nums[i] 将会是一个范围在 [-1000, 1000]的整数。

  • 思路:

    • 法一:暴力法。遍历一遍数组,逐个计算左侧元素和右侧元素相加的值,若存在,则返回分界点的索引值,若不存在则返回-1。时间复杂度为O(n),空间复杂度为O(1)。
    • 法二:是根据提示写出来的555我好菜。设置一个辅助数组value,长度为nums.length+1,value[i]用来保存nums[0] + nums[1] + ……+nums[i-1]的结果。
      • value[0] = 0:nums[0]的左子数组和为0;value[1] = nums[0]:nums[1]的左子数组和 = nums[0],value[nums.length] = nums[0] + ……+ nums[nums.length - 1]:nums[i]整个数组之和。
      • 然后遍历value数组,value[i]代表nums[i]的左子数组之和,value[nums.length] - value[i] - nums[i]代表右子数组之和。所以当存在左子数组之和等于右子数组之和的情况时,直接返回索引值 i 。
  • 自己的菜鸡代码: ```java // 法一 class Solution { public int pivotIndex(int[] nums) {

    1. // 暴力法
    2. int left, right;
    3. for(int i = 0; i < nums.length; i++){
    4. left = 0; right = 0;
    5. for(int j = 0; j < i; j++) left += nums[j];
    6. for(int k = i + 1; k < nums.length; k++) right += nums[k];
    7. if(left == right) return i;
    8. }
    9. return -1;

    } }

// 法二 class Solution { public int pivotIndex(int[] nums) { int[] value = new int[nums.length+1]; for(int i = 1; i <= nums.length; i++){ value[i] = value[i-1] + nums[i - 1]; }

  1. for(int i = 0; i < nums.length; i++){
  2. if(value[nums.length] - value[i] - nums[i] == value[i]) return i;
  3. }
  4. return -1;
  5. }

}

  1. - 运行结果:
  2. 法一(左)、法二(右):<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611827397275-4c41c46c-5403-4480-8d58-38ab7d26ec42.png#align=left&display=inline&height=299&margin=%5Bobject%20Object%5D&name=image.png&originHeight=458&originWidth=624&size=28958&status=done&style=none&width=408)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611828318301-4e15118e-a6d1-4c13-ab6e-a7c034882483.png#align=left&display=inline&height=294&margin=%5Bobject%20Object%5D&name=image.png&originHeight=424&originWidth=599&size=27667&status=done&style=none&width=415)
  3. <a name="Ntnhf"></a>
  4. ### 2021.01.28 和为S的连续正数序列(未写)
  5. 今日题目:输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611829930853-72da193c-4d48-4541-b9f6-0b9dca31943a.png#align=left&display=inline&height=126&margin=%5Bobject%20Object%5D&name=image.png&originHeight=126&originWidth=236&size=3366&status=done&style=none&width=236)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611829944260-5a7e97bd-ba7a-4ddc-858b-2e164b824f14.png#align=left&display=inline&height=121&margin=%5Bobject%20Object%5D&name=image.png&originHeight=121&originWidth=343&size=4414&status=done&style=none&width=343)<br />限制:1 <= target <= 10^5
  6. - 思路:
  7. - 自己的菜鸡代码:
  8. - 运行结果:
  9. <a name="qBGS1"></a>
  10. ### 2021.01.30 数组中数字出现的次数(位运算牛哇)
  11. 今日题目:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611972367556-8bf794d0-ecbb-460d-963e-5bed71a6bcd4.png#align=left&display=inline&height=126&margin=%5Bobject%20Object%5D&name=image.png&originHeight=126&originWidth=245&size=3690&status=done&style=none&width=245)![image.png](https://cdn.nlark.com/yuque/0/2021/png/1584832/1611972378513-33bd0fde-b2b7-491c-8237-2de4d654abf4.png#align=left&display=inline&height=118&margin=%5Bobject%20Object%5D&name=image.png&originHeight=118&originWidth=320&size=4393&status=done&style=none&width=320)<br />限制:2 <= nums.length <= 10000
  12. - 思路:(5555想不出来)看评论区官方给出的解法是用异或,因为有两个只出现过一次的数字,其他数字都出现了两次,所以有两种情况:
  13. 将整个数组分成左右子数组,两个数字在不同的子数组中,两个数都在同一子数组中。
  14. > 查找只出现一次的数字(1个):全员进行异或操作即可。考虑异或操作的性质:对于两个操作数的每一位,相同结果为 0,不同结果为 1。那么在计算过程中,成对出现的数字的所有位会两两抵消为 0,最终得到的结果就是那个出现了一次的数字。
  15. 算法:<br />先把整个数组进行异或,最后得到的数字就是两个目标值异或的结果(肯定不为0),找到最低位的1,然后以这一位为分组依据。再将这两组内的元素进行异或,最后得出的两个结果就是所求值。
  16. - 自己的菜鸡代码:
  17. ```java
  18. class Solution {
  19. public int[] singleNumbers(int[] nums) {
  20. int ret = 0;
  21. for (int n : nums) {
  22. ret ^= n;
  23. }
  24. int div = 1;
  25. while ((div & ret) == 0) {
  26. div <<= 1;
  27. }
  28. // 下面的比较不好理解
  29. int a = 0, b = 0;
  30. for (int n : nums) {
  31. if ((div & n) != 0) {
  32. a ^= n;
  33. } else {
  34. b ^= n;
  35. }
  36. }
  37. return new int[]{a, b};
  38. }
  39. }
  • 运行结果:

image.png

2021.01.31 数组中数字出现的次数II(同样是位运算)

今日题目:在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
image.pngimage.png
限制:

  • 1 <= nums.length <= 10000
  • 1 <= nums[i] < 2^31

  • 思路:

    1. 法一:以空间换时间,可以用哈希表记录每个数字出现的次数,最后遍历一遍哈希表找到次数为1的目标值返回即可。时间复杂度:O(n),空间复杂度(n)。(大佬甚至没有列出这种方法orz)。(大佬们总能玩出新花样)
    2. 法二:因为只有一个数字出现了一次,其他数字出现了三次,所以可以利用一个辅助的数组rec(容量为32位)用于记录每一位1出现的次数,最后遍历rec数组,都每个值都进行模3计算,若是结果为0,则代表目标值的这一位为0,若结果为1,则表示目标值的这一位为1。
    3. 法三:(有限状态自动机,大佬牛哇)看不懂。
  • 自己的菜鸡代码: ```java // 法一 哈希表法 class Solution { public int singleNumber(int[] nums) {
    1. // 没有时间跟空间的要求,所以可以采用哈希表
    2. Map<Integer,Integer> map = new HashMap<>();
    3. int temp = 0;
    4. for(int i = 0; i < nums.length; i++){
    5. if(map.get(nums[i]) == null) map.put(nums[i],1);
    6. else {
    7. temp = map.get(nums[i]);
    8. map.put(nums[i], ++temp);
    9. }
    10. }
    11. for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
    12. if(entry.getValue() == 1){
    13. temp = entry.getKey();
    14. break;
    15. }
    16. }
    17. return temp;
    } }

// 法二 统计二进制数中1的数量 class Solution { public int singleNumber(int[] nums) { int res = 0; int[] rec = new int[32]; for(int num : nums){ for(int i = 31; i >= 0; —i){ // 少点if判断能降低时间复杂度 rec[i] += (num & 1); num >>>= 1; } } for(int i = 0; i < 32; i++){ res <<= 1; res |= (rec[i] % 3); } return res; } }

```

  • 运行结果:

法一(1)、法二(2)、法三(3)
image.pngimage.png

2021.02.12 杨辉三角

今日题目:给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。在杨辉三角中,每个数是它左上方和右上方的数的和。
image.pngimage.png
进阶:你可以优化你的算法到 O(k) 空间复杂度吗?

  • 思路:
  • 自己的菜鸡代码:
  • 运行结果: