美文网首页
2.数组(二)

2.数组(二)

作者: 今天柚稚了么 | 来源:发表于2020-03-25 19:44 被阅读0次

https://leetcode-cn.com/tag/array/

题目汇总

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

35. 搜索插入位置简单

39. 组合总和中等

40. 组合总和 II中等

41. 缺失的第一个正数困难

42. 接雨水困难

45. 跳跃游戏 II困难

48. 旋转图像中等

53. 最大子序和简单

54. 螺旋矩阵中等

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(logn) 级别。如果数组中不存在目标值,返回 [-1, -1]
输入: nums = [5,7,7,8,8,10], target = 8,输出: [3,4]

思路:二分查找

二分查找算法细节详解这位博主写的简直不能太清晰!!!值得一看!!!

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int left = left_bound(nums, target);
        int right = right_bound(nums, target);
        return new int[]{left, right};

    }
    public int left_bound(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] == target) {
                // 别返回,收缩左侧边界
                right = mid - 1;
            }
        }
        // 最后要检查 left 越界的情况
        if (left >= nums.length || nums[left] != target)
            return -1;
        return left;
    }


    public int right_bound(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] == target) {
                // 别返回,收缩右侧边界
                left = mid + 1;
            }
        }
        // 最后要检查 right 越界的情况
        if (right < 0 || nums[right] != target)
            return -1;
        return right;
    }
    
}

35. 搜索插入位置简单

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
输入: [1,3,5,6], 5,输出: 2

思路:二分查找

每次根据 nums[mid] 和 target 之间的大小进行判断,相等则直接返回下标,nums[mid] < target 则start右移,nums[mid] > target 则 end 左移
查找结束如果没有相等值则返回 start,该值为插入位置

class Solution {
    public int searchInsert(int[] nums, int target) {
        if(nums.length==0||nums==null)
            return 0;
        int start = 0;
        int end = nums.length-1;
        while(start<=end){
            int mid = start+(end-start)/2;
            if(nums[mid]==target){
                return mid;
            }
            else if(nums[mid]<target){
                start = mid + 1;
            }
            else{
                end = mid - 1;
            }
        }
    return start;
    }
}

39. 组合总和中等

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取
说明: 所有数字(包括 target)都是正整数,解集不能包含重复的组合。
输入: candidates = [2,3,6,7], target = 7,所求解集为:
[
[7],
[2,2,3]
]

思路:回溯算法+剪枝

这个解答讲解清楚,点它!!!我何时才能拥有这样的脑子呢,哦我不配

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        int len = candidates.length;
        Arrays.sort(candidates);
        dfs(candidates, len, target, 0, new ArrayDeque<>(), res);
        return res;
    }

    /**
     * @param candidates 数组输入
     * @param len        输入数组的长度,冗余变量
     * @param residue    剩余数值
     * @param begin      本轮搜索的起点下标
     * @param path       从根结点到任意结点的路径
     * @param res        结果集变量
     */
    private void dfs(int[] candidates,
                     int len,
                     int residue,
                     int begin,
                     Deque<Integer> path,
                     List<List<Integer>> res) {
        if (residue == 0) {
            // 由于 path 全局只使用一份,到叶子结点的时候需要做一个拷贝
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = begin; i < len; i++) {

            // 在数组有序的前提下,剪枝
            if (residue - candidates[i] < 0) {
                break;
            }

            path.addLast(candidates[i]);
            dfs(candidates, len, residue - candidates[i], i, path, res);
            path.removeLast();

        }
    }
}

40. 组合总和 II中等

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次
说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。
    输入: candidates = [10,1,2,7,6,1,5], target = 8,所求解集为:
    [
    [1, 7],
    [1, 2, 5],
    [2, 6],
    [1, 1, 6]
    ]
思路:回溯法+剪枝

大佬解答

以 target 为根结点,依次减去数组中的数字,直到小于 0 或者等于 0,把等于 0 的结果记录到结果集中。

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        int len = candidates.length;
        Arrays.sort(candidates);
        dfs(candidates, len, target, 0, new ArrayDeque<>(), res);
        return res;
    }
    private void dfs(int[] candidates,
                     int len,
                     int residue,
                     int begin,
                     Deque<Integer> path,
                     List<List<Integer>> res) {
        if (residue == 0) {
            // 由于 path 全局只使用一份,到叶子结点的时候需要做一个拷贝
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = begin; i < len; i++) {
            // 在数组有序的前提下,大剪枝
            if (residue - candidates[i] < 0) {
                break;
            }
             // 小剪枝,去除重复元素
            if (i > begin && candidates[i] == candidates[i - 1]) {
                continue;
            }

            path.addLast(candidates[i]);
            dfs(candidates, len, residue - candidates[i], i+1, path, res);
            path.removeLast();

        }
    }
}

41. 缺失的第一个正数困难

给定一个未排序的整数数组,找出其中没有出现的最小的正整数。
输入: [1,2,0],输出: 3
输入: [7,8,9,11,12],输出: 1

思路:映射哈希+交换

数值为 i 的数映射到下标为 i - 1 的位置上
评论区大佬添加的注释很全面:使用座位交换法
缺失的第一个整数是 [1, len + 1] 之间,那么我们可以遍历数组,然后将对应的数据填充到对应的位置上去,比如 1 就填充到 nums[0] 的位置, 2 就填充到 nums[1],如果填充过程中, nums[i] < 1 && nums[i] > len,那么直接舍弃。填充完成,我们再遍历一次数组,如果对应的 nums[i] != i + 1,那么这个 i + 1 就是缺失的第一个正数
比如 nums = [7, 8, 9, 10, 11], len = 5
我们发现数组中的元素都无法进行填充,直接舍弃跳过,那么最终遍历数组的时候,我们发现 nums[0] != 0 + 1,即第一个缺失的是 1
比如 nums = [3, 1, 2], len = 3
填充过后,我们发现最终数组变成了 [1, 2, 3],每个元素都对应了自己的位置,那么第一个缺失的就是 len + 1 == 4

class Solution {
    public int firstMissingPositive(int[] nums) {
        for(int i=0;i<nums.length;i++){
            while(nums[i]>0&&nums[i]<=nums.length&&nums[nums[i]-1]!=nums[i]){
                swap(nums,nums[i]-1,i);
            } 
        }
        for(int i=0;i<nums.length;i++){
            if(nums[i]!=i+1){
                return i+1;
            }
        }
    return nums.length+1;
    }
    public void swap(int[] nums, int index1, int index2){
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

42. 接雨水困难

精选题解的几种解法都超级棒

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
输入: [0,1,0,2,1,0,1,3,2,1,2,1],输出: 6

思路一:暴力法,时间复杂度O(n*n)

对于数组中的每个元素,下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值。为了找到最大值每次都要向左和向右扫描一次。

class Solution {
    public int trap(int[] height) {
        int sum = 0;
        for(int i=1;i<height.length-1;i++){
            int max_left = 0;
            int max_right = 0;
            //左边最高列
            for(int j=i;j>=0;j--){
                if(height[j]>max_left)
                    max_left = height[j];
            }
            //右边最高列
            for(int j=i;j<height.length;j++){
                if(height[j]>max_right)
                    max_right = height[j];

            }
            int min = Math.min(max_left,max_right);
            //只有较小的一列大于当前列的高度才会有水
            if (min > height[i]) 
                sum += (min - height[i]);
        }
    return sum;
    }
}
思路二:动态规划,时间复杂度O(n)

在按列求解的暴力法中,对于每一列,我们求它左边最高的墙和右边最高的墙,都是重新遍历一遍所有高度。为了降低时间复杂度,定义两个数组,max_left [i] 代表第 i 列左边最高的墙的高度,max_right[i] 代表第 i 列右边最高的墙的高度。

class Solution {
    public int trap(int[] height) {
        int sum = 0;
        int[] max_left = new int[height.length];
        int[] max_right = new int[height.length];
        for(int i = 1; i < height.length - 1; i++){
            max_left[i] = Math.max(max_left[i-1],height[i-1]);
        }
        for(int  i = height.length - 2; i >= 0; i--){
            max_right[i] = Math.max(max_right[i + 1], height[i + 1]);

        }
        for (int i = 1; i < height.length - 1; i++) {
            int min = Math.min(max_left[i],max_right[i]);
            //只有较小的一列大于当前列的高度才会有水
            if(min>height[i])
                sum += (min - height[i]);
        }
    return sum;
    }
}

45. 跳跃游戏 II困难

给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。
输入: [2,3,1,1,4],输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:假设你总是可以到达数组的最后一个位置。

思路:贪心算法

搬运解题区精选解题的顺藤摸瓜

class Solution {
    public int jump(int[] nums) {
        int end = 0;
        int maxPosition = 0; 
        int steps = 0;
        for(int i = 0; i < nums.length - 1; i++){
            //找能跳的最远的
            maxPosition = Math.max(maxPosition, nums[i] + i); 
            if( i == end){ //遇到边界,就更新边界,并且步数加一
                end = maxPosition;
                steps++;
            }
        }
    return steps;
    }
}

48. 旋转图像中等

给定一个 n × n 的二维矩阵表示一个图像。将图像顺时针旋转 90 度。
说明:你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]

思路:转置+翻转,时间复杂度O(n*n)

先转置矩阵,然后翻转每一行

class Solution {
    public void rotate(int[][] matrix) {
        int len = matrix.length;
        //矩阵转置
        for(int i=0;i<len;i++){
            for(int j=0;j<i;j++){
                int temp = matrix[j][i];
                matrix[j][i] = matrix[i][j];
                matrix[i][j] = temp;
            }
        }
        //翻转
        for(int i=0;i<len;i++){
            for(int j=0;j<len/2;j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[i][len-j-1];
                matrix[i][len-j-1] = temp;
            }
        }
    }
}

53. 最大子序和简单

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入: [-2,1,-3,4,-1,2,1,-5,4],输出: 6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

思路一:动态规划

首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans
如果 sum > 0,则 sum 保留并加上当前遍历数字
如果 sum <= 0,则需要舍弃,sum 直接更新为当前遍历数字
每次比较 sum 和 ans 的大小,将最大值置为ans,遍历结束返回结果
时间复杂度O(n)

class Solution {
    public int maxSubArray(int[] nums) {
        int ans = nums[0];
        int sum = 0;
        for(int i=0;i<nums.length;i++){
            if(sum>0){
                sum += nums[i];
            }
            else{
                sum = nums[i];
            }
            ans = Math.max(sum,ans);
        }
    return ans;
    }
}
思路二:分冶法

见官方题解

54. 螺旋矩阵中等

给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
与剑指Offer的顺时针打印矩阵相同

思路:设置上下左右四个界限法

定义四个变量代表范围,up、down、left、right
1.向右走存入整行的值,当存入后,该行再也不会被遍历,代表上边界的 up 加一,同时判断是否和代表下边界的 down 交错
2.向下走存入整列的值,当存入后,该列再也不会被遍历,代表右边界的 right 减一,同时判断是否和代表左边界的 left 交错
3.向左走存入整行的值,当存入后,该行再也不会被遍历,代表下边界的 down 减一,同时判断是否和代表上边界的 up 交错
4.向上走存入整列的值,当存入后,该列再也不会被遍历,代表左边界的 left 加一,同时判断是否和代表右边界的 right 交错

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if(matrix==null||matrix.length==0||matrix[0].length==0){
            return list;
        }
        int up = 0;//上边界
        int down = matrix.length - 1;//下边界,行数-1
        int left = 0;//左边界
        int right = matrix[0].length - 1;//右边界,列数-1
        while(true){
            //最上边一行,向右走存入整行的值
            for(int col=left;col<=right;col++){
                list.add(matrix[up][col]);
            }
            up++;//向下逼近
            if(up>down){
                break;
            }
            //最右边一列,向下走存入整列的值
            for(int row=up;row<=down;row++){
                list.add(matrix[row][right]);
            }
            right--;//向左逼近
            if(right<left){
                break;
            }
            //最下边一行,向左走存入整行的值
            for(int col=right;col>=left;col--){
                list.add(matrix[down][col]);
            }
            down--;//向上逼近
            if(down<up){
                break;
            }
             //最左边一列,向上走存入整列的值
            for(int row=down;row>=up;row--){
                list.add(matrix[row][left]);
            }
            left++;//向右逼近
            if(left>right){
                break;
            }
        }
     return list;
    }
}

相关文章

网友评论

      本文标题:2.数组(二)

      本文链接:https://www.haomeiwen.com/subject/tzklyhtx.html