mirror of
https://github.com/chefyuan/algorithm-base.git
synced 2026-03-10 11:54:43 +00:00
chefyuan
This commit is contained in:
96
animation-simulation/数组篇/leetcode1052爱生气的书店老板.md
Normal file
96
animation-simulation/数组篇/leetcode1052爱生气的书店老板.md
Normal file
@@ -0,0 +1,96 @@
|
||||
|
||||
|
||||
**题目描述**
|
||||
|
||||
今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。
|
||||
|
||||
在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。
|
||||
|
||||
书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。
|
||||
|
||||
请你返回这一天营业下来,最多有多少客户能够感到满意的数量。
|
||||
|
||||
|
||||
示例:
|
||||
|
||||
> 输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3
|
||||
> 输出:16
|
||||
|
||||
解释:
|
||||
书店老板在最后 3 分钟保持冷静。
|
||||
感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.
|
||||
|
||||
|
||||
|
||||
该题目思想就是,我们将 customer 数组的值分为三部分, leftsum, winsum, rightsum。我们题目的返回值则是三部分的最大和。
|
||||
|
||||
注意这里的最大和,我们是怎么计算的。
|
||||
|
||||

|
||||
|
||||
winsum 是窗口内的所有值,不管 grumpy[i] 的值是 0 还是 1,窗口的大小,就对应 K 的值,也就是老板的技能发动时间,该时间段内,老板不会生气,所以为所有的值。
|
||||
|
||||
leftsum 是窗口左边区间的值,此时我们不能为所有值,只能是 grumpy[i] == 0 时才可以加入,因为此时不是技能发动期,老板只有在 grumpy[i] == 0 时,才不会生气。
|
||||
|
||||
rightsum 是窗口右区间的值,和左区间加和方式一样。那么我们易懂一下窗口,我们的 win 值和 leftsum 值,rightsum 值是怎么变化的呢?
|
||||
|
||||
见下图
|
||||
|
||||

|
||||
|
||||
我们此时移动了窗口,
|
||||
|
||||
则左半区间范围扩大,但是 leftsum 的值没有变,这时因为新加入的值,所对应的 grumpy[i] == 1,所以其值不会发生改变,因为我们只统计 grumpy[i] == 0 的值,
|
||||
|
||||
右半区间范围减少,rightsum 值也减少,因为右半区间减小的值,其对应的 grumpy[i] == 0,所以 rightsum -= grumpy[i]。
|
||||
|
||||
winsum 也会发生变化, winsum 需要加上新加入窗口的值,减去刚离开窗口的值, 也就是 customer[left-1],left 代表窗口左边缘。
|
||||
|
||||
好啦,知道怎么做了,我们直接开整吧。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int maxSatisfied(int[] customers, int[] grumpy, int X) {
|
||||
|
||||
int winsum = 0;
|
||||
int rightsum = 0;
|
||||
int len = customers.length;
|
||||
//右区间的值
|
||||
for (int i = X; i < len; ++i) {
|
||||
if (grumpy[i] == 0) {
|
||||
rightsum += customers[i];
|
||||
}
|
||||
}
|
||||
//窗口的值
|
||||
for (int i = 0; i < X; ++i) {
|
||||
winsum += customers[i];
|
||||
}
|
||||
int leftsum = 0;
|
||||
//窗口左边缘
|
||||
int left = 1;
|
||||
//窗口右边缘
|
||||
int right = X;
|
||||
int maxcustomer = winsum + leftsum + rightsum;
|
||||
while (right < customers.length) {
|
||||
//重新计算左区间的值,也可以用 customer 值和 grumpy 值相乘获得
|
||||
if (grumpy[left-1] == 0) {
|
||||
leftsum += customers[left-1];
|
||||
}
|
||||
//重新计算右区间值
|
||||
if (grumpy[right] == 0) {
|
||||
rightsum -= customers[right];
|
||||
}
|
||||
//窗口值
|
||||
winsum = winsum - customers[left-1] + customers[right];
|
||||
//保留最大值
|
||||
maxcustomer = Math.max(maxcustomer,winsum+leftsum+rightsum);
|
||||
//移动窗口
|
||||
left++;
|
||||
right++;
|
||||
}
|
||||
return maxcustomer;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
73
animation-simulation/数组篇/leetcode1438绝对值不超过限制的最长子数组.md
Normal file
73
animation-simulation/数组篇/leetcode1438绝对值不超过限制的最长子数组.md
Normal file
@@ -0,0 +1,73 @@
|
||||
#### 1438. 绝对差不超过限制的最长连续子数组
|
||||
|
||||
给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。
|
||||
|
||||
如果不存在满足条件的子数组,则返回 0 。
|
||||
|
||||
示例
|
||||
|
||||
> 输入:nums = [10,1,2,4,7,2], limit = 5
|
||||
> 输出:4
|
||||
> 解释:满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。
|
||||
|
||||
**提示:**
|
||||
|
||||
- 1 <= nums.length <= 10^5
|
||||
|
||||
- 1 <= nums[i] <= 10^9
|
||||
- 0 <= limit <= 10^9
|
||||
|
||||
**题目解析**
|
||||
|
||||
我们结合题目,示例,提示来看,这个题目也可以使用滑动窗口的思想来解决。我们需要判断某个子数组是否满足最大绝对差不超过限制值。
|
||||
|
||||
那么我们应该怎么解决呢?
|
||||
|
||||
我们想一下,窗口内的最大绝对差,如果我们知道窗口的最大值和最小值,最大值减去最小值就能得到最大绝对差。
|
||||
|
||||
所以我们这个问题就变成了获取滑动窗口内的最大值和最小值问题,哦?滑动窗口的最大值,是不是很熟悉,大家可以先看一下[滑动窗口的最大值](https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/solution/yi-shi-pin-sheng-qian-yan-shuang-duan-du-mbga/)这个题目,那我们完全可以借助刚才题目的思想来解决这个题目。啪的一下我就搞懂了。
|
||||
|
||||
滑动窗口的最大值,我们当时借助了双端队列,来维护一个单调递减的双端队列,进而得到滑动窗口的最大值
|
||||
|
||||
那么我们同样可以借助双端队列,来维护一个单调递增的双端队列,来获取滑动窗口的最小值。既然知道了最大值和最小值,我们就可以判断当前窗口是否符合要求,如果符合要求则扩大窗口,不符合要求则缩小窗口,循环结束返回最大的窗口值即可。
|
||||
|
||||
下面我们来看一下我们的动画模拟,一下就能看懂!
|
||||
|
||||
<img src="https://img-blog.csdnimg.cn/20210320092423565.gif" style="zoom:150%;" />
|
||||
|
||||
其实,我们只要把握两个重点即可,我们的 maxdeque 维护的是一个单调递减的双端队列,头部为当前窗口的最大值, mindeque 维护的是一个单调递增的双端队列,头部为窗口的最小值,即可。好啦我们一起看代码吧。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int longestSubarray(int[] nums, int limit) {
|
||||
|
||||
Deque<Integer> maxdeque = new LinkedList<>();
|
||||
Deque<Integer> mindeque = new LinkedList<>();
|
||||
int len = nums.length;
|
||||
int right = 0, left = 0, maxwin = 0;
|
||||
|
||||
while (right < len) {
|
||||
while (!maxdeque.isEmpty() && maxdeque.peekLast() < nums[right]) {
|
||||
maxdeque.removeLast();
|
||||
}
|
||||
while (!mindeque.isEmpty() && mindeque.peekLast() > nums[right]) {
|
||||
mindeque.removeLast();
|
||||
}
|
||||
//需要更多视频解算法,可以来我的公众号:袁厨的算法小屋
|
||||
maxdeque.addLast(nums[right]);
|
||||
mindeque.addLast(nums[right]);
|
||||
while (maxdeque.peekFirst() - mindeque.peekFirst() > limit) {
|
||||
if (maxdeque.peekFirst() == nums[left]) maxdeque.removeFirst();
|
||||
if (mindeque.peekFirst() == nums[left]) mindeque.removeFirst();
|
||||
left++;
|
||||
}
|
||||
//保留最大窗口
|
||||
maxwin = Math.max(maxwin,right-left+1);
|
||||
right++;
|
||||
}
|
||||
return maxwin;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
是不是很有趣这个题目,大家快来打卡吧,希望对各位有一丢丢帮助吧。
|
||||
84
animation-simulation/数组篇/leetcode1两数之和.md
Normal file
84
animation-simulation/数组篇/leetcode1两数之和.md
Normal file
@@ -0,0 +1,84 @@
|
||||
### leetcode 1:两数之和
|
||||
|
||||
**题目描述:**
|
||||
|
||||
> 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
|
||||
>
|
||||
> 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
|
||||
|
||||
**示例:**
|
||||
|
||||
> 给定 nums = [2, 7, 11, 15], target = 9
|
||||
>
|
||||
> 因为 nums[0] + nums[1] = 2 + 7 = 9
|
||||
> 所以返回 [0, 1]
|
||||
|
||||
题目很容易理解,即让查看数组中有没有两个数的和为目标数,如果有的话则返回两数下标,我们为大家提供两种解法双指针(暴力)法,和哈希表法
|
||||
|
||||
**双指针(暴力)法**
|
||||
|
||||
**解析**
|
||||
|
||||
双指针(L,R)法的思路很简单,L指针用来指向第一个值,R指针用来从第L指针的后面查找数组中是否含有和L指针指向值和为目标值的数。见下图
|
||||
|
||||

|
||||
|
||||
例:绿指针指向的值为3,蓝指针需要在绿指针的后面遍历查找是否含有 target - 3 = 2的元素,若含有返回即可。
|
||||
|
||||
**题目代码**
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int[] twoSum(int[] nums, int target) {
|
||||
if(nums.length < 2){
|
||||
return new int[0];
|
||||
}
|
||||
int[] rearr = new int[2];
|
||||
//查询元素
|
||||
for(int i = 0; i < nums.length; i++){
|
||||
for(int j = i+1; j < nums.length; j++ ){
|
||||
//发现符合条件情况
|
||||
if(nums[i] + nums[j] ==target){
|
||||
rearr[0] = i;
|
||||
rearr[1] = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rearr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**哈希表**
|
||||
|
||||
**解析**
|
||||
|
||||
哈希表的做法很容易理解,我们只需通过一次循环即可,假如我们的 target 值为 9,当前指针指向的值为 2 ,我们只需从哈希表中查找是否含有 7,因为9 - 2 =7 。如果含有 7 我们直接返回即可,如果不含有则将当前的2存入哈希表中,指针移动,指向下一元素。注: key 为元素值,value 为元素索引。
|
||||
|
||||
**动图解析:**
|
||||
|
||||

|
||||
|
||||
是不是很容易理解,下面我们来看一下题目代码。
|
||||
|
||||
**题目代码:**
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int[] twoSum(int[] nums, int target) {
|
||||
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
|
||||
for(int i = 0; i < nums.length; i++){
|
||||
//如果存在则返回
|
||||
if(map.containsKey(target-nums[i])){
|
||||
return new int[]{map.get(target-nums[i]),i};
|
||||
}
|
||||
//不存在则存入
|
||||
map.put(nums[i],i);
|
||||
|
||||
}
|
||||
return new int[0];
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
87
animation-simulation/数组篇/leetcode219数组中重复元素2.md
Normal file
87
animation-simulation/数组篇/leetcode219数组中重复元素2.md
Normal file
@@ -0,0 +1,87 @@
|
||||
### leetcode 219 数组中重复元素2
|
||||
|
||||
**题目描述**
|
||||
|
||||
给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。
|
||||
|
||||
示例 1:
|
||||
|
||||
> 输入: nums = [1,2,3,1], k = 3
|
||||
> 输出: true
|
||||
|
||||
示例 2:
|
||||
|
||||
> 输入: nums = [1,0,1,1], k = 1
|
||||
> 输出: true
|
||||
|
||||
示例 3:
|
||||
|
||||
> 输入: nums = [1,2,3,1,2,3], k = 2
|
||||
> 输出: false
|
||||
|
||||
**Hashmap**
|
||||
|
||||
这个题目和我们上面那个数组中的重复数字几乎相同,只不过是增加了一个判断相隔是否小于K位的条件,我们先用 HashMap 来做一哈,和刚才思路一致,我们直接看代码就能整懂
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public boolean containsNearbyDuplicate(int[] nums, int k) {
|
||||
//特殊情况
|
||||
if (nums.length == 0) {
|
||||
return false;
|
||||
}
|
||||
// hashmap
|
||||
HashMap<Integer,Integer> map = new HashMap<>();
|
||||
for (int i = 0; i < nums.length; i++) {
|
||||
// 如果含有
|
||||
if (map.containsKey(nums[i])) {
|
||||
//判断是否小于K,如果小于则直接返回
|
||||
int abs = Math.abs(i - map.get(nums[i]));
|
||||
if (abs <= k) return true;//小于则返回
|
||||
}
|
||||
//更新索引,此时有两种情况,不存在,或者存在时,将后出现的索引保存
|
||||
map.put(nums[i],i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**HashSet**
|
||||
|
||||
**解析**
|
||||
|
||||
这个方法算是属于固定滑动窗口。我们需要维护一个长度为 K 的滑动窗口,如果窗口内含有该值,则直接返回 true,尾部进入新元素时,则将头部的元素去掉。继续查看是否含有该元素。下面我们来看视频解析吧,保证以下就能搞懂了。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
**题目代码**
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public boolean containsNearbyDuplicate(int[] nums, int k) {
|
||||
//特殊情况
|
||||
if (nums.length == 0) {
|
||||
return false;
|
||||
}
|
||||
// set
|
||||
HashSet<Integer> set = new HashSet<>();
|
||||
for (int i = 0; i < nums.length; ++i) {
|
||||
//含有该元素,返回true
|
||||
if (set.contains(nums[i])) {
|
||||
return true;
|
||||
}
|
||||
// 添加新元素
|
||||
set.add(nums[i]);
|
||||
//维护窗口长度
|
||||
if (set.size() > k) {
|
||||
set.remove(nums[i-k]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
102
animation-simulation/数组篇/leetcode27移除元素.md
Normal file
102
animation-simulation/数组篇/leetcode27移除元素.md
Normal file
@@ -0,0 +1,102 @@
|
||||
### leetcode 27 移除元素
|
||||
|
||||
**题目描述**
|
||||
|
||||
> 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
|
||||
>
|
||||
> 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
|
||||
>
|
||||
> 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
|
||||
|
||||
**示例 1:**
|
||||
|
||||
> 给定 nums = [3,2,2,3], val = 3,
|
||||
>
|
||||
> 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
|
||||
>
|
||||
> 你不需要考虑数组中超出新长度后面的元素。
|
||||
|
||||
**示例 2:**
|
||||
|
||||
> 给定 nums = [0,1,2,2,3,0,4,2], val = 2,
|
||||
>
|
||||
> 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
|
||||
>
|
||||
> 注意这五个元素可为任意顺序。
|
||||
>
|
||||
> 你不需要考虑数组中超出新长度后面的元素。
|
||||
|
||||
**暴力法**
|
||||
|
||||
**解析**
|
||||
|
||||
该题目也算是简单题目,适合新手来做,然后大家也不要看不起暴力解法,我们可以先写出暴力解法,然后再思考其他方法,这对于我们的编码能力有很大的帮助。我们来解析一下这个题目的做题思路,他的含义就是让我们删除掉数组中的元素,然后将数组后面的元素跟上来。最后返回删除掉元素的数组长度即可。比如数组长度为 10,里面有2个目标值,我们最后返回的长度为 8,但是返回的 8 个元素,需要排在数组的最前面。那么暴力解法的话则就需要两个 for 循环,一个用来找到删除,另一个用来更新数组。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
总体思路就是这样的,后面的会不断往前覆盖。暴力解法也是不超时的,实现也不算太简单主要需要注意两个地方。
|
||||
|
||||
(1)需要先定义变量len获取数组长度,因为后面我们的返回的数组长度是改变的,所以不可以用 nums.length 作为上界
|
||||
|
||||
(2)我们每找到一个需要删除的值的时候,需要i--,防止出现多个需要删除的值在一起的情况,然后漏删。
|
||||
|
||||
**题目代码**
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int removeElement(int[] nums, int val) {
|
||||
//获取数组长度
|
||||
int len = nums.length;
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
int i = 0;
|
||||
for (i = 0; i < len; ++i) {
|
||||
//发现符合条件的情况
|
||||
if (nums[i] == val) {
|
||||
//前移一位
|
||||
for (int j = i; j < len-1; ++j) {
|
||||
nums[j] = nums[j+1];
|
||||
}
|
||||
i--;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**双指针**
|
||||
|
||||
快慢指针的做法比较有趣,只需要一个 for 循环即可解决,时间复杂度为 O(n) ,总体思路就是有两个指针,前面一个后面一个,前面的用于搜索需要删除的值,当遇到需要删除的值时,前指针直接跳过,后面的指针不动,当遇到正常值时,两个指针都进行移动,并修改慢指针的值。最后只需输出慢指针的索引即可。
|
||||
|
||||
**动图解析:**
|
||||
|
||||

|
||||
|
||||
**题目代码:**
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int removeElement(int[] nums, int val) {
|
||||
int len = nums.length;
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
int i = 0;
|
||||
for (int j = 0; j < nums.length; ++j) {
|
||||
//如果等于目标值,则删除
|
||||
if (nums[j] == val) {
|
||||
continue;
|
||||
}
|
||||
// 不等于目标值时,则赋值给num[i],i++
|
||||
nums[i++] = nums[j];
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
111
animation-simulation/数组篇/leetcode41缺失的第一个正数.md
Normal file
111
animation-simulation/数组篇/leetcode41缺失的第一个正数.md
Normal file
@@ -0,0 +1,111 @@
|
||||
#### 41. 缺失的第一个正数
|
||||
|
||||
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
|
||||
|
||||
示例 1:
|
||||
|
||||
> 输入: [1,2,0]
|
||||
> 输出: 3
|
||||
|
||||
示例 2:
|
||||
|
||||
> 输入: [3,4,-1,1]
|
||||
> 输出: 2
|
||||
|
||||
示例 3:
|
||||
|
||||
> 输入: [7,8,9,11,12]
|
||||
> 输出: 1
|
||||
|
||||
### 重复遍历
|
||||
|
||||
让我们找出缺失的最小正整数,而且这是一个未排序的数组,我们可以利用暴力求解,挨个遍历发现那个不存在直接返回即可。我们这里使用两种方法解决这个问题,大家也可以提出自己的做法。
|
||||
|
||||
我们既然是返回缺失的正整数,那么我们则可以将这个数组中的所有正整数保存到相应的位置,见下图。
|
||||
|
||||

|
||||
|
||||
上图中,我们遍历一遍原数组,将正整数保存到新数组中,然后遍历新数组,第一次发现 newnum[i] != i 时,则说明该值是缺失的,返回即可,例如我上图中的第一个示例中的 2,如果遍历完新数组,发现说所有值都对应,说明缺失的是 新数组的长度对应的那个数,比如第二个示例中 ,新数组的长度为 5,此时缺失的为 5,返回长度即可,很容易理解。
|
||||
|
||||
注:我们发现我们新的数组长度比原数组大 1,是因为我们遍历新数组从 1,开始遍历。
|
||||
|
||||
动图解析
|
||||
|
||||

|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int firstMissingPositive(int[] nums) {
|
||||
if (nums.length == 0) {
|
||||
return 1;
|
||||
}
|
||||
//因为是返回第一个正整数,不包括 0,所以需要长度加1,细节1
|
||||
int[] res = new int[nums.length + 1];
|
||||
//将数组元素添加到辅助数组中
|
||||
for (int x : nums) {
|
||||
if (x > 0 && x < res.length) {
|
||||
res[x] = x;
|
||||
}
|
||||
}
|
||||
//遍历查找,发现不一样时直接返回
|
||||
for (int i = 1; i < res.length; i++) {
|
||||
if (res[i] != i) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
//缺少最后一个,例如 1,2,3此时缺少 4 ,细节2
|
||||
return res.length;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
我们通过上面的例子了解这个解题思想,我们有没有办法不使用辅助数组完成呢?我们可以使用原地置换,直接在 nums 数组内,将值换到对应的索引处,与上个方法思路一致,只不过没有使用辅助数组,理解起来也稍微难理解一些。
|
||||
|
||||
下面我们看一下原地置换的一些情况。
|
||||
|
||||
注:红色代表待置换,绿色代表置换完毕
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
动图解析:
|
||||
|
||||

|
||||
|
||||
题目代码:
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int firstMissingPositive(int[] nums) {
|
||||
int len = nums.length;
|
||||
if (len == 0) {
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < len; ++i) {
|
||||
//需要考虑指针移动情况,大于0,小于len+1,不等与i+1,两个交换的数相等时,防止死循环
|
||||
while (nums[i] > 0 && nums[i] < len + 1 && nums[i] != i+1 && nums[i] != nums[nums[i]-1]) {
|
||||
swap(nums,i,nums[i] - 1);
|
||||
}
|
||||
}
|
||||
//遍历寻找缺失的正整数
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if(nums[i] != i+1) {
|
||||
return i+1;
|
||||
}
|
||||
}
|
||||
return len + 1;
|
||||
}
|
||||
//交换
|
||||
public void swap(int[] nums, int i, int j) {
|
||||
if (i != j) {
|
||||
nums[i] ^= nums[j];
|
||||
nums[j] ^= nums[i];
|
||||
nums[i] ^= nums[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
84
animation-simulation/数组篇/leetcode485最大连续1的个数.md
Normal file
84
animation-simulation/数组篇/leetcode485最大连续1的个数.md
Normal file
@@ -0,0 +1,84 @@
|
||||
## **leetcode 485 最大连续 1 的个数**
|
||||
|
||||
给定一个二进制数组, 计算其中最大连续1的个数。
|
||||
|
||||
示例 1:
|
||||
|
||||
> 输入: [1,1,0,1,1,1]
|
||||
> 输出: 3
|
||||
> 解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.
|
||||
|
||||
我的这个方法比较奇怪,但是效率还可以,战胜了 100% , 尽量减少了 Math.max()的使用,我们来看一下具体思路,利用 right 指针进行探路,如果遇到 1 则继续走,遇到零时则停下,求当前 1 的个数。
|
||||
|
||||
这时我们可以通过 right - left 得到 1 的 个数,因为此时我们的 right 指针指在 0 处,所以不需要和之前一样通过 right - left + 1 获得窗口长度。
|
||||
|
||||
然后我们再使用 while 循环,遍历完为 0 的情况,跳到下一段为 1 的情况,然后移动 left 指针。 left = right,站在同一起点,继续执行上诉过程。
|
||||
|
||||
下面我们通过一个视频模拟代码执行步骤大家一下就能搞懂了。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
下面我们直接看代码吧
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int findMaxConsecutiveOnes(int[] nums) {
|
||||
|
||||
int len = nums.length;
|
||||
int left = 0, right = 0;
|
||||
int maxcount = 0;
|
||||
|
||||
while (right < len) {
|
||||
if (nums[right] == 1) {
|
||||
right++;
|
||||
continue;
|
||||
}
|
||||
//保存最大值
|
||||
maxcount = Math.max(maxcount, right - left);
|
||||
//跳过 0 的情况
|
||||
while (right < len && nums[right] == 0) {
|
||||
right++;
|
||||
}
|
||||
//同一起点继续遍历
|
||||
left = right;
|
||||
}
|
||||
return Math.max(maxcount, right-left);
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
刚才的效率虽然相对高一些,但是代码不够优美,欢迎各位改进,下面我们说一下另外一种情况,一个特别容易理解的方法。
|
||||
|
||||
我们通过计数器计数 连续 1 的个数,当 nums[i] == 1 时,count++,nums[i] 为 0 时,则先保存最大 count,再将 count 清零,因为我们需要的是连续的 1 的个数,所以需要清零。
|
||||
|
||||
好啦,下面我们直接看代码吧。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int findMaxConsecutiveOnes(int[] nums) {
|
||||
|
||||
int count = 0;
|
||||
int maxcount = 0;
|
||||
|
||||
for (int i = 0; i < nums.length; ++i) {
|
||||
if (nums[i] == 1) {
|
||||
count++;
|
||||
//这里可以改成 while
|
||||
} else {
|
||||
maxcount = Math.max(maxcount,count);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
return Math.max(count,maxcount);
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
81
animation-simulation/数组篇/leetcode54螺旋矩阵.md
Normal file
81
animation-simulation/数组篇/leetcode54螺旋矩阵.md
Normal file
@@ -0,0 +1,81 @@
|
||||
### leetcode 54 螺旋矩阵
|
||||
|
||||
题目描述
|
||||
|
||||
*给定一个包含 m* x n个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
|
||||
|
||||
|
||||
|
||||
示例一
|
||||
|
||||
> 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
|
||||
> 输出:[1,2,3,6,9,8,7,4,5]
|
||||
|
||||
示例二
|
||||
|
||||
> 输入: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]
|
||||
|
||||
|
||||
|
||||
这个题目很细非常细,思路很容易想到,但是要是完全实现也不是特别容易,我们一起分析下这个题目,我们可以这样理解,我们像剥洋葱似的一步步的剥掉外皮,直到遍历结束,见下图。
|
||||
|
||||
|
||||
|
||||
*
|
||||
|
||||
|
||||
|
||||
题目很容易理解,但是要想完全执行出来,也是不容易的,因为这里面的细节太多了,我们需要认真仔细的考虑边界。
|
||||
|
||||
|
||||
|
||||
我们也要考虑重复遍历的情况即什么时候跳出循环。刚才我们通过箭头知道了我们元素的遍历顺序,这个题目也就完成了一大半了,下面我们来讨论一下什么时候跳出循环,见下图。
|
||||
|
||||
|
||||
|
||||
注:这里需要注意的是,框框代表的是每个边界。
|
||||
|
||||

|
||||
|
||||
题目代码:
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public List<Integer> spiralOrder(int[][] matrix) {
|
||||
|
||||
List<Integer> arr = new ArrayList<>();
|
||||
int left = 0, right = matrix[0].length-1;
|
||||
int top = 0, down = matrix.length-1;
|
||||
|
||||
while (true) {
|
||||
for (int i = left; i <= right; ++i) {
|
||||
arr.add(matrix[top][i]);
|
||||
}
|
||||
top++;
|
||||
if (top > down) break;
|
||||
for (int i = top; i <= down; ++i) {
|
||||
arr.add(matrix[i][right]);
|
||||
}
|
||||
right--;
|
||||
if (left > right) break;
|
||||
for (int i = right; i >= left; --i) {
|
||||
arr.add(matrix[down][i]);
|
||||
}
|
||||
down--;
|
||||
if (top > down) break;
|
||||
for (int i = down; i >= top; --i) {
|
||||
arr.add(matrix[i][left]);
|
||||
}
|
||||
left++;
|
||||
if (left > right) break;
|
||||
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
146
animation-simulation/数组篇/leetcode560和为K的子数组.md
Normal file
146
animation-simulation/数组篇/leetcode560和为K的子数组.md
Normal file
@@ -0,0 +1,146 @@
|
||||
### leetcode560. 和为K的子数组
|
||||
|
||||
**题目描述**
|
||||
|
||||
> 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
|
||||
|
||||
**示例 1 :**
|
||||
|
||||
> 输入:nums = [1,1,1], k = 2
|
||||
> 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
|
||||
|
||||
**暴力法**
|
||||
|
||||
**解析**
|
||||
|
||||
这个题目的题意很容易理解,就是让我们返回和为 k 的子数组的个数,所以我们直接利用双重循环解决该题,这个是很容易想到的。我们直接看代码吧。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int subarraySum(int[] nums, int k) {
|
||||
int len = nums.length;
|
||||
int sum = 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
for (int j = i; j < len; ++j) {
|
||||
sum += nums[j];
|
||||
if (sum == k) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
sum = 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面我们我们使用前缀和的方法来解决这个题目,那么我们先来了解一下前缀和是什么东西。其实这个思想我们很早就接触过了。见下图
|
||||
|
||||

|
||||
|
||||
我们通过上图发现,我们的 presum 数组中保存的是 nums 元素的和,presum[1] = presum[0] + nums[0];
|
||||
|
||||
presum [2] = presum[1] + nums[1],presum[3] = presum[2] + nums[2] ... 所以我们通过前缀和数组可以轻松得到每个区间的和,
|
||||
|
||||
例如我们需要获取 nums[2] 到 nums[4] 这个区间的和,我们则完全根据 presum 数组得到,是不是有点和我们之前说的字符串匹配算法中 BM,KMP 中的 next 数组和 suffix 数组作用类似。
|
||||
|
||||
那么我们怎么根据presum 数组获取 nums[2] 到 nums[4] 区间的和呢?见下图
|
||||
|
||||

|
||||
|
||||
所以我们 nums[2] 到 nums[4] 区间的和则可以由 presum[5] - presum[2] 得到。
|
||||
|
||||
也就是前 5 项的和减去前 2 项的和,得到第 3 项到第 5 项的和。那么我们可以遍历 presum 就能得到和为 K 的子数组的个数啦。
|
||||
|
||||
直接上代码。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int subarraySum(int[] nums, int k) {
|
||||
//前缀和数组
|
||||
int[] presum = new int[nums.length+1];
|
||||
for (int i = 0; i < nums.length; i++) {
|
||||
//这里需要注意,我们的前缀和是presum[1]开始填充的
|
||||
presum[i+1] = nums[i] + presum[i];
|
||||
}
|
||||
//统计个数
|
||||
int count = 0;
|
||||
for (int i = 0; i < nums.length; ++i) {
|
||||
for (int j = i; j < nums.length; ++j) {
|
||||
//注意偏移,因为我们的nums[2]到nums[4]等于presum[5]-presum[2]
|
||||
//所以这样就可以得到nums[i,j]区间内的和
|
||||
if (presum[j+1] - presum[i] == k) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我们通过上面的例子我们简单了解了前缀和思想,那么我们如果继续将其优化呢?
|
||||
|
||||
**前缀和 + HashMap**
|
||||
|
||||
**解析**
|
||||
|
||||
其实我们在之前的两数之和中已经用到了这个方法,我们一起来回顾两数之和 HashMap 的代码.
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int[] twoSum(int[] nums, int target) {
|
||||
|
||||
HashMap<Integer,Integer> map = new HashMap<>();
|
||||
//一次遍历
|
||||
for (int i = 0; i < nums.length; ++i) {
|
||||
//存在时,我们用数组得值为 key,索引为 value
|
||||
if (map.containsKey(target - nums[i])){
|
||||
return new int[]{i,map.get(target-nums[i])};
|
||||
}
|
||||
//存入值
|
||||
map.put(nums[i],i);
|
||||
}
|
||||
//返回
|
||||
return new int[]{};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,我们将数组的值和索引存入 map 中,当我们遍历到某一值 x 时,判断 map 中是否含有 target - x,即可。其实我们现在这个题目和两数之和原理是一致的,只不过我们是将**所有的前缀和**该**前缀和出现的次数**存到了 map 里。下面我们来看一下代码的执行过程。
|
||||
|
||||
**动图解析**
|
||||
|
||||

|
||||
|
||||
**题目代码**
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int subarraySum(int[] nums, int k) {
|
||||
if (nums.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
HashMap<Integer,Integer> map = new HashMap<>();
|
||||
//细节,这里需要预存前缀和为 0 的情况,会漏掉前几位就满足的情况
|
||||
//例如输入[1,1,0],k = 2 如果没有这行代码,则会返回0,漏掉了1+1=2,和1+1+0=2的情况
|
||||
//输入:[3,1,1,0] k = 2时则不会漏掉
|
||||
//因为presum[3] - presum[0]表示前面 3 位的和,所以需要map.put(0,1),垫下底
|
||||
map.put(0, 1);
|
||||
int count = 0;
|
||||
int presum = 0;
|
||||
for (int x : nums) {
|
||||
presum += x;
|
||||
//当前前缀和已知,判断是否含有 presum - k的前缀和,那么我们就知道某一区间的和为 k 了。
|
||||
if (map.containsKey(presum - k)) {
|
||||
count += map.get(presum - k);//获取presum-k前缀和出现次数
|
||||
}
|
||||
//更新
|
||||
map.put(presum,map.getOrDefault(presum,0) + 1);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
143
animation-simulation/数组篇/leetcode59螺旋矩阵2.md
Normal file
143
animation-simulation/数组篇/leetcode59螺旋矩阵2.md
Normal file
@@ -0,0 +1,143 @@
|
||||
|
||||
|
||||
### leetcode 59 螺旋矩阵 2
|
||||
|
||||
给你一个正整数 `n` ,生成一个包含 `1` 到 `n2` 所有元素,且元素按顺时针顺序螺旋排列的 `n x n` 正方形矩阵 `matrix` 。
|
||||
|
||||
**示例 1:**
|
||||
|
||||
> 输入:n = 3
|
||||
> 输出:[[1,2,3],[8,9,4],[7,6,5]]
|
||||
|
||||
**示例 2:**
|
||||
|
||||
> 输入:n = 1
|
||||
> 输出:[[1]]
|
||||
|
||||
其实我们只要做过了螺旋矩阵 第一题,这个题目我们完全可以一下搞定,几乎没有进行更改,我们先来看下 **leetcode 54** 题的解析。
|
||||
|
||||
|
||||
|
||||
### leetcode 54 螺旋矩阵
|
||||
|
||||
题目描述
|
||||
|
||||
*给定一个包含 m* x n个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
|
||||
|
||||
|
||||
|
||||
示例一
|
||||
|
||||
> 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
|
||||
> 输出:[1,2,3,6,9,8,7,4,5]
|
||||
|
||||
示例二
|
||||
|
||||
> 输入: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]
|
||||
|
||||
|
||||
|
||||
这个题目很细非常细,思路很容易想到,但是要是完全实现也不是特别容易,我们一起分析下这个题目,我们可以这样理解,我们像剥洋葱似的一步步的剥掉外皮,直到遍历结束,见下图。
|
||||
|
||||
|
||||
|
||||
**
|
||||
|
||||
|
||||
|
||||
题目很容易理解,但是要想完全执行出来,也是不容易的,因为这里面的细节太多了,我们需要认真仔细的考虑边界。
|
||||
|
||||
|
||||
|
||||
我们也要考虑重复遍历的情况即什么时候跳出循环。刚才我们通过箭头知道了我们元素的遍历顺序,这个题目也就完成了一大半了,下面我们来讨论一下什么时候跳出循环,见下图。
|
||||
|
||||
|
||||
|
||||
注:这里需要注意的是,框框代表的是每个边界。
|
||||
|
||||

|
||||
|
||||
题目代码:
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public List<Integer> spiralOrder(int[][] matrix) {
|
||||
|
||||
List<Integer> arr = new ArrayList<>();
|
||||
int left = 0, right = matrix[0].length-1;
|
||||
int top = 0, down = matrix.length-1;
|
||||
|
||||
while (true) {
|
||||
for (int i = left; i <= right; ++i) {
|
||||
arr.add(matrix[top][i]);
|
||||
}
|
||||
top++;
|
||||
if (top > down) break;
|
||||
for (int i = top; i <= down; ++i) {
|
||||
arr.add(matrix[i][right]);
|
||||
}
|
||||
right--;
|
||||
if (left > right) break;
|
||||
for (int i = right; i >= left; --i) {
|
||||
arr.add(matrix[down][i]);
|
||||
}
|
||||
down--;
|
||||
if (top > down) break;
|
||||
for (int i = down; i >= top; --i) {
|
||||
arr.add(matrix[i][left]);
|
||||
}
|
||||
left++;
|
||||
if (left > right) break;
|
||||
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
我们仅仅是将 54 反过来了,往螺旋矩阵里面插值,下面我们直接看代码吧,大家可以也可以对其改进,大家可以思考一下,如果修改能够让代码更简洁!
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int[][] generateMatrix(int n) {
|
||||
|
||||
int[][] arr = new int[n][n];
|
||||
int left = 0;
|
||||
int right = n-1;
|
||||
int top = 0;
|
||||
int buttom = n-1;
|
||||
int num = 1;
|
||||
int numsize = n*n;
|
||||
while (true) {
|
||||
for (int i = left; i <= right; ++i) {
|
||||
arr[top][i] = num++;
|
||||
}
|
||||
top++;
|
||||
if (num > numsize) break;
|
||||
for (int i = top; i <= buttom; ++i) {
|
||||
arr[i][right] = num++;
|
||||
|
||||
}
|
||||
right--;
|
||||
if (num > numsize) break;
|
||||
for (int i = right; i >= left; --i) {
|
||||
arr[buttom][i] = num++;
|
||||
}
|
||||
buttom--;
|
||||
if (num > numsize) break;
|
||||
for (int i = buttom; i >= top; --i) {
|
||||
arr[i][left] = num++;
|
||||
}
|
||||
left++;
|
||||
if (num > numsize) break;
|
||||
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
60
animation-simulation/数组篇/leetcode66加一.md
Normal file
60
animation-simulation/数组篇/leetcode66加一.md
Normal file
@@ -0,0 +1,60 @@
|
||||
### leetcode 66 加一
|
||||
|
||||
**题目描述**
|
||||
|
||||
> 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
|
||||
>
|
||||
> 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
|
||||
>
|
||||
> 你可以假设除了整数 0 之外,这个整数不会以零开头。
|
||||
|
||||
**示例 1:**
|
||||
|
||||
> 输入:digits = [1,2,3]
|
||||
> 输出:[1,2,4]
|
||||
> 解释:输入数组表示数字 123。
|
||||
|
||||
**示例 2:**
|
||||
|
||||
> 输入:digits = [4,3,2,1]
|
||||
> 输出:[4,3,2,2]
|
||||
> 解释:输入数组表示数字 4321。
|
||||
|
||||
**示例 3:**
|
||||
|
||||
输入:digits = [0]
|
||||
输出:[1]
|
||||
|
||||
**数组遍历**
|
||||
|
||||
**题目解析**
|
||||
|
||||
我们思考一下,加一的情况一共有几种情况呢?是不是有以下三种情况
|
||||
|
||||

|
||||
|
||||
则我们根据什么来判断属于第几种情况呢?
|
||||
|
||||
我们可以根据当前位 余10来判断,这样我们就可以区分属于第几种情况了,大家直接看代码吧,很容易理解的。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int[] plusOne(int[] digits) {
|
||||
//获取长度
|
||||
int len = digits.length;
|
||||
for (int i = len-1; i >= 0; i--) {
|
||||
digits[i] = (digits[i] + 1) % 10;
|
||||
//第一种和第二种情况,如果此时某一位不为 0 ,则直接返回即可。
|
||||
if (digits[i] != 0) {
|
||||
return digits;
|
||||
}
|
||||
|
||||
}
|
||||
//第三种情况,因为数组初始化每一位都为0,我们只需将首位设为1即可
|
||||
int[] arr = new int[len+1];
|
||||
arr[0] = 1;
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
112
animation-simulation/数组篇/leetcode75颜色分类.md
Normal file
112
animation-simulation/数组篇/leetcode75颜色分类.md
Normal file
@@ -0,0 +1,112 @@
|
||||
**leetcode 75 颜色分类**
|
||||
|
||||
题目描述:
|
||||
|
||||
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
|
||||
|
||||
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
|
||||
|
||||
示例 1:
|
||||
|
||||
> 输入:nums = [2,0,2,1,1,0]
|
||||
> 输出:[0,0,1,1,2,2]
|
||||
|
||||
示例 2:
|
||||
|
||||
> 输入:nums = [2,0,1]
|
||||
> 输出:[0,1,2]
|
||||
|
||||
示例 3:
|
||||
|
||||
> 输入:nums = [0]
|
||||
> 输出:[0]
|
||||
|
||||
示例 4:
|
||||
|
||||
> 输入:nums = [1]
|
||||
> 输出:[1]
|
||||
|
||||
**做题思路**
|
||||
|
||||
这个题目我们使用 Arrays.sort() 解决,哈哈,但是那样太无聊啦,题目含义就是让我们将所有的 0 放在前面,2放在后面,1 放在中间,是不是和我们上面说的荷兰国旗问题一样。我们仅仅将 1 做为 pivot 值。
|
||||
|
||||
下面我们直接看代码吧,和三向切分基本一致。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public void sortColors(int[] nums) {
|
||||
int len = nums.length;
|
||||
int left = 0;
|
||||
//这里和三向切分不完全一致
|
||||
int i = left;
|
||||
int right = len-1;
|
||||
|
||||
while (i <= right) {
|
||||
if (nums[i] == 2) {
|
||||
swap(nums,i,right--);
|
||||
} else if (nums[i] == 0) {
|
||||
swap(nums,i++,left++);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void swap (int[] nums, int i, int j) {
|
||||
int temp = nums[i];
|
||||
nums[i] = nums[j];
|
||||
nums[j] = temp;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
另外我们看这段代码,有什么问题呢?那就是我们即使完全符合时,仍会交换元素,这样会大大降低我们的效率。
|
||||
|
||||
例如:[0,0,0,1,1,1,2,2,2]
|
||||
|
||||
此时我们完全符合情况,不需要交换元素,但是按照我们上面的代码,0,2 的每个元素会和自己进行交换,所以这里我们可以根据 i 和 left 的值是否相等来决定是否需要交换,大家可以自己写一下。
|
||||
|
||||
下面我们看一下另外一种写法
|
||||
|
||||
这个题目的关键点就是,当我们 nums[i] 和 nums[right] 交换后,我们的 nums[right] 此时指向的元素是符合要求的,但是我们 nums[i] 指向的元素不一定符合要求,所以我们需要继续判断。
|
||||
|
||||

|
||||
|
||||
我们 2 和 0 交换后,此时 i 指向 0 ,0 应放在头部,所以不符合情况,所以 0 和 1 仍需要交换。下面我们来看一下动画来加深理解吧。
|
||||
|
||||

|
||||
|
||||
另一种代码表示
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public void sortColors(int[] nums) {
|
||||
|
||||
int left = 0;
|
||||
int len = nums.length;
|
||||
int right = len - 1;
|
||||
for (int i = 0; i <= right; ++i) {
|
||||
if (nums[i] == 0) {
|
||||
swap(nums,i,left);
|
||||
left++;
|
||||
}
|
||||
if (nums[i] == 2) {
|
||||
swap(nums,i,right);
|
||||
right--;
|
||||
//如果不等于 1 则需要继续判断,所以不移动 i 指针,i--
|
||||
if (nums[i] != 1) {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public void swap (int[] nums,int i, int j) {
|
||||
int temp = nums[i];
|
||||
nums[i] = nums[j];
|
||||
nums[j] = temp;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
好啦,这个问题到这就结束啦,是不是很简单啊,我们明天见!
|
||||
|
||||
76
animation-simulation/数组篇/剑指offer3数组中重复的数.md
Normal file
76
animation-simulation/数组篇/剑指offer3数组中重复的数.md
Normal file
@@ -0,0 +1,76 @@
|
||||
### 剑指 offer 3 数组中重复的数字
|
||||
|
||||
**题目描述**
|
||||
|
||||
找出数组中重复的数字。
|
||||
|
||||
|
||||
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
|
||||
|
||||
示例 1:
|
||||
|
||||
输入:
|
||||
[2, 3, 1, 0, 2, 5, 3]
|
||||
输出:2 或 3
|
||||
|
||||
#### **HashSet**
|
||||
|
||||
**解析**
|
||||
|
||||
这种题目或许一下就让人想到 HashSet,题目描述很清楚就是让我们找到数组中重复的元素,那我们第一下想到的就是 HashSet,我们遍历数组,如果发现 set 含有该元素则返回,不含有则存入哈希表,题目代码也很简单
|
||||
|
||||
**题目代码**
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int findRepeatNumber(int[] nums) {
|
||||
// HashSet
|
||||
HashSet<Integer> set = new HashSet<Integer>();
|
||||
for (int x : nums) {
|
||||
//发现某元素存在,返回
|
||||
if (set.contains(x)) {
|
||||
return x;
|
||||
}
|
||||
//存入哈希表
|
||||
set.add(x);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **原地置换**
|
||||
|
||||
**解析**
|
||||
|
||||
这一种方法也是我们经常用到的,主要用于重复出现的数,缺失的数等题目中,下面我们看一下这个原地置换法,原地置换的大体思路就是将我们**指针对应**的元素放到属于他的位置(索引对应的地方)。我们可以这样理解,每个人都有自己的位置,我们需要和别人调换回到属于自己的位置,调换之后,如果发现我们的位置上有人了,则返回。大致意思了解了,下面看代码的执行过程。通过视频一下就可以搞懂啦。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
**题目代码**
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int findRepeatNumber(int[] nums) {
|
||||
if (nums.length == 0) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < nums.length; ++i) {
|
||||
while (nums[i] != i) {
|
||||
//发现重复元素
|
||||
if (nums[i] == nums[nums[i]]) {
|
||||
return nums[i];
|
||||
}
|
||||
//置换,将指针下的元素换到属于他的索引处
|
||||
int temp = nums[i];
|
||||
nums[i] = nums[temp];
|
||||
nums[temp] = temp;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user