本期讲O(n)类型问题,共14题。3道简单题,9道中等题,2道困难题。数组篇共归纳总结了50题,本篇是数组篇的最后一篇。其他三个篇章可参考:

本系列50道题是作者在LeetCode题库数组标签中包含的202道题中,按照解答考点分类归纳总结的题型。解法仅供参考,主要在于题目和考点的分类。希望对准备刷LeetCode,而感觉题目繁多、标签太多、时间较少,不知道从何开始刷题的同学一点小小的帮助^~^,也是自己后期二刷的资料吧(PS:如果有时间的话)。

O(n)类型问题,是指要求算法的时间复杂度为O(n)。这类题目的特点是题意一般比较容易理解,而且其暴力求解的方案也比较容易想到。但是,题目确要求你不能采用暴力法求解,这往往是考察我们对双指针、快慢指针、动态规划、哈希数组和特定数学思想的应用。

在双指针方面,一般基础的策略是采用空间换取时间的策略。即先采用一个数组从原数组右边开始遍历,保存当前更新的临时变量。最后,从数组的左边开始依次遍历,不断更新最终的结果。此思路的应用,可以参考例9、例10和例11。

另外,双指针的应用解法也可以在O(1)的空间复杂度里面实现,采用一个临时变量随着遍历不断更新当前状态,夹杂着动态规划的思想。这类考点的应用,可以参考例5和例12。

在数学思维考察方面,组合数学的知识应用也是比较常见。比如考察对组合数学中字典序求解的应用,可以参考例1。数学中正负数转换为数组下标的思想,可以参考例2、例6。快速找到当前示例的数学规律,归纳出递推公式,可以参考例8、例13。

例3是一道非常经典的面试题,题目有多种解法,本文中给出是采用三次翻转求得最终结果的解法。在矩阵应用中,利用翻转操作一般也可以取得令人惊奇的效果。活用翻转也是一种技巧。

例4则是让人感叹的解法。采用摩尔投票法寻找数组中最多的元素。该思维应该可以归纳为寻找最多元素的一种特解思路。

在数组哈希思路的应用方面,可以参考例7和例14,是很典型的以空间换取时间的例题。

例1 下一个排列

题号:31,难度:中等

题目描述:

解题思路:

本题需要注意的关键点:原地修改,字典序。此题的解答用到了组合数学的知识,寻找比当前序列大的最小字典序。即从该序列尾部开始遍历,直到当前元素(假设位置为i)比该元素前面的元素大的时候停止。然后从i道最后一个元素序列中找到比第i-1个元素大的最小元素进行交换,最后把最后i个元素从小到大排序即可。

具体代码:

class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
while(i >= 0 && nums[i] >= nums[i+1])
i--;
if(i >= 0) {
int j = nums.length - 1;
while(nums[j] <= nums[i])
j--;
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
reverse(nums, i+1, nums.length - 1);
} else {
reverse(nums, 0, nums.length-1);
}
} public void reverse(int[] nums, int i, int j) {
while(i < j) {
int temp = nums[i];
nums[i++] = nums[j];
nums[j--] = temp;
}
}
}

执行结果:

例2 缺失的第一个正数

题号:41,难度:困难

题目描述:

解题思路:

此题虽然被划分为困难题,实际上比较简单。题目要求是没有出现的最小正整数,那么返回的结果最大值只能是数组长度加1,最小值是1。那么只需要利用原有数组,把其中小于等于0的数字标记为大于数组长度*2的元素,剩下的把在1到数组长度之间的元素采用数组的下标元素取负数表示。最后,从数组第一个元素开始遍历,一旦出现大于0的元素,那么该元素下标即为最终结果。

具体代码:

class Solution {

    public int firstMissingPositive(int[] nums) {
int len = nums.length * 2;
for(int i = 0;i < nums.length;i++) {
if(nums[i] <= 0)
nums[i] = len++;
} int j = 0;
for(;j < nums.length;j++) {
if(Math.abs(nums[j]) <= nums.length && nums[Math.abs(nums[j]) - 1] > 0)
nums[Math.abs(nums[j]) - 1] *= -1;
}
for(j = 0;j < nums.length;j++) {
if(nums[j] > 0)
break;
} return j + 1;
} }

执行结果:

例3 旋转数组

题号:189,难度:简单

题目描述:

解题思路:

采用三次翻转操作。第一次将整个数组翻转一次,第二次将要右移的前K个元素翻转一次,第三次将剩余的k-n-1个元素翻转一次。最终得到的结构即为目标值。

具体代码:

class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n;
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, n - 1);
} private void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start++] = nums[end];
nums[end--] = temp;
}
}
}

执行结果:

例4 求众数II

题号:229,难度:中等

题目描述:

解题思路:

采用摩尔投票法,具体就是遇到相等的数,统计该数的个数自动加1,否则自动减一,一旦减到0后,更换当前存储的数字。摩尔投票法首次运用的题是求一维数组中数目超过一半的数(具体可参考题目:求众数, 题号169, 难度:简单)。本题稍作变换即可,开启两个变量计数和存储当前的数。开启两个数的数学意义在于,一个数组最多只能有两个数超过数组的三分之一。

具体代码:

class Solution {

    public List<Integer> majorityElement(int[] nums) {
List<Integer> result = new ArrayList<>(); //摩尔投票法
int count1 = 0, temp1 = 0;
int count2 = 0, temp2 = 0;
for(int i = 0;i < nums.length;i++) {
if((count1 == 0 || temp1 == nums[i]) && temp2 != nums[i]) {
count1++;
temp1 = nums[i];
} else if(count2 == 0 || temp2 == nums[i]) {
count2++;
temp2 = nums[i];
} else{
count1--;
count2--;
}
} count1 = 0;
count2 = 0;
for(int i = 0;i < nums.length;i++) {
if(nums[i] == temp1)
count1++;
else if(nums[i] == temp2)
count2++;
}
if(count1 > nums.length / 3)
result.add(temp1);
if(temp1 != temp2 && count2 > nums.length / 3)
result.add(temp2); return result;
} }

执行结果:

例5 除自身以外数组的乘积

题号:238,难度:中等

题目描述:

解题思路:

以空间换时间的策略,从数组左边依次遍历,保存连续乘积;然后,从数组右边依次遍历,保存连续乘积。最后,从数组第一数字开始遍历,取该数组左边的连续乘积和右边的连续乘积相乘即可。时间复杂度为O(n),空间复杂度为O(n)。

进阶如何使得空间复杂度为O(1)呢?即采用常数空间保存左边连续乘积,和右边连续乘积即可。这里感觉采用了动态规划的思路来临时保存左右连续乘积。

具体代码:

class Solution {

    public int[] productExceptSelf(int[] nums) {
int[] result = new int[nums.length];
int left = 1;
int right = 1; for(int i = 0;i < nums.length;i++) {
result[i] = left;
left *= nums[i];
} for(int i = nums.length - 1;i >= 0;i--) {
result[i] *= right;
right *= nums[i];
} return result;
}
}

执行结果:

例6 数组中重复的数据

题号:442,难度:中等

题目描述:

解题思路:

此题采用数组下标来判定出现两次的元素。题目中表明1 <= a[i] <= n,那么出现两次的元素i,对应下标i -1,出现一次时使得a[i - 1] * -1,当再次出现a[i - 1]小于零时,那么i就出现了两次。

具体代码:

class Solution {

    public List<Integer> findDuplicates(int[] nums) {
List<Integer> result = new ArrayList<>();
for(int i = 0;i < nums.length;i++) {
int j = Math.abs(nums[i]) - 1;
if(nums[j] < 0)
result.add(j + 1);
else
nums[j] *= -1;
} return result;
}
}

执行结果:

例7 数组拆分I

题号:561,难度:简单

题目描述:

解题思路:

题目中有说明元素的范围,且比较小。观察示例的数据发现,只需要对数据进行从小到大排序,依次选取两个元素中第一个元素作为最终结果的一部分即可。此时,可以采取数据哈希的思路来完成数据的排序操作,时间复杂度为O(n)。

具体代码:

class Solution {

    public int arrayPairSum(int[] nums) {
int[] temp = new int[20001];
for(int i = 0;i < nums.length;i++) {
int j = nums[i] + 10000;
temp[j]++;
} int result = 0;
for(int i = 0;i < temp.length;i++) {
if(temp[i] > 0) {
int a = i - 10000;
temp[i]--;
while(i < temp.length && temp[i] <= 0) {
i++;
}
// if(i < temp.length)
// System.out.println("i = "+i+", temp[i] = "+temp[i]);
if(i < temp.length) {
result += a;
temp[i]--;
}
if(temp[i] > 0)
i--;
}
} return result;
}
}

执行结果:

例8 优美的排列II

题号:667,难度:中等

题目描述:

解题思路:

此题考察我们寻找数学规律。先从1到k存储每个元素,然后从k+1开始每两个数存储(n--, k++)即可。

具体代码:

class Solution {

    public int[] constructArray(int n, int k) {
int[] result = new int[n];
int temp = 1;
for(int i = 0;i < n - k;i++)
result[i] = temp++;
int count = n;
boolean judge = true;
for(int i = n - k;i < n;i++) {
if(judge) {
result[i] = count--;
judge = false;
} else {
result[i] = temp++;
judge = true;
}
} return result;
}

执行结果:

例9 最多能完成排序的块 II

题号:768,难度:困难

题目描述:

解题思路:

此题考察双指针和动态规划思想的应用。双指针,从右边依次遍历存储当前的最小值。从左边开始依次遍历,存储当前的最大值。如果左边当前的最大值小于等于右边的最小值,则可以分割为一个块。

具体代码:

class Solution {

    public int maxChunksToSorted(int[] arr) {
int[] right_min = new int[arr.length];
for(int i = arr.length - 1;i >= 0;i--) {
if(i == arr.length - 1)
right_min[i] = arr[i];
else
right_min[i] = Math.min(arr[i], right_min[i+1]);
} int result = 1;
int left_max = 0;
for(int i = 0;i < arr.length - 1;i++) {
if(arr[left_max] <= right_min[i + 1]) {
result++;
left_max = i + 1;
} else {
if(arr[left_max] < arr[i])
left_max = i;
}
} return result;
}

执行结果:

例10 最佳观光组合

题号:1014,难度:中等

题目描述:

解题思路:

此题是一个双指针和动态规划思想的应用。可以把得分拆为两个部分,左边遍历,寻找max(A[i] + i);右边遍历,寻找max(A[j] - j)。可以采用一个数组保存右边最大值,让后从左边开始遍历,不断更新最终的最大值。

具体代码:

class Solution {

    public int maxScoreSightseeingPair(int[] A) {
int[] right_max = new int[A.length];
for(int i = A.length - 1;i >= 0;i--) {
if(i == A.length - 1)
right_max[i] = A[i] - i;
else
right_max[i] = Math.max(A[i] - i, right_max[i+1]);
}
int result = 0;
for(int i = 0;i < A.length - 1;i++)
result = Math.max(result, A[i] + i + right_max[i+1]); return result;
}
}

执行结果:

例11 到最近的人的最大距离

题号:849,难度:简单

题目描述:

解题思路:

此题考察我们双指针的思想应用。可以采用一个指针从右边依次遍历,存储到当前元素的连续零的个数(此处需要注意尾部全为零的特殊情况)。然后,从左边开始遍历,计算左边连续零的个数,最后比较左边和右边零个数的大小即可。

具体代码:

class Solution {

    public int maxDistToClosest(int[] seats) {
int[] right = new int[seats.length];
for(int i = seats.length - 1;i >= 0;i--) {
if(i == seats.length - 1) {
right[i] = seats[i] == 1 ? 0 : 1;
} else if(seats[i] == 0){
right[i] = 1 + right[i+1];
}
}
int result = 0, left = seats.length;
for(int i = 0;i < seats.length;i++) {
// System.out.println("i = "+i+", left = "+left+", right = "+right[i]+", result = "+result);
if(seats[i] == 1)
left = 1;
else {
int temp = left;
if(right[i] < left && right[i] + i < seats.length)
temp = right[i];
result = Math.max(result, temp);
left++;
}
} return result;
}

执行结果:

例12 分割数组

题号:915,难度:中等

题目描述:

解题思路:

此题同样是双指针思路的应用,但是可采用当前最大值和左数组最大值的思想来做。

具体代码:

class Solution {
public int partitionDisjoint(int[] A) {
int[] right_min = new int[A.length];
for(int i = A.length - 1;i >= 0;i--) {
if(i == A.length - 1)
right_min[i] = A[i];
else
right_min[i] = Math.min(A[i], right_min[i+1]);
}
int result = 0, left_max = A[0];
for(;result < A.length - 1;result++) {
left_max = Math.max(A[result], left_max);
if(left_max <= right_min[result + 1])
break;
} return result + 1;
} /* 当前最大值和左边最大值
public int partitionDisjoint(int[] A) {
if (A == null || A.length == 0) {
return 0;
} int leftMax = A[0];
int max = A[0];
int index = 0; for (int i = 0; i < A.length; i++) {
max = Math.max(max, A[i]);
if(A[i] < leftMax) {
leftMax = max;
index = i;
}
} return index + 1;
} */
}

执行结果:

例13 将字符串翻转到单调递增

题号:926, 难度:中等

题目描述:

解题思路:

此题考察我们的数学思维。统计从左到右遍历时0的个数和1的个数,一旦零的个数大于1,结果自动增加1的个数,同时把0和1的个数置零,从新开始统计。

具体代码:

class Solution {
/*
* 某一位为1时,前面一位是0或者1都可以
* 某一位为0时,前面一位只能为0
*/
public int minFlipsMonoIncr(String S) {
int zero = 0, one = 0;
int result = 0;
for(char s: S.toCharArray()){
if(s == '0')
zero++;
else
one++;
if(zero > one) {
result += one;
zero = 0;
one = 0;
}
}
result += zero; return result;
}
}

执行结果:

例14 使数组唯一的最小增量

题号:945,难度:中等

题目描述:

 

解题思路:

此题提示说明,0 <= A[i] < 40000。可知可以采用数组哈希的思想来求解本题,以空间换时间的思想,最终的时间复杂度为O(n)。

具体代码:

class Solution {

    public int minIncrementForUnique(int[] A) {
int[] value_A = new int[41000];
for(Integer a: A)
value_A[a]++;
int result = 0;
for(int i = 0;i < A.length;i++) {
if(value_A[A[i]] == 1)
continue;
int temp = A[i];
int count = 0;
while(value_A[temp] > 1) {
value_A[temp]--;
while(value_A[A[i]] > 0) {
count++;
A[i]++;
}
value_A[A[i]]++;
result += count;
}
} return result;
}
}

执行结果:

LeetCode刷题总结-数组篇(下)的更多相关文章

  1. LeetCode刷题总结-数组篇(中)

    本文接着上一篇文章<LeetCode刷题总结-数组篇(上)>,继续讲第二个常考问题:矩阵问题. 矩阵也可以称为二维数组.在LeetCode相关习题中,作者总结发现主要考点有:矩阵元素的遍历 ...

  2. LeetCode刷题总结-数组篇(上)

    数组是算法中最常用的一种数据结构,也是面试中最常考的考点.在LeetCode题库中,标记为数组类型的习题到目前为止,已累计到了202题.然而,这202道习题并不是每道题只标记为数组一个考点,大部分习题 ...

  3. LeetCode刷题总结-数组篇(番外)

    本期共7道题,三道简单题,四道中等题. 此部分题目是作者认为有价值去做的一些题,但是其考察的知识点不在前三篇总结系列里面. 例1解法:采用数组索引位置排序的思想. 例2解法:考察了组合数学的组合公式应 ...

  4. leetcode 刷题(数组篇)26题 删除有序数组中的重复值 (双指针)

    题目描述 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度. 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额 ...

  5. leetcode 刷题(数组篇)15题 三数之和 (双指针)

    很有意思的一道题,值得好好思考,虽然难度只有Mid,但是个人觉得不比Hard简单 题目描述 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b ...

  6. leetcode 刷题(数组篇)11题 盛最多水的容器(双指针)

    题目描述 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) .找出其 ...

  7. leetcode 刷题(数组篇)4题 寻找两个正序数组的中位数(二分查找)

    题目描述 给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2.请你找出并返回这两个正序数组的 中位数 . 示例 1: 输入:nums1 = [1,3], nums2 = ...

  8. leetcode 刷题(数组篇)1题 两数之和(哈希表)

    题目描述 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数组中同一个元 ...

  9. leetcode 刷题(数组篇)152题 乘积最大子数组 (动态规划)

    题目描述 给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积. 示例 1: 输入: [2,3,-2,4] 输出: 6 解释: 子 ...

随机推荐

  1. JavaSE----01.Java简介

    01.Java简介 1.java介绍     Java是于1995年由Sun公司推出的一种跨平台.面向对象的高级程序设计语言.Java最初的名字叫OAK.Java是一种通过解释方式来执行的语言,其语法 ...

  2. (八十四)c#Winform自定义控件-导航菜单(类Office菜单)

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kwwwvagaa/NetWinformControl 码云:ht ...

  3. typedef说明

    typedef 只对已有的类型进行别名定义,不产生新的类型: #define 只是在预处理过程对代码进行简单的替换. 类比理解: typedef  unsigned int  UINT32;  // ...

  4. Unity动态改变物体遮挡关系

    在动态创建物体时,通常同父级下先创建的子物体会被后创建的遮挡,此时就需要我们用代码改变对象的层级. GameObject go;go.transform.SetAsLastSibling();//设置 ...

  5. KEIL软件中编译时出现的Error L6200E: symbol multiply defined ...的解决方法

    原因:如LCD.C文件使用了bmp.h中的image[ ]变量,那么就不能将#include"bmp.h"放在LCD.H中,要将#include"bmp.h"放 ...

  6. 一份超级完整的PyCharm图解教程

    微信搜索公众号:Python极客社区. 每天分享不一样的Python干货 PyCharm 是一种 Python IDE,可以帮助程序员节约时间,提高生产效率.那么具体如何使用呢?本文从 PyCharm ...

  7. PHP中sha1()函数和md5()函数的绕过

    相信大家都知道,sha1函数和md5都是哈希编码的一种,在PHP中,这两种编码是存在绕过漏洞的. PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值 ...

  8. maven在线自动更新太慢怎么办?

    使用IDEA和Eclipse开发maven项目时,maven在添加一项新的依赖时,如果发现本地仓库没有,就会向位于国外服务器的中央仓库下载.如果所处网络没有翻墙,下载速度会慢到你想原地爆炸. 这个时候 ...

  9. Python开发【第七篇】列表

    问题:当我们要用一系列数字的时候,我们需要将数字进行存储,我们就需要找个容器把数字装起来,我们需要用的时候再拿出来.如何将计算机运算的数据存储在一个地方,同时方便 对数据进行 增.删.改.查 列表 列 ...

  10. MySQL 日志系统之 redo log 和 binlog

    之前我们了解了一条查询语句的执行流程,并介绍了执行过程中涉及的处理模块.一条查询语句的执行过程一般是经过连接器.分析器.优化器.执行器等功能模块,最后到达存储引擎. 那么,一条 SQL 更新语句的执行 ...