1 简介
是不是有许多小伙伴在刷力扣的时候感觉无从下手?从头按顺序开始刷的童鞋们可能会比较有感触,为什么才第四题就感觉很难了?没关系,本文将对力扣的 1-500 题中不需要会员的数据结构与算法题目(数据库与 shell 除外)进行分类,并推荐一个刷题的顺序。
完全零基础可以刷题吗?
不能,至少要基本掌握一门计算机语言的语法。但现在在网上随便搜一下就能搜到许多关于计算机语言的教程。当然,最好还是上一下正规的课程。
刷题顺序很重要吗?
重要。按照题目类别结构化地刷题的速度不仅更快,而且可以在刷完一类题之后进行总结。对于水平较高的小伙伴们来说,按照推荐的顺序刷,可以在 200 小时内刷完 500 多题。对于萌新们来说,按照推荐顺序刷,能更好地掌握数据结构与算法基础。
此篇文章是小编整理和学习的过程和心得、包含了小编刷题的心得与解法(主要使用JAVA语言来解题),此后小编会继续分享后续的刷题的一些详解和如何进行有效的刷题。
2 刷题顺序
2.1 数组篇
|------------|------------------------|
| 题目分类 | 题目编号 |
| 数组遍历 | 485、495、414、628 |
| 统计数组的元素 | 645、697、448、442、41、273 |
| 数组的改变移动 | 453、665、283 |
| 二维数组及滚动数组 | 118、119 |
| 数组的旋转 | 189、396 |
| 特定顺序遍历二维数组 | 54、59 |
| 二维数组变换 | 566、48、73、289 |
| 前缀和数组 | 303、304、238 |
2.2 数组的理论基础
数组是非常基础的数据结构,在面试中,考察数组的题目一般在思维上都不难,主要是考察对代码的掌控能力
也就是说,想法很简单,但实现起来 可能就不是那么回事了。
首先要知道数组在内存中的存储方式,这样才能真正理解数组相关的面试题
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便的通过下标索引的方式获取到下标对应的数据。
举一个字符数组的例子,如图所示:
有亮点需要注意:
- 数组共有三个部分组成,地址+值+数组下
- 其中我们可以看见数组的的下标都是从0开始计算的,所以数组的下标一般都是数组长度 - i(代表第几个数组) ,并且数组内容都是连续的。
正是因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
例如删除下标为3的元素,需要对下标为3的元素后面的所有元素都要做移动操作,如图所示:
然后二维数组其实和一维数组其实是一样的概念,只不过由X Y来组成数组元素,如图:
注意:
- 二维数组在空间上也是连续的
- 二维数组其实就是m*n的数组
2.3 数组遍历
2.3.1 最大连续1的个数(485)
题目:给定一个二进制数组 nums
, 计算其中最大连续 1
的个数。
示例 1:
输入:nums = [1,1,0,1,1,1]
输出:3
解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.
示例 2:
输入:nums = [1,0,1,1,0,1]
输出:2
详解:我们只需要遍历数组,对数组中的元素进行判断、设置一个标记为、只要等于1就要标志位+1,最后输出最大连续数
题解:
class Solution {
public int findMaxConsecutiveOnes(int[] nums) {
int maxCount = 0;
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == 1) {
count++;
if (count > maxCount) {
maxCount = count;
}
} else {
count = 0;
}
}
return maxCount;
}
}
提示:
1 <= nums.length <= 105
nums[i]
不是0
就是1
.
2.3.2 提莫攻击 (495)
题目:
在《英雄联盟》的世界中,有一个叫 "提莫" 的英雄。他的攻击可以让敌方英雄艾希(编者注:寒冰射手)进入中毒状态。
当提莫攻击艾希,艾希的中毒状态正好持续 duration
秒。
正式地讲,提莫在 t
发起攻击意味着艾希在时间区间 [t, t + duration - 1]
(含 t
和 t + duration - 1
)处于中毒状态。如果提莫在中毒影响结束 前 再次攻击,中毒状态计时器将会 重置 ,在新的攻击之后,中毒影响将会在 duration
秒后结束。
给你一个 非递减 的整数数组 timeSeries
,其中 timeSeries[i]
表示提莫在 timeSeries[i]
秒时对艾希发起攻击,以及一个表示中毒持续时间的整数 duration
。
返回艾希处于中毒状态的 总 秒数。
示例 1:
输入:timeSeries = [1,4], duration = 2
输出:4
解释:提莫攻击对艾希的影响如下:
- 第 1 秒,提莫攻击艾希并使其立即中毒。中毒状态会维持 2 秒,即第 1 秒和第 2 秒。
- 第 4 秒,提莫再次攻击艾希,艾希中毒状态又持续 2 秒,即第 4 秒和第 5 秒。
艾希在第 1、2、4、5 秒处于中毒状态,所以总中毒秒数是 4 。
示例 2:
输入:timeSeries = [1,2], duration = 2
输出:3
解释:提莫攻击对艾希的影响如下:
- 第 1 秒,提莫攻击艾希并使其立即中毒。中毒状态会维持 2 秒,即第 1 秒和第 2 秒。
- 第 2 秒,提莫再次攻击艾希,并重置中毒计时器,艾希中毒状态需要持续 2 秒,即第 2 秒和第 3 秒。
艾希在第 1、2、3 秒处于中毒状态,所以总中毒秒数是 3 。
提示:
1 <= timeSeries.length <= 104
0 <= timeSeries[i], duration <= 107
timeSeries
按 非递减 顺序排列
详解:这一题需要先判断第 i 个元素和 i - 1 个元素之间的差值是否大于duration,如果差值比duration大,直接加上duration、如果没有那么就要加上的是元素之间的差值,图解
题解:
class Solution {
public int findPoisonedDuration(int[] timeSeries, int duration) {
int totalDuration = 0;
for (int i = 1; i < timeSeries.length; i++) {
int interval = timeSeries[i] - timeSeries[i - 1];
if (interval >= duration) {
totalDuration += duration;
} else {
totalDuration += interval;
}
}
// 加上最后一次攻击的持续时间
totalDuration += duration;
return totalDuration;
}
}
2.3.3 第三大的数(414)
题目:给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。
示例 1:
输入:[3, 2, 1]
输出:1
解释:第三大的数是 1 。
示例 2:
输入:[1, 2]
输出:2
解释:第三大的数不存在, 所以返回最大的数 2 。
示例 3:
输入:[2, 2, 3, 1]
输出:1
解释:注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。
此例中存在两个值为 2 的数,它们都排第二。在所有不同数字中排第三大的数为 1
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
详解;我们可以使用JAVA里的一种数据结构TreeSet(自然排序集合),遍历数组,只保留三个元素在Treeset集合了,超过三个就剔除最小那个,最后只需要输出TreeSet最小的元素就是第三大元素
题解:;
class Solution {
public int thirdMax(int[] nums) {
TreeSet<Integer> set = new TreeSet<>();
for(int num : nums) {
set.add(num);
if(set.size() > 3){
set.pollFirst(); // 移除最小的元素以保持集合的大小为3
}
}
return set.size() == 3 ? set.first() : set.last(); //第三大数不存在 返回最大的数
}
}
2.3.4 三个数最大乘积 (628)
题目:
代码
测试用例
测试结果
测试结果
已解答
简单
相关标签
相关企业
给你一个整型数组 nums
,在数组中找出由三个数组成的最大乘积,并输出这个乘积。
示例 1:
输入:nums = [1,2,3]
输出:6
示例 2:
输入:nums = [1,2,3,4]
输出:24
示例 3:
输入:nums = [-1,-2,-3]
输出:-6
提示:
3 <= nums.length <= 104
-1000 <= nums[i] <= 1000
详解:首先要求三个数的最大乘积,我们要先给数组排序(JAVA里面的 Arrays.sort方法就可以排序 O(n)=nlog2n),这样才能更好找出最大的数来,然后是有两种情况
三个最大的正数
两个最小负数和一个最大正数
最后比较两个数 看一看是哪个最大就是最大的三个数乘积(Math.max)(a,b))
题解:
class Solution {
public int maximumProduct(int[] nums) {
// 利用Arrays给数组做自然排序
Arrays.sort(nums);
int n = nums.length;
//情况一 两个最小负数和最大数
int multiplyMax1 = nums[0] * nums[1] * nums[n - 1] ;
//情况二 最大三个正数
int multiplyMax2 = nums[n - 1] * nums[n - 2] * nums[n - 3];
return Math.max(multiplyMax1, multiplyMax2);
}
}
2.4 统计数组的元素
2.4.1 错误的集合 (645)
题目:
集合 s
包含从 1
到 n
的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另外一个数字的值,导致集合 丢失了一个数字 并且 有一个数字重复 。
给定一个数组 nums
代表了集合 S
发生错误后的结果。
请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。
示例 1:
输入:nums = [1,2,2,4]
输出:[2,3]
示例 2:
输入:nums = [1,1]
输出:[1,2]
提示:
2 <= nums.length <= 104
1 <= nums[i] <= 104
详解:因为数组是[1,n]的数组,要找出重复的数字和缺失的数,首先重复的数好找,我们先给数组进行排序(Arrays.srot()),然后遍历找出重复的那个元素。对于缺失的元素,我们可以用一个boolean数组来遍历,给[1,n]的数组每个位置都标记为True,然后再次遍历原数组,如果booelan[i]=false,i就是那个丢失的元素
题解:
class Solution {
public int[] findErrorNums(int[] nums) {
int repeat = 0;
int lost = 0;
Arrays.sort(nums);
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) {
repeat = nums[i];
}
// 检查从1到n的每一个数字
boolean[] seen = new boolean[nums.length + 1];
for (int num : nums) {
seen[num] = true;
}
for (int j = 1; j <= nums.length; j++) {
if (!seen[j]) {
lost = j;
break;
}
}
}
return new int[] { repeat, lost };
}
}
2.4.2 数组的度(697)
题目:
给定一个非空且只包含非负数的整数数组 nums
,数组的 度 的定义是指数组里任一元素出现频数的最大值。
你的任务是在 nums
中找到与 nums
拥有相同大小的度的最短连续子数组,返回其长度。
示例 1:
输入:nums = [1,2,2,3,1]
输出:2
解释:
输入数组的度是 2 ,因为元素 1 和 2 的出现频数最大,均为 2 。
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组 [2, 2] 的长度为 2 ,所以返回 2 。
示例 2:
输入:nums = [1,2,2,3,1,4,2]
输出:6
解释:
数组的度是 3 ,因为元素 2 重复出现 3 次。
所以 [2,2,3,1,4,2] 是最短子数组,因此返回 6 。
提示:
nums.length
在1
到50,000
范围内。nums[i]
是一个在0
到49,999
范围内的整数。
详解:要找频率出现的最大的度,并且还要输出最短连续数组的长度。我们可以用一个哈希表记录元素的key和出现的count次数,然后用两个哈希表记录下每个元素第一次出现时间和最后一次出现的时间,最后遍历第一个哈希表(也就是记录了所有值的哈希表),用得到的度去判断两个哈希表中元素位置,再相减就能求出来了。
题解:
class Solution {
public int findShortestSubArray(int[] nums) {
// 哈希表记录每个元素的出现频数
Map<Integer, Integer> countMap = new HashMap<>();
// 哈希表记录每个元素首次出现的位置
Map<Integer, Integer> firstOccurrenceMap = new HashMap<>();
// 哈希表记录每个元素最后出现的位置
Map<Integer, Integer> lastOccurrenceMap = new HashMap<>();
int degree = 0; // 数组的度
int minLength = nums.length; // 初始化最短子数组长度为数组的长度
// 遍历数组
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
// 更新出现次数
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
// 记录首次出现的位置
if (!firstOccurrenceMap.containsKey(num)) {
firstOccurrenceMap.put(num, i);
}
// 更新最后出现的位置
lastOccurrenceMap.put(num, i);
}
// 查找最大频数(数组的度)
for (int count : countMap.values()) {
degree = Math.max(degree, count);
}
// 查找具有最大频数的元素的最短子数组长度
for (int num : countMap.keySet()) {
if (countMap.get(num) == degree) {
int length = lastOccurrenceMap.get(num) - firstOccurrenceMap.get(num) + 1;
minLength = Math.min(minLength, length);
}
}
return minLength;
}
}
2.4.3 找到数组中消失的数字(448)
题目:
给你一个含 n
个整数的数组 nums
,其中 nums[i]
在区间 [1, n]
内。请你找出所有在 [1, n]
范围内但没有出现在 nums
中的数字,并以数组的形式返回结果。
示例 1:
输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
示例 2:
输入:nums = [1,1]
输出:[2]
提示:
n == nums.length
1 <= n <= 105
1 <= nums[i] <= n
详解:先对数组进行排序,然后使用一个boolean数组来进行标记为True,遍历数组,对boolean[i]进行判断,为false的就是缺失的数据。
题解:
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
int length = nums.length;
//用来标记不存在的数字
boolean[] bool = new boolean[length + 1];
List<Integer> result = new ArrayList<>();
for(int num : nums){
bool[num]=true;
}
for(int i = 1; i <= length; i++){
if(!bool[i]) {
result.add(i);
}
}
return result;
}
}
2.4.4数组中重复的数据(442)
题目:
给你一个长度为 n
的整数数组 nums
,其中 nums
的所有整数都在范围 [1, n]
内,且每个整数出现 一次 或 两次 。请你找出所有出现 两次 的整数,并以数组形式返回。
你必须设计并实现一个时间复杂度为 O(n)
且仅使用常量额外空间的算法解决此问题。
示例 1:
输入:nums = [4,3,2,7,8,2,3,1]
输出:[2,3]
示例 2:
输入:nums = [1,1,2]
输出:[1]
示例 3:
输入:nums = [1]
输出:[]
提示:
n == nums.length
1 <= n <= 105
1 <= nums[i] <= n
nums
中的每个元素出现 一次 或 两次
详解:同样的这里也是可以用一个哈希表来记录值和出现的次数,然后遍历哈希表,取出出现2次的值即可
题解:
class Solution {
public List<Integer> findDuplicates(int[] nums) {
Map<Integer, Integer> twice = new HashMap<>();
List<Integer> result = new ArrayList<>();
int count = 0; // 记录出现两次的数组值
for (int num : nums) {
twice.put(num, twice.getOrDefault(num, 0) + 1);
}
// 找出出现两次的数字
for (Map.Entry<Integer, Integer> entry : twice.entrySet()) {
if (entry.getValue() == 2) {
result.add(entry.getKey());
}
}
return result;
}
}
2.4.5 缺失的第一个正数(41)
题目:
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n)
并且只使用常数级别额外空间的解决方案。
示例 1:
输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。
示例 2:
输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。
示例 3:
输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。
详解:需要找出最小的正整数,我们可以这样想,把大于0的数每个数都放与之对应的索引上去,然后再次遍历数组,如果哪个数与其索引并不对应,那么索引 i+1 就是缺失的最小正数
题解:
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
// Step 1: 将每个数字放到正确的位置上
for (int i = 0; i < n; i++) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
swap(nums, i, nums[i] - 1);
}
}
// Step 2: 找到第一个不符合位置的索引
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
// 如果所有位置都正确,返回 n + 1
return n + 1;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
2.4.6 整数转英文的表示(273)
题目:
将非负整数 num
转换为其对应的英文表示。
示例 1:
输入:num = 123
输出:"One Hundred Twenty Three"
示例 2:
输入:num = 12345
输出:"Twelve Thousand Three Hundred Forty Five"
示例 3:
输入:num = 1234567
输出:"One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"
题解:
class Solution {
// 低于 20 的数字及其英文表示
private static final String[] LESS_THAN_20 = {
"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",
"Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"
};
// 十位数及其英文表示
private static final String[] TENS = {
"", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"
};
// 千、百万、亿及其英文表示
private static final String[] THOUSANDS = {
"", "Thousand", "Million", "Billion"
};
public String numberToWords(int num) {
if (num == 0) return "Zero";
int i = 0;
String words = "";
while (num > 0) {
if (num % 1000 != 0) {
words = helper(num % 1000) + THOUSANDS[i] + " " + words;
}
num /= 1000;
i++;
}
return words.trim();
}
private String helper(int num) {
if (num == 0) return "";
else if (num < 20) return LESS_THAN_20[num] + " ";
else if (num < 100) return TENS[num / 10] + " " + helper(num % 10);
else return LESS_THAN_20[num / 100] + " Hundred " + helper(num % 100);
}
}
2.5 数组的移动改变
2.5.1 最小操作使数组元素相等(453)
题目:
给你一个长度为 n
的整数数组,每次操作将会使 n - 1
个元素增加 1
。返回让数组所有元素相等的最小操作次数。
示例 1:
输入:nums = [1,2,3]
输出:3
解释:
只需要3次操作(注意每次操作会增加两个元素的值):
[1,2,3] => [2,3,3] => [3,4,3] => [4,4,4]
示例 2:
输入:nums = [1,1,1]
输出:0
提示:
n == nums.length
1 <= nums.length <= 105
-109 <= nums[i] <= 109
- 答案保证符合 32-bit 整数
详解:每次操作让n - 1个元素加1,我们可以逆向思考,每次操作都使得1个元素 -1,然后返回最少操作让所有元素的值都一样。
题解:
class Solution {
public int minMoves(int[] nums) {
int count = 0;
Arrays.sort(nums);
for(int i = 1; i < nums.length; i++) {
//让没每个元素都减去最小的元素然后再累加
count = count + nums[i] - nums[0];
}
return count;
}
}
2.5.2 非递减数列(665)
题目:
给你一个长度为 n
的整数数组 nums
,请你判断在 最多 改变 1
个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中任意的 i
(0 <= i <= n-2)
,总满足 nums[i] <= nums[i + 1]
。
示例 1:
输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个 4 变成 1 来使得它成为一个非递减数列。
示例 2:
输入: nums = [4,2,1]
输出: false
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。
提示:
n == nums.length
1 <= n <= 104
-105 <= nums[i] <= 105
详解:暂无
题解:
class Solution {
public boolean checkPossibility(int[] nums) {
int count = 0;
for (int i = 1; i < nums.length && count <= 1; i++) {
if (nums[i] < nums[i - 1]) {
count++;
// 如果当前元素小于前一个元素,尝试调整当前或前一个元素
if (i - 2 < 0 || nums[i - 2] <= nums[i]) {
nums[i - 1] = nums[i]; // 调整前一个元素
} else {
nums[i] = nums[i - 1]; // 调整当前元素
}
}
}
return count <= 1;
}
}
2.5.3 移动零(283)
题目:
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
详解:我们可以把非零的元素移到前面,然后把其他位置的元素置零
题解:
class Solution {
public void moveZeroes(int[] nums) {
int nonZeroIndex = 0; // 用于记录非零元素应该存放的位置
// 遍历数组,将非零元素移动到数组前部
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
nums[nonZeroIndex++] = nums[i];
}
}
// 将剩余的位置全部填充为0
for (int i = nonZeroIndex; i < nums.length; i++) {
nums[i] = 0;
}
}
}
2.6 二维数组及滚动数组
2.6.1 杨辉三角(118)
题目:
给定一个非负整数 numRows
, 生成「杨辉三角」的前 *numRows
*行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:
输入: numRows = 1
输出: [[1]]
详解:其实这个就是左边和右边为1 第一行也是为1,然后下一行是上一行两个相邻元素之和
题解:
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> triangle = new ArrayList<>();
if (numRows == 0) {
return triangle;
}
// 第一行固定为 [1]
List<Integer> firstRow = new ArrayList<>();
firstRow.add(1);
triangle.add(firstRow);
// 生成每一行
for (int i = 1; i < numRows; i++) {
List<Integer> prevRow = triangle.get(i - 1);
List<Integer> currentRow = new ArrayList<>();
// 每行的第一个元素为 1
currentRow.add(1);
// 中间的元素等于上一行的两个相邻元素之和
for (int j = 1; j < i; j++) {
currentRow.add(prevRow.get(j - 1) + prevRow.get(j));
}
// 每行的最后一个元素为 1
currentRow.add(1);
triangle.add(currentRow);
}
return triangle;
}
}
2.6.2 杨辉三角II(119)
题目:
给定一个非负索引 rowIndex
,返回「杨辉三角」的第 rowIndex
行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: rowIndex = 3
输出: [1,3,3,1]
示例 2:
输入: rowIndex = 0
输出: [1]
示例 3:
输入: rowIndex = 1
输出: [1,1]
详解:这个是返回第几行的数组元素,就是简化版本的杨辉三角,
题解:这里的话就是每一行插入当前的元素。
class Solution {
public List<Integer> getRow(int rowIndex) {
List<Integer> row = new ArrayList<>();
// 初始化第 0 行
for (int i = 0; i <= rowIndex; i++) {
row.add(0, 1);
for (int j = 1; j < row.size() - 1; j++) {
row.set(j, row.get(j) + row.get(j + 1));
}
}
return row;
}
}
2.7 数组的轮转
2.7.1 轮转数组(189)
题目:
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
提示:
1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
0 <= k <= 105
详解:这里有一种解法就是先翻转所有的元素,然后再次翻转前 k 个元素,再翻转[k, length - 1]个元素。
例如:数组 [1, 2, 3, 4, 5, 6, 7]
变为 [7, 6, 5, 4, 3, 2, 1]
。
- 反转数组的前
k
个元素,使这些元素恢复到它们在旋转后的正确位置。 - 例如,反转
[7, 6, 5]
,结果为[5, 6, 7]
。 - 反转数组从第
k
个位置到末尾的元素,使这些元素恢复到它们在旋转后的正确位置。 - 例如,反转
[4, 3, 2, 1]
,结果为[1, 2, 3, 4]
。
题解:
class Solution {
public void rotate(int[] nums, int k) {
k = k % nums.length; // 处理 k 大于数组长度的情况
reverse(nums, 0, nums.length - 1);
// 反转前k个元素
reverse(nums, 0, k - 1);
// 反转剩余元素
reverse(nums, k, nums.length - 1);
}
// 辅助方法:反转数组中指定范围的元素
private void reverse(int[] nums, int start, int end) {
while(start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
2.7.2 旋转函数(396)
题目:
给定一个长度为 n
的整数数组 nums
。
假设 arrk
是数组 nums
顺时针旋转 k
个位置后的数组,我们定义 nums
的 旋转函数 F
为:
F(k) = 0 * arrk[0] + 1 * arrk[1] + ... + (n - 1) * arrk[n - 1]
返回 F(0), F(1), ..., F(n-1)
中的最大值。
生成的测试用例让答案符合 32 位 整数。
示例 1:
输入: nums = [4,3,2,6]
输出: 26
解释:
F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25
F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16
F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23
F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26
所以 F(0), F(1), F(2), F(3) 中的最大值是 F(3) = 26 。
示例 2:
输入: nums = [100]
输出: 0
提示:
n == nums.length
1 <= n <= 105
-100 <= nums[i] <= 100
详解:
根据以下公式计算 F(k)
: F(k)=F(k−1)+sum−n⋅nums[n−k] 其中,nums[n-k]
是旋转到第 k
位的元素。
题解:
class Solution {
public int maxRotateFunction(int[] nums) {
int sum = 0;
int F = 0;
// 计算数组元素的总和和初始的 F(0)
for (int i = 0; i < n; i++) {
sum += nums[i];
F += i * nums[i];
}
int maxF = F;
// 计算其他 F(k)
for (int k = 1; k < n; k++) {
F = F + sum - n * nums[n - k];
maxF = Math.max(maxF, F);
}
return maxF;
}
}
2.8 特定顺序遍历二维数组
2.8.1 螺旋矩阵(54)
题目:
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100
详解:
首先:top = 0 bottom = matrix
.length - 1
left= 0 right=matrix
[0].length - 1
- 上边界:从左往右走,left <= right maxtrix[top][i] top ++
- 右边界:从上网下走,top <= bottom maxtrix[right][i] right--
- 下边界:从右往左走,right>=left maxtrix[bottom][i] bottom--
- 左边界:从下往上走:bottom>=top maxtrix[i][left] i++
题解:
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<>();
if (matrix == null || matrix.length == 0) {
return result;
}
int top = 0;
int bottom = matrix.length - 1;
int left = 0;
int right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
// 从左往右走
for (int i = left; i <= right; i++) {
result.add(matrix[top][i]);
}
top++;
// 从右边上往下走
for (int i = top; i <= bottom; i++) {
result.add(matrix[i][right]);
}
right--;
// 从右往左走下边界
if (top <= bottom) {
for (int i = right; i >= left; i--) {
result.add(matrix[bottom][i]);
}
bottom--;
}
// 从左边往上走
if (left <= right) {
for (int i = bottom; i >= top; i--) {
result.add(matrix[i][left]);
}
left++;
}
}
return result;
}
}
2.8.2 螺旋矩阵II(59)
题目:
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1
输出:[[1]]
提示:
1 <= n <= 20
详解:这个就是我们在遍历的时候增加一个num++,放到数组里面
题解:
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int num = 1;
int top = 0;
int bottom = n - 1;
int left = 0;
int right = n - 1;
while (num <= n * n) {
// 从上左往右走
for (int i = left; i <= right; i++) {
matrix[top][i] = num++;
}
top++;
// 从右边往下走
for (int i = top; i <= bottom; i++) {
matrix[i][right] = num++;
}
right--;
// 从下边界往左走
if (top <= bottom) {
for (int i = right; i >= left; i--) {
matrix[bottom][i] = num++;
}
bottom--;
}
// 从左边界往上走
if (left <= right) {
for (int i = bottom; i >= top; i--) {
matrix[i][left] = num++;
}
left++;
}
}
return matrix;
}
}
2.9 二维数组变换
2.9.1 重塑举证(566)
题目:
在 MATLAB 中,有一个非常有用的函数 reshape
,它可以将一个 m x n
矩阵重塑为另一个大小不同(r x c
)的新矩阵,但保留其原始数据。
给你一个由二维数组 mat
表示的 m x n
矩阵,以及两个正整数 r
和 c
,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。
如果具有给定参数的 reshape
操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
示例 1:
输入:mat = [[1,2],[3,4]], r = 1, c = 4
输出:[[1,2,3,4]]
示例 2:
输入:mat = [[1,2],[3,4]], r = 2, c = 4
输出:[[1,2],[3,4]]
提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 100
-1000 <= mat[i][j] <= 1000
1 <= r, c <= 300
详解:不管给什么矩阵,改造后长宽的乘积一定要一样,也就是元素个数之和一定要一样。我么可以先把矩阵放进一个数组里然后再遍历新的二维矩阵,把数组里的数据放到新的矩阵里面。
题解:
class Solution {
public int[][] matrixReshape(int[][] mat, int r, int c) {
int m = mat.length;
int n = mat[0].length;
int[] fresh = new int[m * n];
if(m*n != r*c) return mat;
int index = 0;
for(int i = 0; i < m; i++ ){
for(int j = 0; j < n; j++){
fresh[index++] = mat[i][j];
}
}
int[][] reshaped = new int[r][c];
index = 0;
for(int i = 0; i < r; i++ ){
for(int j = 0; j < c; j++){
reshaped[i][j] = fresh[index++];
}
}
return reshaped;
}
}
2.9.2 旋转图像 (48)
题目:
给定一个 n × n 的二维矩阵 matrix
表示一个图像。请你将图像顺时针旋转 90 度。
你必须在**原地** 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
提示:
n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000
详解:因为不能使用复制矩阵,所以只能在原矩阵上操作,细细观察,变换的矩阵就是把原来的矩阵翻转,然后再沿着列翻转
题解:
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
for(int i = 0; i < n; i++) {
for(int j = i; j < n; j++ ) {
//二维数组翻转
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < n /2 ; j++ ) {
//将翻转后的数组列翻转
int temp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = temp;
}
}
}
}
2.9.3 矩阵置零(73)
题目:
给定一个 mx
n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**
示例 1:
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
示例 2:
输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]
提示:
m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
-231 <= matrix[i][j] <= 231 - 1
详解:声明二个Boolean的数组,用于记录每一个为0的元素的横纵坐标,然后再次遍历二维数组,把有横纵坐标的那一行或那一列变为0
题解:
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
// 创建标记数组 记录为零元素的行和列
boolean[] rows = new boolean[m];
boolean[] cols = new boolean[n];
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(matrix[i][j] == 0){
rows[i] = true;
cols[j] = true;
}
}
}
//根据标记将行置零
for(int i = 0; i < m; i++){
if(rows[i] == true){
for(int j = 0; j < n; j++){
matrix[i][j] =0;
}
}
}
//根据标记将列置零
for(int j = 0; j < n; j++){
if(cols[j] == true){
for(int i = 0; i < m; i++){
matrix[i][j] = 0;
}
}
}
}
}
2.9.4 生命游戏(289)
题目:
根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。
给定一个包含 m × n
个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1
即为 活细胞 (live),或 0
即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:
- 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
- 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
- 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
- 如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n
网格面板 board
的当前状态,返回下一个状态。
示例 1:
输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]]
示例 2:
输入:board = [[1,1],[1,0]]
输出:[[1,1],[1,1]]
提示:
m == board.length
n == board[i].length
1 <= m, n <= 25
board[i][j]
为0
或1
详解:
题解:
class Solution {
public void gameOfLife(int[][] board) {
int m = board.length;
int n = board[0].length;
// 遍历每个细胞并计算其下一状态
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int liveNeighbors = countLiveNeighbors(board, i, j);
// 根据规则更新状态
if (board[i][j] == 1 && (liveNeighbors < 2 || liveNeighbors > 3)) {
// 规则1或规则3:活细胞死亡
board[i][j] = 2; // 10 in binary
}
if (board[i][j] == 0 && liveNeighbors == 3) {
// 规则4:死细胞复活
board[i][j] = 3; // 11 in binary
}
}
}
// 更新细胞状态
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
board[i][j] %= 2;
}
}
}
private int countLiveNeighbors(int[][] board, int i, int j) {
int count = 0;
int m = board.length;
int n = board[0].length;
// 方向数组,表示相邻的8个位置
int[] directions = {-1, 0, 1};
for (int x : directions) {
for (int y : directions) {
if (x == 0 && y == 0) {
continue;
}
int newX = i + x;
int newY = j + y;
if (newX >= 0 && newX < m && newY >= 0 && newY < n) {
if (board[newX][newY] == 1 || board[newX][newY] == 2) {
count++;
}
}
}
}
return count;
}
}
2.10 前缀和数组
2.10.1 区域和检索-数组不可变(303)
题目:
给定一个整数数组 nums
,处理以下类型的多个查询:
- 计算索引
left
和right
(包含left
和right
)之间的nums
元素的 和 ,其中left <= right
实现 NumArray
类:
NumArray(int[] nums)
使用数组nums
初始化对象int sumRange(int i, int j)
返回数组nums
中索引left
和right
之间的元素的 总和 ,包含left
和right
两点(也就是nums[left] + nums[left + 1] + ... + nums[right]
)
示例 1:
输入:
["NumArray", "sumRange", "sumRange", "sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]
解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))
提示:
1 <= nums.length <= 104
-105 <= nums[i] <= 105
0 <= i <= j < nums.length
- 最多调用
104
次sumRange
方法
详解:前缀和数组是一种预处理技术,可以在常数时间内计算任意子数组的和。先计算好,然后利用索引可以直接求出。
题解:
class NumArray {
private int[] prefixSum;
public NumArray(int[] nums) {
int n = nums.length;
prefixSum = new int[n + 1];
for (int i = 0; i < n; i++) {
prefixSum[i + 1] = prefixSum[i] + nums[i];
}
}
public int sumRange(int left, int right) {
return prefixSum[right + 1] - prefixSum[left];
}
}
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* int param_1 = obj.sumRange(left,right);
*/
2.10.2 除自身以外数组的乘积(238)
题目:
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法, 且在 O(n)
时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
提示:
2 <= nums.length <= 105
-30 <= nums[i] <= 30
- 保证 数组
nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内
详解:这里也是利用第 k 个元素的 k - 1前面的乘积和 k + 1 后面的乘积
题解:
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] answer = new int[n];
int[] prefix = new int[n];
int[] suffix = new int[n];
// 初始化 prefix 和 suffix 数组
prefix[0] = 1;
suffix[n - 1] = 1;
// 计算 prefix 数组
for (int i = 1; i < n; i++) {
prefix[i] = prefix[i - 1] * nums[i - 1];
}
// 计算 suffix 数组
for (int i = n - 2; i >= 0; i--) {
suffix[i] = suffix[i + 1] * nums[i + 1];
}
// 计算最终结果
for (int i = 0; i < n; i++) {
answer[i] = prefix[i] * suffix[i];
}
return answer;
}
}