【数据结构&算法】数组的LeetCode练习题(5题:11、283、15、88、4)

在讲LeetCode关于数组的习题之前,先讲述两个思想:空间换时间和时间换空间

  • 空间换时间
    • 用空间换时间的设计思想。当内存空间充足的时候,如果我们更加追求代码的执行速度,我们就可以选择空间复杂度相对较高、但时间复杂度相对很低的算法或者数据结构。
    • 缓存实际上就是利用了空间换时间的设计思想。如果我们把数据存储 在硬盘上,会比较节省内存,但每次查找数据都要询问一次硬盘,会比较慢。但如果我们通 过缓存技术,事先将数据加载在内存中,虽然会比较耗费内存空间,但是每次数据查询的速 度就大大提高了
    • 数据结构中双向链表正是用了空间换时间,提高了crud的效率
  • 时间换空间
    • 如果内存比较紧缺,比如代码跑在 手机或者单片机上,这个时候,就要反过来用时间换空间的设计思路

对于执行较慢的程序,可以通过消耗更多的内存(空间换时间)来进行优化;而消耗过多内存的程序,可以通过消耗更多的时间(时间换空间)来降低内存的消耗

注:下面每题的右上角的数字表示难易度:1-简单,2-中等,3-困难

11.盛水最多的容器²

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。
在这里插入图片描述
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例:

输入:[1,8,6,2,5,4,8,3,7]
输出:49

解法一:枚举

  • 思路:枚举出左右两边的所有情况
  • 复杂度
    • Time:O(n^2),448ms
    • Space:O(1)
public int maxArea(int[] height) {
    
    
        int capcity = 0;
        for (int i = 0; i < height.length - 1; i++) {
    
    
            for (int j = i + 1; j < height.length; j++) {
    
    
                capcity = Math.max(capcity, Math.min(height[i],height[j]) * (j-i));
            }
        }
        return capcity;
}

解法二:双指针(夹逼)

  • 思路:双指针列举两边,高度小的一边向内移动

    ====>空间换时间(升维),将两重循环,变成一次循环+夹逼指针

  • 复杂度

    • Time:O(n), 4ms
    • Space:O(1)
public int maxArea(int[] height) {
    
    
        public int maxArea(int[] height) {
    
    
        int capcity = 0;
        // 双指针    
        int i = 0, j = height.length - 1;
        while (i < j) {
    
    
            capcity = Math.max(capcity, (j - i) * Math.min(height[i],height[j]));
            // i,j小的那个向中间移动
            if (height[i] <= height[j])
                i++;
            else 
                j--;
        }
        return capcity;
    }
}

代码优化:这种比较后要移动索引的,可以优化成条件表达式

public int maxArea(int[] height) {
    
    
     
        int capcity = 0;
        int i = 0, j = height.length - 1;
        while (i < j) {
    
    
            // 在为高度比较大小的时候就移动指针
            int minheigh = height[i] <= height[j] ? height[i++] : height[j--];
            // 注:这里要j - i + 1,因为已经向内移动了
            capcity = Math.max(capcity, (j - i + 1) * minheigh);
        }
        return capcity;
}

283.移动零¹(数组删除问题)

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

解法一:双指针(真假)

可以引申到数组一次删除多个元素,避免了多次数组copy

public void moveZeroes(int[] nums) {
    
    
        // 双指针,i记录下标++,j记录赋值下标
        int i , j;
        for (i = 0, j = 0; i < nums.length; i++) {
    
    
            if (nums[i] != 0) nums[j++] = nums[i];
        }
    	// 将剩余元素置0
        while (j < nums.length) {
    
    
            nums[j++] = 0;
        }
}

15. 三数之和²

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 *a,b,c ,*使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

解法一:循环枚举(超时)

  • 去重:数组排序 --> list.contains
  • 注意每次循环的边界条件:length - 2;length - 1;length
public List<List<Integer>> threeSum(int[] nums) {
    
    
        Arrays.sort(nums);
        List<List<Integer>> list = new ArrayList<>();
	    // 注意每次循环的结束条件
        for (int i = 0; i < nums.length - 2; i++) {
    
    
            for (int j = i + 1; j < nums.length - 1; j++) {
    
    
                for (int k = j + 1; k < nums.length; k++) {
    
    
                    if (nums[i] + nums[j] + nums[k] == 0) {
    
    
                        List addlist = Arrays.asList(nums[i],nums[j],nums[k]);
                        // 必须先排序,否则每次contains都是fasle
                        if (!list.contains(addlist))
                        list.add(addlist);
                    }
                }
            }
        }
        return list;
}

解法二:双指针(夹逼)

空间换时间(升维):一次循环+夹逼指针 = 两重循环

  • 排序,去重的基础
  • 循环取第一位,i < length - 2
    • 判断第一位是否重复
  • 双指针找二三位(代替了两重循环)
    • 找到了
      • 判断二三位的下一个是否重复
      • l++,r–
    • 小于sum,l++
    • 大于sum,r–;

优化:若第一位(nums[i]) 大于0,那么直接就不用找了,和肯定大于0了,break

public List<List<Integer>> threeSum(int[] nums) {
    
    
        if (nums.length < 3 || nums == null) return new ArrayList();
        Arrays.sort(nums);
        List<List<Integer>> list = new ArrayList<>();
        // 优化:若nums[i]>0,sum肯定大于0,不用找了
        for(int i = 0; i < nums.length - 2 && nums[i] <= 0; i++) {
    
    
            // 第一位去重
            if (i > 0 && nums[i]  == nums[i - 1]) continue;
            int sum = 0, left = i + 1, right = nums.length-1;
			
            while(left < right) {
    
    
                if ((sum = nums[i] + nums[left] + nums[right]) == 0) {
    
    
                    list.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    // 第二位去重,while可能后面有多个相同
                    while(left < right && nums[left] == nums[left + 1] ) left++;
                    // 第三位去重,left
                    while(left < right && nums[right] == nums[right - 1]) right--;
                   
                    left++;
                    right--;
                }else if (sum < 0) {
    
    
                    left++;
                }else {
    
    
                    right--;
                }
            }
        } 
        return list;
}

88. 合并两个有序数组¹

给你两个有序整数数组 nums1nums2,请你将 nums2 合并到 nums1 中*,*使 nums1 成为一个有序数组。

说明:

  • 初始化 nums1nums2 的元素数量分别为 mn
  • 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:

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

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

1.解法一:先合并再排序

  • 思路:先将nums2放入nums1,让后将nums1排序
  • 复杂度
    • Time:O(n*logn),基于sort
    • Space:O(1)
public void merge(int[] nums1, int m, int[] nums2, int n) {
    
    
        for (int i = 0; i < n; i++) {
    
    
            nums1[i + m] = nums2[i];
        }
        Arrays.sort(nums1);
    }

解法二:逆序插入

  • 思路:既然按序插入需要移动后面的元素,那么我可以倒着走啊,从后面向前插
  • 复杂度
    • Time:O(m + n)
    • Space:O(1)
public void merge(int[] nums1, int m, int[] nums2, int n) {
    
    
    	// -1因为这里是索引
        int i = m - 1, j = n - 1, k = m + n - 1;
        while (i >= 0 && j >= 0) {
    
    
            if (nums1[i] > nums2[j]) 
                nums1[k--] = nums1[i--];
            else 
                nums1[k--] = nums2[j--];
        }
		// 注:不用判断i>=0的情况,因为要放入nums1,且nums1已经是有序的
        while (j >= 0) nums1[k--] = nums2[j--];
    }

4. 两个正序数组的中位数³

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1nums2

请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1nums2 不会同时为空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0

示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

解法一:归并

  • 思路:将nums1和nums2合并到一个数组,采用归并排序里面的合并方法,然后返回即可
  • 复杂度
    • Time:O(m + n),但在LeetCode上运行时间与下一种无异
    • Space:O(m + n)
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    
    
        int total = nums1.length + nums2.length;
        int[] tmp = new int[total];
        int i = 0, j = 0, k = 0;
    	// 核心!
        while (i < nums1.length && j < nums2.length) 
            tmp[k++] = nums1[i] <= nums2[j] ? nums1[i++] : nums2[j++];
    	// 处理未放入的情况
        while (i < nums1.length) tmp[k++] = nums1[i++];
        while (j < nums2.length) tmp[k++] = nums2[j++];

        if ((total & 1) == 1) return tmp[total/2];
    	// 注:这里一定要转成double,因为默认int会舍去除的结果
        else return (double)(tmp[total/2 - 1] + tmp[total/2])/2;
    }

解法二:二分查找 ?

  • 复杂度
    • Time:O(log(m+n))
    • Space:O(1)
class Solution {
    
    
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    
    
        int total = nums1.length + nums2.length;
        if ((total & 1) == 1) {
    
    
            // 这里+1是表示要寻找第k小的的元素
            return findKthSmallestInSortedArrays(nums1, nums2, total/2 + 1);
        } else {
    
    
            double a = findKthSmallestInSortedArrays(nums1, nums2, total/2);
            double b = findKthSmallestInSortedArrays(nums1, nums2, total/2 + 1);
            return (a + b) / 2;
        }
    }

    // 寻找第K小的元素
    double findKthSmallestInSortedArrays(int[] nums1, int[] nums2, int k) {
    
    
        // len来表示结束条件, base记录基准索引
        int len1 = nums1.length, len2 = nums2.length, base1 = 0, base2 = 0;
        while (true) {
    
    
            // 结束条件:1.其中一个数组遍历完了  2.俩数组没走完,k走完了
            // 注:-1 是因为k表示的是第n小,这里要转换为索引
            if (len1 == 0) return nums2[base2 + k - 1];
            if (len2 == 0) return nums1[base1 + k - 1];
            if (k == 1) return Math.min(nums1[base1], nums2[base2]);

            // 寻找第k小:在nums1找k/2小, nums2找k-i小
            int i = Math.min(k/2, len1);
            int j = Math.min(k-i, len2);
            int a = nums1[base1 + i - 1];
            int b = nums2[base2 + j - 1];
		   // 找到k,且元素相等的情况
            if (i + j == k && a == b) return a;

            // 更新两数组中小的 base,len,k
            // 注:其中只有base是向后移动++,len和k都是--
            if (a <= b) {
    
    
                base1 += i;
                len1 -= i;
                k -= i;
            }
            if (a >= b) {
    
    
                base2 += j;
                len2 -= j;
                k -= j;
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_43935927/article/details/108650988
今日推荐