刷题记录及题目理解
完成题目编号:167、215、347、75、455
一.题目编号:167
- 题目描述
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 说明:
返回的下标值(index1 和 index2)不是从零开始的。 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9 输出: [1,2] 解释: 2 与 7 之和等于目标数 9。 因此 index1 = 1, index2 = 2 。
-
题目理解 升序排列:数组已经排序完成,从小到大 找到:只要找到其中两个数满足相加之和等于目标数即可 注意:不排除重复元素出现的情况;不排除数组中出现负数和零的情况
-
逻辑思路 题目是关于双指针算法的,本题中,数组已是升序排列的有序数组,一个和数等于两个数加和,其中一个数较小,另一个数较大,因此,可指定一个指针指向较小的数,指定另一个指针指向较大的数,当两数加和小于目标数时,移动较小数指针指向更大一点的数,让加和大一些;当两数加和大于目标数时,移动较大数指针指向更小一点的数,让加和小一些,直至两数加和等于目标数。
-
优解代码
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 ;
}
}
-
优解理解 图解缩减搜索空间 因为数组有序排列,首尾相加之后,加和相较于目标数有三种情况:大;小;相等。大了的话,减小大的加数(此时就缩减了考虑增大小的加数的情况),小了的话,增大小的加数(此时就缩减了考虑减小大的加数的情况)。每次将两数加和与结果比较都会出现缩减一行或者一列的情况
-
问题代码
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;
}
}
- 总结 不需要考虑不存在解的情况,在题目描述中的情况说明里已经声明了。以上暴力解题的想法,时间复杂度太高O(n^2^),相当于重复遍历数组每一个元素,嵌套的for循环,十分致命。
二、题目编号:215
- 题目描述
在未排序的数组中找到第 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
-
题目理解 要找数组的第k个最大元素,且不是第k个不同的元素,根据示例,可以了解到,如果出现重复元素,也要算在个数之内,不可以去掉重复元素再排序
-
逻辑思路 思路一:暴力解法 思路二:大顶堆、小顶堆法:java中的优先级序列,可以将存入其中的元素自动排序,大顶堆最先poll出来的是最大值,小顶堆最先poll出来的是最小值 思路三:快速选择法:不同于快速排序法,快速排序在partition后需要对两个序列再进行排序,比较partition的元素的索引和最终需要求解的元素索引的大小关系,决定对partition后哪一部分继续求解。
-
优解代码
//大顶堆法
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);
}
}
- 优解理解 快速选择法需要先partition,再缩减范围,最后递归,直至求得结果。
- 问题代码
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];
}
}
- 总结
三、题目编号:347
- 题目描述
给定一个非空的整数数组,返回其中出现频率前 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]
-
题目理解 算法的时间复杂度有要求,不能单纯的依靠排序算法
-
逻辑思路 可以创建哈希表,记录键值及其出现的频次,最后通过对哈希表中的值堆排序得到频率前k高的元素(小顶堆法)。
-
优解代码
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;
}
}
-
优解理解 HashMap双列集合对于统计,查找具有优势,上述代码,k越小,性能越优,对于k大的情况,可以用去除出现频率较低的数的思路(反向)。
-
问题代码思路 不会使用HashMap,对于堆的使用生疏。
-
总结 无话可说
四、题目编号:75
- 题目描述 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 注意: 不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 你能想出一个仅使用常数空间的一趟扫描算法吗?
- 题目理解 因为元素只有0、1、2三种可能取值,且位置只有左、中、右三种选择,因此,将左、右两个边界的元素排号之后,其余的元素也正确了
- 逻辑思路 三指针方法,设定左右两边界索引,判断当前索引所指数字是否需要和左或右边界进行交换
- 优解代码
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++;
}
}
}
}
-
优解理解 上述代码,扫描左边时,与==最小值交换完需要current++==;扫描右边时,与最大值交换完之后,需要判断一下用于交换的值是否是最小的值,因此没有current自加语句。
-
总结 冒泡排序不让用。
五、题目编号:455
- 题目描述 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 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。
- 题目理解 尽可能多的满足孩子,只要用最小的代价满足最大的胃口即可。
- 逻辑思路 贪心算法:无所谓其他解,只要局部解最优即可,因为可以对孩子胃口和饼干尺寸排序,之后比较从小到大,两相比较即可。
- 优解代码
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;
}
}
- 优解理解
需要先使用排序函数进行从小到大的排序。
- 总结