(LeetCode)刷题记录及题目理解3.20

284 阅读10分钟

刷题记录及题目理解

完成题目编号:167、215、347、75、455

一.题目编号:167

  1. 题目描述

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 说明:

返回的下标值(index1 和 index2)不是从零开始的。 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

输入: numbers = [2, 7, 11, 15], target = 9 输出: [1,2] 解释: 2 与 7 之和等于目标数 9。 因此 index1 = 1, index2 = 2 。

  1. 题目理解 升序排列:数组已经排序完成,从小到大 找到:只要找到其中两个数满足相加之和等于目标数即可 注意:不排除重复元素出现的情况;不排除数组中出现负数和零的情况

  2. 逻辑思路 题目是关于双指针算法的,本题中,数组已是升序排列的有序数组,一个和数等于两个数加和,其中一个数较小,另一个数较大,因此,可指定一个指针指向较小的数,指定另一个指针指向较大的数,当两数加和小于目标数时,移动较小数指针指向更大一点的数,让加和大一些;当两数加和大于目标数时,移动较大数指针指向更小一点的数,让加和小一些,直至两数加和等于目标数。

  3. 优解代码

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int[] indexArray = new int[2];
        int low = 0;
        int high = numbers.length - 1;
        while (low < high) {
            if (numbers[low] + numbers[high] > target) {
                high--;
            }
            else if (numbers[low] + numbers[high] < target) {
                low++;
            }
            else {
                indexArray [0] = low + 1;
                indexArray [1] = high + 1;
                break;
            }
        }
        return indexArray ;
    }
}
  1. 优解理解 图解缩减搜索空间 因为数组有序排列,首尾相加之后,加和相较于目标数有三种情况:大;小;相等。大了的话,减小大的加数(此时就缩减了考虑增大小的加数的情况),小了的话,增大小的加数(此时就缩减了考虑减小大的加数的情况)。每次将两数加和结果比较都会出现缩减一行或者一列的情况

  2. 问题代码

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        List arrayList = new ArrayList<>();
        List<Integer> indexArrayList = new ArrayList<Integer>();
        for(int i = 0 ; i < numbers.length; i++){
                arrayList.add(numbers[i]);
        }
        int arrayListLength = arrayList.size();
        int arrayListLastIndex = arrayListLength - 1;
        switch(arrayListLastIndex){
            case -1:
                System.out.println("不存在两个数相加等于目标数");
            break;
            case 0:
                System.out.println("不存在两个数相加等于目标数");
            break;
            default:
                for(int i = arrayListLastIndex ; i > 0; i--){
                    for(int j = 1; j <= i; j++){
                        if(numbers[i-j] == target - numbers[i]){
                            indexArrayList.add(i-j+1);
                            indexArrayList.add(i+1);
                        }
                    }
                }
                numbers = new int[2];
                if(indexArrayList.size() == 0){
                    System.out.println("不存在两个数相加等于目标数");
                }
                numbers[0] = indexArrayList.get(0);   
                numbers[1] = indexArrayList.get(1);
            break;
                
        }      
        return numbers;
    }
}
  1. 总结 不需要考虑不存在解的情况,在题目描述中的情况说明里已经声明了。以上暴力解题的想法,时间复杂度太高O(n^2^),相当于重复遍历数组每一个元素,嵌套的for循环,十分致命。

二、题目编号:215

  1. 题目描述

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

说明

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

示例1

输入: [3,2,1,5,6,4] 和 k = 2 输出: 5

示例2

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4

  1. 题目理解 要找数组的第k个最大元素,且不是第k个不同的元素,根据示例,可以了解到,如果出现重复元素,也要算在个数之内,不可以去掉重复元素再排序

  2. 逻辑思路 思路一:暴力解法 思路二:大顶堆、小顶堆法:java中的优先级序列,可以将存入其中的元素自动排序,大顶堆最先poll出来的是最大值,小顶堆最先poll出来的是最小值 思路三:快速选择法:不同于快速排序法快速排序在partition后需要对两个序列再进行排序,比较partition的元素的索引最终需要求解的元素索引的大小关系,决定对partition后哪一部分继续求解。

  3. 优解代码

//大顶堆法
class Solution {
	public int findKthLargest(int[] nums, int k) {
        //创建一个从大到小的大顶堆,lamada变达式:(a,b)->b-a 注:(a,b)->a-b代表从小到大的小顶堆   
        PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>((a,b)->b-a);
        for(int i =0;i<nums.length;i++){
            maxHeap.add(nums[i]);
        }
        for(int i=0;i<k-1;i++){
            maxHeap.poll();
        }
        return maxHeap.peek();
    }
}
//快速选择法
import java.util.Random;
class Solution {
    int [] nums;

    //交换a,b索引指向的元素
    public void swap(int a, int b) {
        int tmp = this.nums[a];
        this.nums[a] = this.nums[b];
        this.nums[b] = tmp;
    }

    //partition操作:将中心点移至最终位置,小于它的元素在左,大于它的元素在右
    //@param:左右边界索引以及选取中心点索引
    //@return :中心点最终索引位置
    public int partition(int left, int right, int pivot_index) {
        int pivot = this.nums[pivot_index];
        // 1. 将中心点移至最后位置
        swap(pivot_index, right);
        int store_index = left;

         // 2. 将小于中心点的元素移至左边
        for (int i = left; i <= right; i++) {
            if (this.nums[i] < pivot) {
                swap(store_index, i);
                store_index++;
            }
        }

         // 3. 将中心点移至它本应在的位置
         swap(store_index, right);

         return store_index;
    }
    //快速选择函数:用于partition后判断对哪边继续进行快速选择操作
    //@params:左右边界索引,和需要检索的索引
    //@return:返回所求值
    public int quickselect(int left, int right, int k_smallest) {
        
        //如果只有一个元素,返回当前值,题目假设k有效
        if (left == right) 
        return this.nums[left];  

        // 随机选择中心点索引,时间复杂度低
        Random random_num = new Random();
        int pivot_index = left + random_num.nextInt(right - left); 
        
        //该中心点最终的索引位置
        pivot_index = partition(left, right, pivot_index);

        // 如果该中心点最终索引等于我们需要求的数字的索引,返回索引指向值
        if (k_smallest == pivot_index)
        return this.nums[k_smallest];
        // 如果该中心点最终索引大于我们需要求的数字的索引,说明需要去左边寻找结果
        else if (k_smallest < pivot_index)
        return quickselect(left, pivot_index - 1, k_smallest);
        // 同上,去右边寻找结果
        return quickselect(pivot_index + 1, right, k_smallest);
    }

    public int findKthLargest(int[] nums, int k) {
        this.nums = nums;
        int size = nums.length;
        // 第k个最大值,相当于第n-k(排序完成后的索引)个最小值(n为数组长度)
        return quickselect(0, size - 1, size - k);
    }
}


  1. 优解理解 快速选择法需要先partition,再缩减范围,最后递归,直至求得结果。
  2. 问题代码
class Solution {
    public int findKthLargest(int[] nums, int k) {
        for(int i =0;i<nums.length-1;i++){
            for(int j = 0;j<nums.length-1-i;j++){
                if(nums[j] < nums[j+1]){
                   int temp = nums[j];
                   nums[j] = nums[j+1];
                   nums[j+1] = temp; 
                }
            }
        }
        return nums[k-1];
    }
}
  1. 总结

三、题目编号:347

  1. 题目描述

给定一个非空的整数数组,返回其中出现频率前 k 高的元素

说明

你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。

示例1

输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]

示例2

输入: nums = [1], k = 1 输出: [1]

  1. 题目理解 算法的时间复杂度有要求,不能单纯的依靠排序算法

  2. 逻辑思路 可以创建哈希表,记录键值及其出现的频次,最后通过对哈希表中的值堆排序得到频率前k高的元素(小顶堆法)。

  3. 优解代码

class Solution {
  public List<Integer> topKFrequent(int[] nums, int k) {
    // 创建hashMap,put(K,V)中的V 如果存在K为n,就取其V值再加1,否则,从1开始计数
    HashMap<Integer, Integer> elementCountMap = new HashMap();
    for (int n: nums) {
      elementCountMap.put(n, elementCountMap.getOrDefault(n, 0) + 1);
    }

    // 创建元素出现频率从小到大的小顶堆,往队列中传入的是K(n1,n2),但比较的是K的V(get(n1)-get(n2))
    PriorityQueue<Integer> heap =
            new PriorityQueue<Integer>((n1, n2) -> elementCountMap.get(n1) - elementCountMap.get(n2));

    // 遍历map中的全部K值,传入小顶堆,当传入的K的数目多于频率k时,把顶层的小概率K踢出
    for (int n: elementCountMap.keySet()) {
      heap.add(n);
      if (heap.size() > k)
        heap.poll();
    }

    // LinkedList在新增和删除对象(链表结构)的性能上优于ArrayList
    List<Integer> top_k = new LinkedList();
    while (!heap.isEmpty())
      top_k.add(heap.poll());
    Collections.reverse(top_k);
    return top_k;
  }
}
  1. 优解理解 HashMap双列集合对于统计,查找具有优势,上述代码,k越小,性能越优,对于k大的情况,可以用去除出现频率较低的数的思路(反向)。

  2. 问题代码思路 不会使用HashMap,对于堆的使用生疏。

  3. 总结 无话可说

四、题目编号:75

  1. 题目描述 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 注意: 不能使用代码库中的排序函数来解决这道题。

示例:

输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]

进阶

一个直观的解决方案是使用计数排序的两趟扫描算法。 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 你能想出一个仅使用常数空间的一趟扫描算法吗?

  1. 题目理解 因为元素只有0、1、2三种可能取值,且位置只有左、中、右三种选择,因此,将左、右两个边界的元素排号之后,其余的元素也正确了
  2. 逻辑思路 三指针方法,设定左右两边界索引,判断当前索引所指数字是否需要和左或右边界进行交换
  3. 优解代码
class Solution {
    public void sortColors(int[] nums) {
        int head = 0;
        int end = nums.length-1;
        int current = 0;
        int temp = 0;

        while(current <= end){
            if(nums[current]==0){
                nums[current] = nums[head];
                nums[head] = 0;
                head++;
                current++;
            }else if(nums[current]==2){
                nums[current] = nums[end];
                nums[end]=2;
                end--;
            }else{
                current++;
            }
        }
        
    }    
}
  1. 优解理解 上述代码,扫描左边时,与==最小值交换完需要current++==;扫描右边时,与最大值交换完之后,需要判断一下用于交换的值是否是最小的值,因此没有current自加语句。

  2. 总结 冒泡排序不让用。

五、题目编号:455

  1. 题目描述 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 注意

你可以假设胃口值为正。 一个小朋友最多只能拥有一块饼干。

示例 1:

输入: [1,2,3], [1,1]

输出: 1

解释:

你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 所以你应该输出1。

示例 2:

输入: [1,2], [1,2,3]

输出: 2

解释:

你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 你拥有的饼干数量和尺寸都足以让所有孩子满足。 所以你应该输出2。

  1. 题目理解 尽可能多的满足孩子,只要用最小的代价满足最大的胃口即可。
  2. 逻辑思路 贪心算法:无所谓其他解,只要局部解最优即可,因为可以对孩子胃口和饼干尺寸排序,之后比较从小到大,两相比较即可。
  3. 优解代码
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int ghead =0;
        int shead =0;
        int count =0;
        while(ghead<g.length&&shead<s.length){
            if(g[ghead]<=s[shead]){
                ghead++;
                shead++;
                count++;
            }else {
                shead++;
            }
        }
        return count;
    }
}
  1. 优解理解

需要先使用排序函数进行从小到大的排序。

  1. 总结