leetcode数组相关

4寻找两个有序数组的中位数

思路:

  • 其中一个为空的情况,利用len/2和(len-1)/2两个结果来求中位数
  • 两者不为空,调用helper
    • 当其中一个被筛选完,就选另一个的left + k
    • 当两个数组都还有候选数字,而k减到0时,取两个数组中的left最小者
    • k未到0时,分别从两者中取(k-1)/2。(考虑取第10时,相当于取第9(0开始),则9/2和(9-1)/2都是4,即取0~4共5个。取前11时,同样应该取5个,但11/2等于5,就是取6个了)当然,取值不能超过边界right。
    • 判断两数组的取值中最大的哪个小,那么该组小的那部分就可以在下次调用时忽略了。新的边界要+1做调整。
int m = arr1.length;
int n = arr2.length;
if (m == 0) return (arr2[(n - 1) / 2] + arr2[(n / 2)]) / 2.0;
if (n == 0) return (arr1[(m - 1) / 2] + arr1[(m / 2)]) / 2.0;
int res1 = helper(arr1, 0, m - 1, arr2, 0, n - 1, (m + n -1) / 2);
int res2 = helper(arr1, 0, m - 1, arr2, 0, n - 1, (m + n) / 2);
return (res1 + res2) / 2.0;

// helper
if (left1 > right1) return arr2[left2 + k];
if (left2 > right2) return arr1[left1 + k];
if (k == 0) return Math.min(arr1[left1], arr2[left2]);

int newLeft1 = Math.min(left1 + (k - 1) / 2, right1); // 参考上面,这里也是-1
int newLeft2 = Math.min(left2 + (k - 1) / 2, right2);

if (arr1[newLeft1] < arr2[newLeft2]) {
    return helper(arr1, newLeft1 + 1, right1, arr2, left2, right2, k - newLeft1 - 1 + left1);
} else {
    return helper(arr1, left1, right1, arr2, newLeft2 + 1, right2, k - newLeft2 - 1 + left2);

11盛最多水的容器,42接雨水

11盛最多水的容器

思路:

  • 设置左右指针
  • 循环,只要left<right
    • 计算当前最大装水量,然后较低的边界收缩。
while (left < right){
    int lh = height[left], rh = height[right];
    res = Math.max(res, Math.min(lh, rh) * (right - left));
    
    if (lh < rh) left++;
    else right--;
}

42接雨水

思路:

  • 循环
    • 计算当前最低位并移动最低位的指针
    • 根据当前最低位以及过去level的最高位来确定level。(实际上level从0开始,说明level是通过lower来提高的,只要曾经出现过高的边界,那么level就不需要降低,因为已经有足够高的边界来容纳这个level)
    • res累加上当前最低位与水位的差
while (right > left){
    // 计算当前最低位并移动最低位的指针
    int lower = height[(height[left] < height[right]) ? left++ : right--];

    // 根据目前两个指针各自所遇到最高值中较小的那个值来更新可以装载的水位
    // 就是木桶原理,短板决定水位,当短板高于旧水位、或者说是短板高于旧短板,水位就提高了
    level = Math.max(lower, level);
    
    // 根据当前指针的高度以及水位的差值计算该坐标下的雨水量
    res += level - lower;
}

15三数之和,16最接近的三数之和,18四数之和

三数之和

思路:

  • 排序,用来考虑可能性、后面去重和mid与right的调整

  • 遍历

    • 以当前数作为left,并去重

    • 以left+1为mid,len-1为right,计算mid和right值的和,比较与left的差距,调整mid和right的位置。如果和等于left,记录left、mid和right,并调整mid和right,同时去重。

Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
int n = nums.length;

// 可能性,注意n == 0
if (n == 0 || nums[0] > 0 || nums[n-1] < 0) return res;

// 遍历
for (int left = 0; left < n - 2 && nums[left] <= 0; left++){
    // left去重
    if (left != 0 && nums[left] == nums[left - 1]) continue;

    // mid、right、target变量
    int mid = left + 1, right = n - 1, target = -nums[left];
    // 遍历mid到right
    while (mid < right){
        // 大于
        if (nums[mid] + nums[right] > target) right--;
        // 小于
        else if (nums[mid] + nums[right] < target) mid++;
        // 相等,去重
        else {
            res.add(Arrays.asList(nums[left], nums[mid], nums[right]));
            mid++;
            right--;
            while(mid<right && nums[mid] == nums[mid-1]) mid++;
            while(mid<right && nums[right] == nums[right+1]) right--;
        }
    }
}
return res; 

// 四数和
for (int i = 0; i < n - 3 && nums[i]*4 <= target; i++){
    if (i > 0 && nums[i] == nums[i-1]) continue;
    if(nums[i] + 3*nums[n-1] < target) continue;
    for (int j = i + 1; j < n - 2 && nums[i] + nums[j]*3 <= target ; j++){
        if (j > i + 1 && nums[j] == nums[j-1]) continue;

最接近的三数之和

思路:

  • 设置closest和diff并初始化。(由于找最接近,所以只要有三个数就是必定存在的)
  • 与上面类似,需要遍历(可以不考虑mid和right的去重,省事)
    • 遍历开始就是判断新的和是否需要更新diff和closet,然后根据和于target的差距调整right和mid(加上sum==target的判断会快一点)
Arrays.sort(nums);
int n = nums.length;
int closest = nums[0] + nums[n/2] + nums[n-1];
int diff = Math.abs(target - closest);

for (int left = 0; left < n-2; left++){
    if (left != 0 && nums[left] == nums[left-1]) continue;
    
    int mid = left +1, right = n - 1;
    while (mid < right){
        int sum = nums[left] + nums[mid] + nums[right];
        int newDiff = Math.abs(target - sum);
        if (newDiff < diff){
            diff = newDiff;
            closest = sum;
        }
        
        if (sum > target) right--;
        else if(sum < target) mid++;
        else return target;
    }
}

26/80删除排序数组中的重复项, 27移除元素

思路:

  • 设置去重指针
  • 遍历,从1开始
    • 如果当前数与去重指针不同,去重指针的下一个就可改写为当前数的值
  • 返回去重指针+1
int n = nums.length;
if (n <= 1) return n;

int p = 0; // 如果允许重复k次,p = k-1
for (int i = 1; i< n; i++){ // 如果允许重复k次,则从k开始
    // 如果允许重复k次,则为num[p-k+1]
    if (nums[p] != nums[i]) nums[++p] = nums[i];
}
return p + 1;

27移除元素

参考list中的移除元素,由于不确定第一个是否需要去除,所以去重指针定义为-1。其他近乎于上面一样。只不过比较的是nums[i] 与 val,而上面是num[p] 与nums[i]

int p = -1;
for(int num : nums){
    if (num != val) nums[++p] = num;
}
return p + 1;

31下一个排列

思路:

  • 从右往左,找出第一个小于右边的数字的index x
  • 判断是否存在这个数a,如果存在,要对它进行一次交换
    • 再次从右往左找第一个小于大于a的数字的index,将两个数位置交换
  • reverse从index x到末尾的数字
public void nextPermutation(int[] nums) {
    int n = nums.length, i = n - 2, j = n - 1;
    while (i >= 0 && nums[i] >= nums[i + 1]) --i;
    if (i > -1) {
        while (nums[j] <= nums[i]) --j;
        swap(nums, i, j);
    } 
    reverse(nums, i+1, n-1);
}

private static void reverse(int[] nums, int startIndex, int endIndex) {
    while (startIndex < endIndex) {
        swap(nums, startIndex++, endIndex--);
    }
}

53最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

思路:

  • 设置res = Integer.MIN_VALUE(它是根据curSum取的)和curSum = 0 (由上述“子数组最少包含一个元素”,可以将curSum和res设为mums[0],并从1开始遍历,参考下面分治法的代码)
  • 遍历
// curSum的思想是,如果当前值加上之前的累加值反而变少了,
// 那么说明之前的子序列已经比不上现在的一个数了,可以重新开始累加了。
curSum = Math.max(curSum + num, num);
res = Math.max(res, curSum);
// 分治法
public int maxSubArray(int[] nums) {
    return helper(nums, 0, nums.length - 1);
}

public int helper(int[] nums, int left, int right) {
    // 底层返回
    // 返回left的原因是,mid是通过除法得到的,结果有可能等于left,然后调用时mid - 1就有可能得-1
    // 例如 0 和 1, 9 和 10,前者mid = 0,后者 9,在调用时,只有mid-1会超出范围
    if (left >= right) return nums[left];

    // 找中间点,对左半边和右半边递归调用
    int mid = left + (right - left) / 2;
    int lmax = helper(nums, left, mid - 1);
    int rmax = helper(nums, mid + 1, right);

    int mmax = nums[left], msum = mmax;
    for (int i = left + 1; i <= right; i++){
        msum = Math.max(msum + nums[i], nums[i]);
        mmax = Math.max(mmax, msum);
    }

    // 取左中右的最大值
    return Math.max(mmax, Math.max(lmax, rmax));
}

56合并区间

思路:

  • 修改interval类,实现Comparable<Interval>的compareTo
  • Collections.sort(intervals)
  • 先将第一个元素放进res,然后遍历
    • 比较当前interval的start和res中最后一个interval的end,如果大于,说明不重叠,直接把当前interval添加到res;否则更改res最后一个interval的end为它本身end和当前interval的end之中的最大值。
res.add(intervals.get(0));
for (int i = 1; i < intervals.size(); ++i) {
    Interval curInterval = intervals.get(i);
    int lastIntervalEnd = res.get(res.size()-1).end;
    if (lastIntervalEnd < curInterval.start) {
        res.add(curInterval);
    } else {
        res.get(res.size()-1).end = Math.max(lastIntervalEnd, curInterval.end);
    }
}

128最长连续序列

输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。

思路:题目要求O(n),所以不能用排序

  • 将数据加入set
  • 遍历
    • 删除遍历到的数,并设置pre、next指针,分别为num-1和num+1,循环搜索并删除set中pre和next所指代的数,并不断扩大pre和next,只要指针所指代的数不存在于set,该指针就停止扩大。循环后更新maxLen = next - pre + 1
for (int num : nums) s.add(num);
for (int num : nums) {
    if (s.remove(num)) {
        int pre = num - 1, next = num + 1;
        while (s.remove(pre)) --pre;
        while (s.remove(next)) ++next;
        // 注意,循环是在发现不存在时退出的,说明两个指针都多走了一步,所以下面+1-2
        res = Math.max(res, next - pre - 1);
    }
}

674最长连续递增序列

思路:

  • 设置res记录最大值和cnt计数器
  • 遍历
    • 如果递增,cnt++,并更新res
    • 否则重置cnt = 1
// 注意这里cnt和res的初始值
int cnt = 1, res = 1, n = nums.length;
if (n <= 1) return n;
for (int i = 1; i < n; i++) {
    if (nums[i] > nums[i-1]) {
        cnt++;
        res = Math.max(res, cnt);
    }
    else cnt = 1;
}

return res;

45/55跳跃游戏

// 55能否到达终点
int n = nums.length, reach = 0;
for (int i = 0; i < n; ++i) { // 实际上能够遍历到n-1已经说明能到达终点了,下面的i > reach判断了一些不能到达终点的情况。
    // i > reach 是用于跳进0的情况,此时不可能跳到最后
    if (i > reach || reach >= n - 1) break;
    // 不断更新当前能够到达的最远距离
    reach = Math.max(reach, i + nums[i]);
}
return reach >= n - 1;

// 45假设总能到达终点
int n = nums.length, curReach = 0, preReach = 0, cnt = 0;
for (int i = 0; i < n; ++i) {
    curReach = Math.max(curReach, i + nums[i]);
    if (i == preReach) { // i小于preReach时更新curReach是为了确定下一步能够到达的最远距离。
        preReach = curReach;
        cnt++;
        if (preReach >= n - 1) break;
    }
}
return cnt;

41缺失的第一个正数

给定一个未排序的整数数组,找出其中没有出现的最小的正整数。

输入: [3,4,-1,1]
输出: 2

思路:

改造原数组,使得nums[0] = 1, nums[i] = i + 1。实现方法是遍历,当nums[i]所代表的数不超出数组坐标范围,而且它应该在的位置不等于它,即nums[i] != nums[nums[i] - 1],那么就交换,把它放到它应该在的index。遍历完就可以在遍历一次,检查哪个位置缺失了应该出现的值。如果没有,就返回n+1。

int n = nums.length;
for (int i = 0; i < n; i++){
    while (nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i] - 1]){
        swap(nums, i, nums[i] - 1);
    }
}
for (int i = 0; i< n; i++){
    if (nums[i] != i + 1) return i + 1;
}
return n + 1;

946验证栈序列

思路:

  • 新建一个栈
  • 遍历
    • 往栈添加push序列的元素
    • 如果栈不为空,而且栈顶等于pop序列,说明需要pop(pop序列另外设置一个指针)
  • 返回栈是否为空

66加一

思路:

  • 设置carry=1
  • 从后遍历,加carry,更新carry,判断carry是否等于1,即是否进1,是的话继续遍历,否的话就可以返回结果了。
  • 如果遍历后carr等于0,说明数组只由“9”组成,返回比原数组长度大1,且res[0] = 1即可,否则返回原数组。
for (int i = n - 1; i >=0; i--){
    int digit = nums[i] + carry;
    nums[i] = digit % 10;
    carry = digit / 10;
    if (carry == 0) return nums;
}

233数字1的个数

思路:https://www.cnblogs.com/xuanxufeng/p/6854105.html

int res = 0;
for (long i = 1; i <= n; i *= 10) {
    long a = n / i;
    long b = n % i;
    res += (a + 8) / 10 * i + (a % 10 == 1 ? b + 1 : 0);
}
return res;

84/85最大矩形

思路:

  • 先说一维数组求最大矩形面积:遍历,如果下一个是大于等于当前元素的,继续。否则开始往前遍历算面积,这个过程不断更新最小高度和res。
  • 扩展到二维:新建一个dp数组,根据当前值以及上一层的值来构造一维数组,并把这个数组代入上面的函数,即可计算到某一层为止的最大矩形面积。
public int maximalRectangle(char[][] matrix) {
    int m = matrix.length, n = matrix[0].length;
    int res = 0;
    int[] dp = new int[n];
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            dp[j] = matrix[i][j] == '0' ? 0 : dp[j] + 1;
        }
        res = Math.max(res, largestRectangleArea(dp));
    }
    return res;
}

public int largestRectangleArea(int[] heights) {
    int res = 0, n = heights.length;
    for (int i = 0; i < n; i++) {
        if (i + 1 < n && heights[i] <= heights[i + 1]) continue;
        int minH = heights[i];
        for (int j = i; j >= 0; j--) {
            minH = Math.min(heights[j], minH);
            int area = minH * (i - j + 1);
            res = Math.max(res, area);
        }
    }
    return res;
}

347前K个高频元素

思路:

1.HashMap + PriorityQueue

2.HashMap + 桶排

桶排,即新建一个长度为最大频率的List数组,然后遍历map,在频率index上的list添加key。最后遍历输出输出结果。

697数组的度

给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。

你的任务是找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

输入: [1, 2, 2, 3, 1]
输出: 2
解释: 
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.

思路:

  • 新建HashMap记录每个数字的freq和startIdx
  • 遍历,更新HashMap,如果当前数字的freq等于degree,那么更新长度,如果大于degree,那么更新长度和degree

88合并两个有序数组

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]

思路:

  • 类似合并两个有序链表,需要三个指针,两个数组各一个(从m-1和n-1开始),一个公共指针(从m+n-1开始)
  • 循环,只要其中一个不为空
    • 比较两个数组指针上的数谁大,大的填充到nums1[公共指针],并移动指针
  • 循环,把剩余的nums2填充到nums1

1两数之和

思路:用map存储遍历过的数及其index;每次遍历检查map中是否有所需补数

217存在重复元素

思路:set

扩展

排序数组中小于0的最大数的下标和大于0的最小数的下标。二分找0。未排序:维护两个长度为2的数组,类似于找最值。

原文地址:https://www.cnblogs.com/code2one/p/10100171.html